diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ebc3520..f0a7165 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,12 +14,25 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.11" - - run: pip install ruff mypy + # Pinned to the pre-commit revisions so CI and local hooks agree. + - run: pip install ruff==0.4.4 - run: ruff check src tests - run: ruff format --check src tests - test: - needs: lint + type: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + # Pinned to the pre-commit revision (see .pre-commit-config.yaml). + - run: pip install mypy==1.10.0 pydantic numpy + - run: pip install -e . + - run: mypy src/otko/core src/otko/services + + test-headless: + needs: [lint, type] strategy: fail-fast: false matrix: @@ -31,8 +44,18 @@ jobs: - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - run: pip install -e ".[dev]" + - run: pytest tests/unit tests/services -m "not slow" + + test-gui: + needs: [lint, type] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" - name: Install Linux Qt deps - if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y libegl1 libxkbcommon-x11-0 libxcb-icccm4 \ @@ -40,9 +63,19 @@ jobs: libxcb-shape0 libxcb-sync1 libxcb-xfixes0 libxcb-xinerama0 \ libxcb-cursor0 libdbus-1-3 libgl1 libxkbcommon0 xvfb - run: pip install -e ".[dev]" - - name: Run tests (Linux, headless) - if: runner.os == 'Linux' - run: xvfb-run -a pytest --cov --cov-report=xml -m "not slow" - - name: Run tests (Win/Mac) - if: runner.os != 'Linux' - run: pytest --cov --cov-report=xml -m "not slow" + - run: xvfb-run -a pytest tests/gui -m "not slow" + + test-integration: + needs: [lint, type] + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - run: pip install -e ".[dev]" + - run: pytest tests/integration -m "not slow" \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f911d7..ab9023e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,37 +1,97 @@ # Contributing -Early-stage project. The bar is architecture cleanliness, not feature -count. If your change breaks a layering rule below, it won't merge — -no matter how useful the feature. +Thanks for helping with OTKO — a SAP2000-style desktop GUI for +OpenSeesPy. Early-stage project: the bar is architecture cleanliness, not +feature count. If your change breaks a layering rule below, it won't +merge — no matter how useful the feature. ## Dev setup ```bash python -m venv .venv -source .venv/bin/activate -pip install -e ".[dev]" +source .venv/bin/activate # Linux / macOS +# .venv\Scripts\activate # Windows +pip install -e ".[gui,dev]" pre-commit install ``` +`pip install -e ".[gui,dev]"` pulls the Qt/PyVista desktop stack plus the +dev tools. For a headless checkout (core + services only, no Qt) use +`pip install -e .` instead. Python 3.10+; on Windows use 3.12+. + +Launch the GUI with: + +```bash +python -m otko +``` + +`pre-commit install` wires ruff + mypy into your local git hooks so +obvious issues are caught before a commit. Run it once per clone. + ## Before opening a PR +Run the verify commands in this order and make sure they are all clean: + ```bash ruff check src tests -ruff format src tests +ruff format src tests # line-length 100, E501 ignored mypy src/otko/core src/otko/services -pytest -m "not slow" +pytest -m "not slow" # CI gate: lint → this, on 3.10/3.11/3.12 × ubuntu/windows/macos ``` +Focused runs are faster while iterating: `pytest tests/unit` (pure logic, +milliseconds), `pytest tests/gui -k ` (pytest-qt, needs a display; +Linux GUI tests want `xvfb-run -a pytest ...`), and +`pytest tests/integration -k ` (real OpenSeesPy runs). Single test: +`pytest tests/unit/test_project.py::test_name -q`. Markers: `gui`, `slow`. + ## Architectural rules (enforced in review) -1. `core/` may not import Qt or `openseespy`. Period. -2. `services/` may not import Qt. -3. `views/` may not import `openseespy` directly — go through a service. -4. Public functions and methods need type hints and a docstring. -5. New domain entities go through Pydantic validation. -6. Long-running operations (>50 ms) run off the GUI thread. +OTKO uses a strict one-way **MVVM + service** architecture. Dependencies +flow outward-in only: + +``` +views → viewmodels → services → core +``` + +`commands` sits alongside the bridge and owns every model mutation. + +1. `core/` is pure Python — stdlib + numpy + pydantic. It may **not** + import Qt or `openseespy`. Period. +2. `services/` may use `core` + `h5py` + `openseespy`, but may **not** + import Qt. +3. `views/` (PySide6/pyvistaqt) may **not** import `openseespy` directly — + go through a service. No business logic in `views`. +4. `viewmodels/` bridges `core` ↔ Qt (signals, `QUndoStack`). +5. `commands/` holds the `QUndoCommand` subclasses; all model mutations + go through `commands`, not ad-hoc edits in `views`. +6. Public functions and methods need type hints and a docstring. +7. New domain entities go through Pydantic validation. +8. Long-running operations (>50 ms) run off the GUI thread (`AnalysisWorker` + in a `QThread`, cancelled via `isInterruptionRequested()`; results cross + threads as a lightweight `ResultsHandle` written to HDF5). + +The full package map and the fixed OpenSeesPy command order the runner +emits live in [`docs/architecture.md`](docs/architecture.md). Never +reorder the runner's commands. + +## Branch model + +`main` is the default and integration branch. Work on short-lived topic +branches cut from `main` — `feat/`, `fix/`, or +`docs/` — and open a pull request against `main`. Do not commit +directly to `main` from an agent session. There is no `develop` branch. ## Commit style Conventional Commits — `feat:`, `fix:`, `refactor:`, `docs:`, `test:`, -`chore:`, `ci:`. +`chore:`, `ci:`. Keep each commit focused; a `feat:` commit should add a +feature, not mix one in with unrelated refactors. + +## Documentation + +If your change is user-visible, update +[`docs/QUICK_GUIDE.md`](docs/QUICK_GUIDE.md) and the relevant roadmap or +ADR entry. Project model files are `.osmodel` (Pydantic-validated JSON); +regenerate the checked-in `examples/*.osmodel` from their scripts with +`python examples/.py` rather than hand-editing them. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..10a928c --- /dev/null +++ b/NOTICE @@ -0,0 +1,70 @@ +# NOTICE + +OTKO +Copyright © 2026 Ozan and contributors. + +## OTKO license + +OTKO's own source code is licensed under the **GNU Affero General Public +License v3.0** (AGPL-3.0). The full text is in [`LICENSE`](LICENSE). This +`NOTICE` file does not replace or modify that license; where the two +disagree, `LICENSE` governs. + +OTKO is **not** an MIT-licensed project. Portions of the codebase were +ported or adapted from an earlier, MIT-licensed prototype called +`otko-development`, so the original MIT notice is reproduced below as +required by that license. + +## Ported / adapted code: `otko-development` + +Portions of OTKO were ported or adapted from the `otko-development` +project, which is distributed under the MIT License. The MIT +copyright and permission notice is reproduced verbatim below: + +``` +MIT License + +Copyright (c) 2026 OTKO contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## Runtime dependencies + +OTKO depends on third-party software that is not covered by OTKO's +AGPL-3.0 license. Each component remains under its own license, and its +license text ships with the corresponding package. The notes below are a +summary for attribution, not a substitute for those license texts. + +- **OpenSees / OpenSeesPy** — the finite-element solver invoked by + `otko.services.opensees_runner`. OpenSees is copyright The Regents of + the University of California and is distributed under a BSD-style + license; the notice is shipped inside the `openseespy` package. +- **PySide6** — the Qt 6 bindings used by the desktop GUI. PySide6 is + available under the GNU Lesser General Public License v3 (LGPLv3) or a + commercial license. OTKO links against it dynamically, which keeps the + LGPL relinking obligation satisfiable for redistributors. +- **numpy**, **pydantic**, **h5py**, **pyvista**, **VTK**, + **pyqtgraph**, and **imageio** — each is distributed under its own + license (for example BSD-3-Clause, MIT, and similar permissive terms). + See the license file bundled with each installed package for the exact + terms. + +When redistributing OTKO, keep `LICENSE`, this `NOTICE`, and the license +notices of the dependencies above. diff --git a/README.md b/README.md index 38f1fd9..c291d67 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,13 @@ views (Qt) → viewmodels → services (OpenSeesRunner, Persistence) → c Long version in [`docs/architecture.md`](docs/architecture.md), including the OpenSeesPy command order the runner emits. +## Documentation + +Practical, task-first walkthroughs live in +[`docs/QUICK_GUIDE.md`](docs/QUICK_GUIDE.md) — a cantilever build, +modal analysis, report/script export, display units, and undo/redo. +The full index is [`docs/README.md`](docs/README.md). + ## Install (development) **Desktop GUI** (Qt, PyVista, pyqtgraph, imageio): diff --git a/docs/QUICK_GUIDE.md b/docs/QUICK_GUIDE.md new file mode 100644 index 0000000..0bc4b8c --- /dev/null +++ b/docs/QUICK_GUIDE.md @@ -0,0 +1,132 @@ +# OTKO Quick Guide + +A practical, task-first guide to the OTKO desktop GUI. It assumes you +have already installed the desktop extras and can launch the app: + +```bash +pip install -e ".[gui,dev]" +python -m otko +``` + +Project files use the `.osmodel` extension — a single, Pydantic-validated +JSON document that diffs cleanly in Git. Analysis output is written +separately to `*.osresults.h5`. + +For the layer map and the OpenSeesPy command order, see +[`architecture.md`](architecture.md). For the feature-by-feature plan, see +[`roadmap.md`](roadmap.md). + +## 1. A cantilever walkthrough + +This follows the bundled `examples/cantilever.osmodel` model: a 5 m +horizontal beam, fixed at the left end, with a tip load. If you would +rather build it by hand, the steps are below. + +1. **Start a project.** **File → New (3D Frame)**. Pick display units in + the bottom-right **Units** combo before typing any values. +2. **Lay out a grid.** **Define → Coordinate System/Grids…** (Ctrl+G). + Define X lines at 0…5 m (say, every 1 m), Y = 0, Z = 0, and set the + grid as the active coordinate system. The 3D canvas will draw it as + reference geometry. +3. **Add nodes.** **Define → Add Node…** (Ctrl+N), or use the **Draw + Node** tool and click on grid intersections at (0,0,0) … (5,0,0). +4. **Define material and section.** **Define → Material Library…** + (Ctrl+Shift+M) then **Define → Section Library…** (Ctrl+Shift+S). The + example uses a steel `ElasticSection` named `W12x40`. +5. **Draw the element.** **Assign/Define → Draw Frame** (F2), then click + from the first node to the last. Assign the section with + **Assign → Frame → Section…**. +6. **Add the support.** Select the node at x = 0 and use + **Assign → Joint → Restraints…** (Ctrl+R); restrain all six DOF. The + support icon confirms the fixed end. +7. **Add the load.** Select the tip node and use **Assign → Joint → + Point Loads…** (Ctrl+L). The example applies -10 kN in Y. Alternatively + build the distributed case with **Assign → Frame → Distributed + Load…**. +8. **Set up and run the case.** **Analyze → Cases…** (Ctrl+Shift+A) to + create or review a Static case, then **Analyze → Run…** (F5). The + bundled file already contains `Tip-Load`, `Uniform-Load`, and a modal + `Modal-3` case. + +### Smoke check + +Open `examples/cantilever.osmodel`, run the `Tip-Load` static case, then +**Display → Show Force Diagram… → M3**. The moment diagram is linear and +peaks at **50 kN·m at the fixed end**. V2 is a constant -10 kN along the +span. If you see that, the model, runner, and post-processor are wired up +correctly. + +## 2. Running a modal analysis + +1. Open a model that has mass assigned (the bundled cantilever lumps mass + at every free node so modal works out of the box). +2. **Analyze → Cases…**, add or select a **Modal** case, and set the + number of modes `n_modes` (the example uses 3). +3. **Analyze → Run…** (F5). Results appear in the results/report panel: + periods, frequencies, and participation factors per mode. +4. **Display → Animate Mode Shape** to view each mode. Use the mode + selector and the animation controls, and **Export…** if you want a + video of the mode shape. +5. Modal results also feed the response-spectrum case: define a response + spectrum, then run the SRSS or CQC combination and open + **Display → Show Response Spectrum**. + +## 3. Reviewing results and exporting a report or script + +After a run, the results/report panel shows a summary for the active case +(static reactions and forces, modal periods, and so on). Use the display +actions to inspect the model visually: + +- **Display → Show Deformed Shape** — with a scale slider. +- **Display → Show Force Diagram…** — axial (P), shear (V2/V3), moment + (M2/M3) diagrams. +- **Display → Show Pushover Curve**, **Show Time-History**, **Show + Hysteresis** as applicable. + +To hand the analysis to someone else, or to archive exactly what was run, +export a script: + +- **File → Export OpenSeesPy (.py)…** — writes the full model, and + optionally a selected analysis case, as a runnable Python script. +- **File → Export Tcl (.tcl)…** — the same model as classic OpenSees Tcl. + +The export dialog lets you choose "Model only (no analysis case)" or one +of the configured cases. The generated script follows the runner's fixed +command order (`wipe → model → node → fix → … → analyze`), so it +reproduces the analysis outside the GUI. + +## 4. Changing display units + +Use either control, they are the same setting: + +- The **Units** combo in the bottom-right of the status bar, or +- **Options → Set Display Units…** + +Changing units updates how lengths, forces, and moments are formatted in +the UI and plots. It does **not** rescale the underlying model numbers — +pick the right unit system before you type values, and convert +deliberately if you switch later. A set of unit labels is available in the +unit-label tests under `tests/unit/test_unit_labels.py`. + +## 5. Undo and redo + +Every model mutation goes through the undo stack, so most edits are +reversible: + +- **Edit → Undo** (Ctrl+Z) +- **Edit → Redo** (Ctrl+Y / Ctrl+Shift+Z) + +Menu text is dynamic — it names the operation, for example "Undo Add 4 +Nodes". Compound operations such as drawing a frame (node + element) are +wrapped in a single macro, so one undo removes the whole step. File +loads, analysis runs, and display-only changes are not model mutations and +are not undoable. + +## Where to go next + +- [`architecture.md`](architecture.md) — MVVM layering and command order. +- [`roadmap.md`](roadmap.md) — what is done and what is planned. +- `examples/` — 20+ verified models, each generated from a checked-in + Python script. +- [`../CONTRIBUTING.md`](../CONTRIBUTING.md) — setup, rules, and verify + commands. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..9ef3bfb --- /dev/null +++ b/docs/README.md @@ -0,0 +1,26 @@ +# OTKO Documentation + +Index of the project documentation. Start with the quick guide if you +just want to build and run a model; read the architecture page if you are +changing code. + +| Document | What it covers | +| --- | --- | +| [QUICK_GUIDE.md](QUICK_GUIDE.md) | Task-first walkthrough: cantilever model, modal analysis, report and script export, display units, undo/redo. | +| [architecture.md](architecture.md) | MVVM layering, package responsibilities, threading, persistence, and the fixed OpenSeesPy command order. | +| [roadmap.md](roadmap.md) | Phase-by-phase feature status, from scaffolding through the earthquake-engineering primitives. | +| [adr/](adr/) | Architecture Decision Records — the "why" behind individual design choices. | +| [screenshots/](screenshots/) | Screenshots referenced by the docs and README. | + +## Architecture Decision Records + +- [ADR-0001 — GiD/OpenSees schema import](adr/ADR-0001-gidopensees-schema-import.md) +- [ADR-0002 — Headless / GUI dependency split](adr/ADR-0002-headless-gui-dep-split.md) + +## Related documentation + +- [`../README.md`](../README.md) — project overview, install, and quick start. +- [`../CONTRIBUTING.md`](../CONTRIBUTING.md) — dev setup, layering rules, commit style, and verify commands. +- [`../AGENTS.md`](../AGENTS.md) — condensed context for automated agents. +- [`gap-analysis-gidopensees.md`](gap-analysis-gidopensees.md) — gap analysis against the GiD/OpenSees reference. +- [`../examples/README.md`](../examples/README.md) — the bundled example models. diff --git a/pyproject.toml b/pyproject.toml index 93bfc44..ffab17d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "otko" -version = "0.1.0" +dynamic = ["version"] description = "OTKO — a modern, SAP2000-style desktop GUI for OpenSeesPy." readme = "README.md" requires-python = ">=3.10" @@ -42,10 +42,6 @@ gui = [ "vtk>=9.3", "pyqtgraph>=0.13", "imageio[ffmpeg]>=2.34", - # scipy and pandas are listed for forward-compatibility; they are not - # currently imported by any module in this repo (phantom deps as of 2026-06-15). - "scipy>=1.11", - "pandas>=2.1", ] dev = [ "pytest>=7.4", @@ -55,6 +51,7 @@ dev = [ "mypy>=1.8", "pre-commit>=3.6", "hatch>=1.9", + "tomli>=2.0; python_version < '3.11'", ] docs = [ "sphinx>=7.2", @@ -72,6 +69,9 @@ Issues = "https://github.com/ogunc/otko/issues" [tool.hatch.build.targets.wheel] packages = ["src/otko"] +[tool.hatch.version] +path = "src/otko/_const.py" + # ─────────── Ruff ─────────── [tool.ruff] line-length = 100 @@ -89,7 +89,25 @@ select = [ "SIM", # simplify "RUF", # ruff-specific ] -ignore = ["E501"] # line length handled by formatter +ignore = [ + "E501", # line-length handled by the formatter + "RUF001", # ambiguous unicode: engineering notation (×, ≈, ²) is deliberate + "RUF002", + "RUF003", +] + +[tool.ruff.lint.per-file-ignores] +# Engineering symbols (A, I, L, E, P) and `importorskip`-then-import blocks +# are normal in tests. +"tests/**" = ["E402", "E741", "N802", "N806", "N815"] +# Qt requires camelCase signal names and PascalCase event overrides. +"src/otko/views/**" = ["N802", "N815"] +"src/otko/viewmodels/**" = ["N815"] +# Material fields mirror the OpenSees parameter names verbatim (cR1, epsU, ...). +"src/otko/core/materials/__init__.py" = ["N815"] +# Engineering symbols in numeric code: L (length), Iz/Iy (second moments). +"src/otko/services/section_properties.py" = ["N806"] +"src/otko/views/canvas3d/model_renderer.py" = ["N802", "N806", "N815"] [tool.ruff.format] quote-style = "double" @@ -97,12 +115,13 @@ quote-style = "double" # ─────────── Mypy ─────────── [tool.mypy] python_version = "3.10" -strict = true -warn_unused_ignores = true -warn_return_any = true -disallow_untyped_defs = true +warn_unused_configs = true plugins = [] +[[tool.mypy.overrides]] +module = ["otko.core.*", "otko.services.*", "otko.viewmodels.*"] +strict = true + [[tool.mypy.overrides]] module = ["openseespy.*", "pyvista.*", "pyvistaqt.*", "vtk.*", "pyqtgraph.*"] ignore_missing_imports = true @@ -121,3 +140,8 @@ markers = [ branch = true source = ["src/otko"] omit = ["src/otko/views/*"] # GUI excluded from coverage targets + +[tool.coverage.report] +exclude_lines = ["if TYPE_CHECKING:", "raise NotImplementedError"] +# Conservative floor for the headless suite; raise deliberately with evidence. +fail_under = 50 diff --git a/specifications/15-rebuild-adoption-plan.md b/specifications/15-rebuild-adoption-plan.md new file mode 100644 index 0000000..f10ddfd --- /dev/null +++ b/specifications/15-rebuild-adoption-plan.md @@ -0,0 +1,383 @@ +# 15 — Rebuild Adoption Plan + +> **Status:** proposed. **Source of truth for intent:** the greenfield +> rebuild specs at `~/Sync/otko-development/specifications` (`00`–`14`). +> This document plans how to bring that rebuild's good ideas into **this** +> repo without breaking the working app. +> +> Requirement IDs are shown without brackets for brevity (e.g. `PKG-032` = +> `[PKG-032]` in the rebuild set) so they stay grep-able. + +--- + +## 1. Context + +| | This repo (`~/Sync/otko`) | Rebuild (`~/Sync/otko-development`) | +|---|---|---| +| Role | **Working app** — the primary deliverable | Greenfield spec-conformant attempt | +| VCS | Not a git repo in this checkout | `rebuild` branch, commit `725040e` | +| License | AGPL-3.0 | MIT (+ `NOTICE`) | +| Python | 3.10–3.12 | 3.12 only | +| Project file | `.osmodel` | `.otko` | +| Scope | Superset: static, modal, **transient, pushover, response-spectrum**, quad/shell-adjacent elements, HDF5 | Narrow v1: static + modal; reports, combinations, self-weight, units display | +| src LOC | ~37k | ~25k | +| Gate | deps not installed here; **57 collection errors** locally | `ruff` passes; `mypy`/`pytest` deps missing locally | + +The rebuild is *ahead* on: **reporting/Typst, load combinations, self-weight, +display-unit conversion, closed-form diagrams, modal mass participation, +progress/cancel, local-axis editing, viewmodel separation, and quality +gates**. It is *behind* on everything this repo already ships. Therefore the +plan is a **selective harvest**, not a merge. + +## 2. Goal and non-goals + +**Goal.** Adopt the rebuild's good capabilities into this repo, incrementally, +each phase independently shippable, with the existing app and tests staying +green. + +**Non-goals.** +- Do **not** rewrite this repo to the rebuild's architecture (`ProjectStore`, + frozen `Project`, `CommandFactory`, `services/scene.py`, + `services/diagram_data.py` substrates). +- Do **not** replace the runner/export/results surface — this repo's is a + strict superset. +- Do **not** change `.osmodel`, the AGPL license, or the Python 3.10–3.12 + support range as part of this plan. + +## 3. Principles + +1. **Additive, adapt, don't copy.** Port algorithms/patterns; re-type to this + repo's APIs (`StaticResults`/`ModalResults`, mutable `Project`, + 4-value `UnitSystem`, `element_forces.DiagramData`). +2. **Protect the working app.** Every phase keeps `pytest -m "not slow"` and + the GUI smoke tests green. New optional deps stay **lazy** so headless + installs are unaffected. +3. **One phase = one branch = one shippable PR**, with tests and a gate. +4. **Trace every item to a rebuild spec ID** so "is it done?" stays mechanical. +5. **License hygiene.** MIT→AGPL-3.0 is one-way compatible: preserve the MIT + copyright/permission notice for ported files (`NOTICE` + per-file header). + Never copy this repo's AGPL code back into the MIT rebuild. +6. **Establish version control first.** This checkout has **no `.git`**. Before + any work, create a repo/branch per `AGENTS.md` (`origin` is the self-hosted + Forgejo; work on `feat/`, PR against `main`). + +## 4. Adoption matrix + +Verdicts: **PORT** (near-verbatim), **ADAPT** (re-type/re-wire), **REIMPL** +(reimplement against this repo's APIs), **SKIP** (intentional divergence). +Size: **S** ≈ hours · **M** ≈ a few days · **L** ≈ 1–2+ weeks. + +### 4.1 Quality / foundations + +| # | Capability | Rebuild source | Verdict | Phase | Size | Spec IDs | +|---|---|---|---|---|---|---| +| Q1 | Single-source version + solver pin via `_const.py` | `src/otko/_const.py`, `pyproject.toml` `[tool.hatch.version]` | ADAPT | 0 | S | PKG-032/033, RUN-085 | +| Q2 | Layering/import-discipline AST tests | `tests/unit/test_architecture.py` (246) | ADAPT (paths) | 0 | M | ARC-001..003/006/011/060..064 | +| Q3 | CI split into 5 jobs incl. missing **`type` (mypy)** job; mark integration `slow` | `.github/workflows/ci.yml`, `tests/unit/test_ci_config.py`, `test_meta_gates.py` | ADAPT | 0 | M | TST-004/031/040/041 | +| Q4 | conftest hardening (offscreen default; `ops.wipe()` in teardown) | `tests/conftest.py` | ADAPT | 0 | S | TST §7 | +| Q5 | `tests/services/` split from `tests/unit/` | tests layout | ADAPT | 0 | M | TST-002 | +| Q6 | Standalone docs + docs test | `docs/QUICK_GUIDE.md`, `docs/README.md`, `tests/unit/test_docs.py` | ADAPT | 0 | M | — | +| Q7 | `NOTICE` + attribution for ported MIT code | `NOTICE` | REIMPL (AGPL wording) | 0 | S | PKG-042 | +| Q8 | Drop phantom `scipy`/`pandas` from `[gui]` | `pyproject.toml`, `tests/unit/test_packaging.py` | ADAPT | 0 | S | PKG-011 | +| Q9 | Scoped mypy overrides + coverage `fail_under` | `pyproject.toml` | ADAPT | 0 | S | TST-031/032 | +| Q10 | Packaging config tests subset (entrypoint, line length, single-source) | `tests/unit/test_packaging.py` (117) | ADAPT | 0 | S | PKG-030..033 | +| Q11 | Spec-ID traceability harness | `tests/unit/test_spec_traceability.py` (343) | REIMPL | 8 | L | TST-013/024/025 | +| Q12 | Performance probes | `tests/performance/*` | ADAPT | 8 | L | OVR-010..014 | + +### 4.2 Services / engine + +| # | Capability | Rebuild source | Verdict | Phase | Size | Spec IDs | +|---|---|---|---|---|---|---| +| S1 | `SolverSession` Protocol + `Real`/`Recording` sessions | `services/solver_session.py` (154) | PORT | 1 | S | RUN-003/010/011/012 | +| S2 | Persistence refuses to save an invalid model + resolved path | `services/persistence.py` (63) | ADAPT | 1 | S | PER-021/022 | +| S3 | Result fields: `StaticResults.time`, modal `ModeParticipation` record | `services/results.py` (138) | ADAPT | 1 | S | RES-001/003/005, ANL-012 | +| S4 | Component-less element forces = absent, not zeros | `runner/run.py` | ADAPT | 1 | S | RUN-053 | +| S5 | `progress(int)` + `cancelled` + `isInterruptionRequested()` + `cancel()`; real Cancel UX | `services/qt_workers.py`, `viewmodels/analysis_vm.py`, `views/dialogs/run.py` | ADAPT | 2 | M | ANL-031..036, ARC-032/033, UX-081 | +| S6 | Modal mass participation unified with response-spectrum path | `runner/modal.py` (466), `services/results.py` | ADAPT | 2 | M | RUN-062/070..072, ANL-013 | +| S7 | Display-unit layer + `GRAVITY` + `ProjectMeta.display` (adapter over 4-system enum) | `core/units.py` (298) | ADAPT | 3 | M | UNT-003/004/010/020..024/030..032 | +| S8 | Closed-form diagram math (`N/V/M/T` shapes + extrema) | `core/diagrams.py` (436) | PORT | 4 | S | RES-010/011/012 | +| S9 | Backend-neutral `diagram_data` service + `render_matplotlib` | `services/diagram_data.py` (400) | REIMPL | 4 | M | RES-020..024/030..032, CAN-084 | +| S10 | **Report pipeline** CSV / PNG(≥300 dpi) / SVG / Typst (+optional PDF) + case-report action | `services/report/*` (1141) | PORT + ADAPT deps | 5 | M | RES-040..047/050/051/061 | +| S11 | Local axis: `LocalAxis`, element field, geomTransf dedup by `(type,vecxz,roll)`, editor + triad | `core/geometry/local_axis.py`, `runner/emit.py` | ADAPT | 6 | M | GEO-060..064, DOM-040 | +| S12 | Load combinations entity + commands + runner materialisation | `core/combinations.py` (44), `commands/combinations.py` (146) | PORT + ADAPT | 7 | M | LOD-050..058, ANL-001, RUN-050 | +| S13 | Self-weight service + regenerate command | `services/self_weight.py` (364) | REIMPL | 7 | M | UNT-040..045, LOD-011/040..043 | +| S14 | Material `rho` coverage for self-weight | `core/materials` | ADAPT | 7 | S | UNT-041 | + +### 4.3 UI / UX + +| # | Capability | Rebuild source | Verdict | Phase | Size | Spec IDs | +|---|---|---|---|---|---|---| +| U1 | Pure formatting helpers (`DOF/MASS/RESTRAINT_LABELS`, number/float parse) | `views/formatting.py` (84) | PORT | 0 | S | UX-033/062 | +| U2 | Timestamped, severity-tagged console | `views/docks/console.py` (103) | PORT | 0 | S | UX-042 | +| U3 | `ValidatedDialog` base (help line, inline errors, OK-gating, unit suffix) | `views/dialogs/base.py` (191) | PORT + staged | 8 | M | UX-060..064 | +| U4 | `ResultsVM` per-case handle cache; `CanvasVM` selection ownership | `viewmodels/results_vm.py`, `canvas_vm.py` | REIMPL | 8 | M | RES-005, CAN-042/041 | +| U5 | Overlay helpers: ghost undeformed, unit-labelled diagram extremes, zero hint, pattern-filtered loads | `views/canvas/overlays.py` (684) | REIMPL | 8 | M | CAN-052/062/082/083, LOD-063 | +| U6 | `QSettings` layout persistence + `closeEvent` save prompt | `views/main_window.py` | PORT | 0 | S | UX-013, PER-032/033, ARC-050 | +| U7 | `ThemeManager` + dark icon set | `views/resources/theme.py`, `icons.py` | ADAPT (verify icon provenance) | 8 | S | UX-070/071/072 | +| U8 | Thicken `ProjectViewModel` (move `action_handlers` logic into VM methods) | `viewmodels/project_vm.py` (713) | REIMPL | 8 | L | UX-001/002/005, ARC-005/020..022 | +| U9 | Analysis VM status + Simple/Advanced control fields | `viewmodels/analysis_vm.py` | ADAPT | 8 | M | ANL-021/050..054, UX-037 | + +### 4.4 Explicitly deferred / skipped + +| Item | Verdict | Why | +|---|---|---| +| Polygon-first sections + `services/sections_mesh.py` (opstool GPLv3) | DEFER (Phase 9, conditional) | Schema-breaking redesign; adds GPLv3/runtime deps; not report-critical | +| Frozen `Project`/entities + `ProjectStore`/`CommandFactory` rewrite | DEFER (not recommended now) | Large coordinated rewrite of 43 commands + all call sites; low user-visible value vs risk | +| Canvas backend Protocol + `plotly_backend.py` + `services/scene.py` | DEFER (Phase 9) | Requires core + views surgery; plotly pulls QtWebEngine (known teardown SIGSEGV) | +| `.otko` file suffix / greenfield `Project` schema (PER-001/006) | SKIP | Breaks `.osmodel` corpus and compatibility; do only as an explicit user-approved migration | +| Rebuild `runner/export.py` | SKIP | This repo's `export.py` is a superset (5 case types, `.py`+`.tcl`) | +| Rebuild static/modal HDF5 | SKIP | Rebuild has none; this repo's transient HDF5 is already better | +| Rebuild slim `materials`/`analysis`/`loads` unions | SKIP | Dropping `HystereticSM`, Transient/Pushover/ResponseSpectrum, Path/Imposed patterns would regress shipped features | +| MIT license / py3.12-only / single-OS CI assertions | SKIP | Intentional differences (AGPL, 3.10–3.12, 3-OS matrix) | + +## 5. Phases + +### Phase 0 — Foundations, safety net, quick wins +**Why first:** makes later phases verifiable and cheap; all items are +additive and low risk. Also unblocks the mpy gate that currently does nothing. + +- **Prep:** initialise git and a `feat/rebuild-adoption` branch (§3.6). +- **Q1** Create `src/otko/_const.py` (`__version__`, `OPENSEESPY_VERSION`), + switch `pyproject.toml` to `dynamic = ["version"]` + `[tool.hatch.version]`, + and import the pin in `services/export.py` (currently duplicated at + `export.py:52`). Spec: PKG-032/033, RUN-085. +- **Q2** Port `tests/unit/test_architecture.py`; adapt `CANVAS = views/canvas3d` + path and `services/{_emit,_run}.py` paths. Expect it to flag three existing + offenders — decide per item: `OSS_PICK_DEBUG` env var in + `views/canvas3d/model_canvas.py:29` (ARC-052), `QUndoStack` outside VMs, and + the canvas path. Either fix or add a documented, narrow allowlist. +- **Q3** Split CI: `lint` (+`ruff format --check`), **`type` (mypy scopes)**, + `test-headless` (`pytest tests/unit tests/services -m "not slow"`), + `test-gui` (`xvfb-run pytest tests/gui`), `test-integration` + (`pytest tests/integration -m slow`). Add `pytestmark = pytest.mark.slow` to + integration modules. Keep the existing 3-OS × 3.10–3.12 matrix and the long + Linux Qt apt list. +- **Q4** `tests/conftest.py`: `QT_QPA_PLATFORM=offscreen` default; move + `ops.wipe()` to teardown (this repo currently wipes before each test). +- **Q5** Move service-level tests (`runner_translation`, `persistence`, + `results`, `export`, …) into `tests/services/`; update CI and `AGENTS.md`. +- **Q6** Add `docs/QUICK_GUIDE.md` + `docs/README.md` (rewrite `.otko` → + `.osmodel`, rebuild-only APIs → this repo's), update `CONTRIBUTING.md` with + the 5-layer table + install/run/test commands; port `test_docs.py`. +- **Q7** Add `NOTICE` crediting MIT `otko-development` for ported files; + add a per-file header to ported files (`# Ported from otko-development + (MIT), (c) 2026 OTKO contributors`). +- **Q8/Q9/Q10** `pyproject.toml`: drop `scipy`/`pandas` from `[gui]`; convert + mypy to scoped `strict` overrides (core/services/viewmodels); add + `[tool.coverage.run] fail_under`; port the `test_packaging.py` subset that + applies (entrypoint, version single-source, line length). +- **U1/U2/U6** Port `views/formatting.py`; timestamp the console + (`views/docks/console.py` into the existing dock); add `save_layout`/ + `restore_layout` (`QSettings`) and a `closeEvent` unsaved-changes prompt. + +**Gate:** `ruff check src tests`, `ruff format --check src tests`, +`mypy src/otko/core src/otko/services src/otko/viewmodels`, +`pytest -m "not slow"`, arch test, and the new packaging/docs tests pass. + +### Phase 1 — Cheap, safe services wins +- **S1** Port `services/solver_session.py` (`SolverSession`, `RealSolverSession`, + `RecordingSolverSession`). Inject into `OpenSeesRunner` while keeping the + existing `ops_module=` shim; consolidate the two wipe paths + (`_emit.py:105` → `session.reset()`). Tests: + `tests/services/test_solver_session.py`. +- **S2** In `services/persistence.py::save_project`, call + `validate_references()` first and refuse to write an invalid model, + reporting the problem list (PER-022); return the resolved path (PER-021). +- **S3** Extend `StaticResults` with `time` and add a `ModeParticipation` + record to `ModalResults` (keep the existing dataclass names — consumers in + `views/docks/results_panel.py` dispatch on them). +- **S4** `RUN-053`: return an absent (not zero-filled) force entry when a + component is missing; guard the static `eleForce` fallback. + +**Gate:** new service tests + existing runner/persistence tests green; runner +integration suite unchanged. + +### Phase 2 — Progress & cancellation +- **S5** Add to `services/qt_workers.py`: `progress(int)`, `cancelled`, + an `_interruption_requested()` check between steps, and a bounded teardown + wait. Add `AnalysisRunner.cancel()` (calls `QThread.requestInterruption()`). + Wire progress callbacks through static (`RUN-054`) and modal (`RUN-063`) + runs. Replace the indeterminate bar in `views/dialogs/run_analysis.py` with + a real progress bar + working Cancel (UX-081). Add per-case status to the + case manager (ANL-050). +- **S6** Unify modal mass participation: put the rebuild's participation + math into one service and have **both** modal results and the existing + response-spectrum path (`services/spectrum.py`) consume it. Add the ANL-013 + no-mass warning. + +**Gate:** cancel leaves no partial handle; progress reaches 100; participation +sums to 1.0 against a hand-checked example. + +### Phase 3 — Units display layer + GRAVITY +- **S7** Port the display machinery from `core/units.py` **as an adapter**: + keep this repo's 4-value `UnitSystem` and map families + (`SI_M_N|SI_MM_N → METRIC`, `US_FT_KIP|US_IN_KIP → IMPERIAL`); add + `GRAVITY`, `display(value, system, quantity, pref)`, `DisplayPrefs`; add + `ProjectMeta.display` (persisted). Retarget the status-bar/menu unit picker + so it writes **display prefs**, not the stored `meta.units` (fixes UNT-031). +- Watch-outs: rebuild uses `StrEnum` (3.11+) — do **not** adopt it (this repo + targets 3.10). `export.py` currently embeds `meta.units.value`; keep that + but source labels via the new API. + +**Gate:** switching display units changes every label and leaves +`project.model_dump()` byte-identical; old 4-system `.osmodel` files still load. + +### Phase 4 — Closed-form diagrams +- **S8** Port `core/diagrams.py` near-verbatim (pure numpy, self-contained). +- **S9** Build a new `services/diagram_data.py` (REIMPL) on top of this repo's + `StaticResults` + load patterns, reusing `core/diagrams` for interior + shapes/extrema. Run it **alongside** `services/element_forces.py`, migrate + `views/canvas3d/diagram_renderer.py` and `views/dock_manager.py` behind a + flag, then retire the old extractor once the on-screen output matches. + +**Gate:** port `test_diagram_data.py` (end-values, Vz/My plane, axial, +all-six components, shared data, auto-scale); on-screen diagrams unchanged. + +### Phase 5 — Reporting pipeline ★ (the headline v1 capability) +- **S10** Port `services/report/{__init__,csv,figures,typst,pdf}.py`. Add a + `[reports]` extra (`matplotlib`, `imageio-ffmpeg`; Typst CLI optional, probed + via `shutil.which`, graceful degradation per RES-046). Expose a "case report" + action that produces the full set: CSV tables, PNG (≥300 dpi), SVG, a Typst + document (PDF optional), conditional static/modal sections. +- Dependencies: **Phase 3** (units) and **Phase 4** (diagrams) must land first; + reuse **Phase 1** result fields. + +**Gate:** the reference test — cantilever moment **50 kN·m at the fixed end** +(RES-061) — plus report generation off the GUI thread and preservation of the +original model file. Port `test_report_{csv,figures,typst,modal}.py` and the +`test_full_report.py` / `test_report_reference.py` integration tests. + +### Phase 6 — Local axis (user-editable) +- **S11** Port `core/geometry/local_axis.py`; add + `local_axis: LocalAxis` to the frame elements (`ElasticBeamColumn`, + `ForceBeamColumn`, `DispBeamColumn`, `BeamWithHinges`). Change + `services/_emit.py` geomTransf to read the explicit axis and dedup by + `(type, vecxz, roll_deg)` instead of auto-deriving. Update the renderer + triad (its current `getattr(el, "vecxz")` at `model_renderer.py:727` is dead + code) and add an editor (property dock / dialog). +- **Critical migration risk:** a blind default `vecxz=(0,0,1)` is parallel to + a vertical member's axis and would break existing vertical models. Use a + **safe default rule** (e.g. derive the default from geometry exactly as the + runner does today, then only override when the user sets it) so existing + `.osmodel` files keep loading and producing identical results. + +**Gate:** existing frame/eigen integration tests unchanged; new +`test_local_axis_parallel_rejected` + `test_geom_transf_dedup_by_combination`. + +### Phase 7 — Load combinations + self-weight +- **S12** Port `core/combinations.py` and `commands/combinations.py`; add + `Project.combinations`; extend `validate_references`; add + `StaticCase.combination_id` with the XOR invariant (combination **or** + patterns+factors, ANL-001); materialise scaled loads in the runner + (LOD-054) without regressing the existing per-pattern factor path. +- **S13** Reimplement `services/self_weight.py` against this repo's types: + `w = ρ·A·g` distributed local for frames, equivalent nodal loads for trusses + (UNT-042/043), a `ConstantTimeSeries` + `PlainLoadPattern`, regeneration that + replaces the prior pattern (LOD-041), no-density/unresolved reporting + (LOD-042), and the gravity constant recorded in the description (UNT-045). + Add a `RegenerateSelfWeightCommand` (undoable). +- **S14** `ElasticIsotropic` currently is the only material with `rho` + (`materials/__init__.py:28`); add `rho` to the other self-weight-capable + materials, or report them as missing density. +- Depends on **Phase 3** (`GRAVITY`) and **Phase 6** (correct local projection). + +**Gate:** port `test_loads_combinations.py` and `test_self_weight.py` (minus +the combination assertion until S12 lands); verify superposition matches a +hand calculation. + +### Phase 8 — UI/UX adoption (larger, optional but valuable) +- **U3** `ValidatedDialog` base + migrate dialogs incrementally (unit suffix + via the Phase 3 API). +- **U4** Extract `ResultsVM` (per-case handle cache) and `CanvasVM` (selection + ownership) as supersets of the current state holders so existing call sites + keep working. +- **U5** Reimplement overlay **pure helpers** against this repo's + `deformation`/`element_forces`: ghost undeformed (CAN-062), unit-labelled + diagram extremes (CAN-082), zero-component hint (CAN-083), and + pattern-filtered load display (CAN-052/LOD-063). Unit-test them display-free. +- **U7** Adopt `ThemeManager` + a dark icon set — **verify SVG provenance and + license first** before copying any icon assets. +- **U8/U9** Thicken `ProjectViewModel` (move `action_handlers.py` logic into VM + methods, one command family at a time) and add analysis-case status / + Simple-vs-Advanced controls. +- **Q11/Q12** Spec-ID traceability harness and performance probes (only if the + team commits to the `[AREA-NNN]` convention and a perf budget). + +**Gate:** GUI tests under `xvfb-run`; VM logic unit-tested without a display. + +### Phase 9 — Deferred, only on explicit decision +- Polygon-first sections + `sections_mesh` (opstool **GPLv3**, lazy import, + optional `[sections]` extra; ensure AGPL compatibility and attribution). +- Frozen `Project`/`ProjectStore`/`CommandFactory` migration. +- Canvas backend Protocol + plotly backend + `services/scene.py`. + +Revisit only after Phases 0–8 are stable and if the value justifies the blast +radius documented in the dossiers. + +## 6. Dependency graph + +``` +Phase 0 (foundations) + │ + ├─► Phase 1 (sessions, persistence, result fields) + │ │ + │ ├─► Phase 2 (progress/cancel, mass participation) + │ │ + │ └─► Phase 3 (units display + GRAVITY) ──┐ + │ │ + │ Phase 4 (diagrams) ────────────────┤ + │ ▼ + │ Phase 5 (REPORTING) ★ + │ + ├─► Phase 6 (local axis) ──┐ + │ ▼ + └─► Phase 7 (combinations + self-weight) + │ + ▼ + Phase 8 (UI/UX) ──► Phase 9 (deferred) +``` + +**Critical path to the v1 promise (a printable case report):** +`Phase 3 → Phase 4 → Phase 5`, with `Phase 1` as a cheap prerequisite and +`Phase 2` interleavable. + +## 7. Licensing & attribution + +- This repo is **AGPL-3.0**; the rebuild is **MIT**. MIT code may be + incorporated into an AGPL work — **one-way**. Preserve the MIT notice: + add a `NOTICE` (Phase 0) and a short provenance header to each ported file. +- **Never** copy this repo's AGPL code back into the MIT rebuild. +- `opstool` is **GPLv3** (only relevant to deferred `sections_mesh`); GPLv3 ↔ + AGPLv3 are compatible. Keep it lazy/optional and do not vendor its source. +- Verify the provenance/license of any copied **icon/SVG assets** before + adopting the dark icon set (U7). + +## 8. Risks and mitigations + +| Risk | Mitigation | +|---|---| +| Big-bang port destabilises the working app | Strict additive phases; feature flags for diagram/report migration; keep old paths until parity is proven | +| Freezing/command rewrite stalls progress | Explicitly deferred (Phase 9); combinations/self-weight do **not** require it | +| Units enum change corrupts stored models | Keep 4-value enum; add a family mapping + `DisplayPrefs`; byte-identical dump test | +| Local-axis default breaks vertical members | Derive default per-element as today; override only on explicit user set; regression tests on vertical frames | +| opstool private-API coupling / GPL | Deferred; pin version + thin adapter; lazy import; attribution | +| Report pipeline pulls GUI deps into headless | Keep `matplotlib`/Typst optional and imported lazily; headless CI must pass without `[reports]` | +| `StrEnum` / 3.11-only syntax | Target py3.10: avoid `StrEnum`, `zip(strict=)`, etc. | +| No VCS in this checkout | Initialise git + branch before any code changes | +| Tests can't run locally (deps missing) | Phase 0 fixes env expectations; run gates in CI | + +## 9. Tracking + +Adopt the rebuild's `.otko-build/tasks.json` **pattern** (schema, gates, +`{id, phase, layer, specs, title, tests, status, deps}`), seeded from this +plan. Each task names the phase, the spec IDs it implements, and its tests +(the rebuild's `tasks.json` is a working reference). Keep build state out of +versioned source (`.gitignore` it) or commit it deliberately — team choice. + +## 10. Recommended first step + +Execute **Phase 0** in one branch. It is almost entirely additive, it turns +the currently-inert mypy/CI gate into a real one, and it establishes the +attribution and docs baseline the later ports depend on. Then proceed +`1 → 2 → 3 → 4 → 5`, pulling Phase 6/7 forward only if self-weight is needed +before reporting. diff --git a/specifications/README.md b/specifications/README.md new file mode 100644 index 0000000..3c8f8d4 --- /dev/null +++ b/specifications/README.md @@ -0,0 +1,37 @@ +# OTKO — specifications (this repo) + +This folder holds **OTKO's own planning documents**. It is deliberately +separate from the greenfield rebuild spec set at +`~/Sync/otko-development/specifications` (docs `00`–`14`), which stays the +reference for *what a conformant rebuild looks like*. Requirement IDs of +the form `[AREA-NNN]` cited in this folder refer to that rebuild set. + +## Why this folder exists + +`~/Sync/otko` (this repo) is the **working, feature-rich app** and is the +primary deliverable. `~/Sync/otko-development` is a **greenfield rebuild** +that conforms closely to the rebuild specs but is less capable in several +areas (transient/pushover/response-spectrum, richer element/material +coverage, `.osmodel` corpus, HDF5). The goal of the plan below is to +**harvest the rebuild's good, spec-conformant capabilities into this app +without breaking what already works** — not to rewrite this app to the +rebuild's architecture. + +## Documents + +| # | Document | Purpose | +|---|---|---| +| 15 | [15-rebuild-adoption-plan.md](15-rebuild-adoption-plan.md) | Dependency-ordered plan for porting rebuild capabilities into this repo, with adoption matrix, phases, gates, risks and explicit skips. | + +## Ground rules (summary — see doc 15 §3) + +- **Additive, never replacement.** This repo is a superset of the rebuild + in the solver/runner/export surface; protect that. +- **Keep the working app working:** `.osmodel` format, AGPL-3.0 license, + Python 3.10–3.12 support, existing tests green. +- **Adapt, don't copy:** re-type ported code to this repo's APIs + (`StaticResults`/`ModalResults`, mutable `Project`, 4-value `UnitSystem`). +- **One phase = one branch = independently shippable**, each with its own + tests and gate. +- **License hygiene:** the rebuild is MIT, this repo is AGPL-3.0; MIT→AGPL + is compatible, but ported files need attribution (see doc 15 §7). diff --git a/src/otko/__init__.py b/src/otko/__init__.py index d9d3102..6291478 100644 --- a/src/otko/__init__.py +++ b/src/otko/__init__.py @@ -1,3 +1,5 @@ """OTKO — a desktop GUI for OpenSeesPy.""" -__version__ = "0.1.0" +from otko._const import __version__ + +__all__ = ["__version__"] diff --git a/src/otko/_const.py b/src/otko/_const.py new file mode 100644 index 0000000..51e97ce --- /dev/null +++ b/src/otko/_const.py @@ -0,0 +1,13 @@ +# Ported from otko-development (MIT), (c) 2026 OTKO contributors +"""Single source of truth for OTKO's version and the pinned solver version. + +Defines the package version and the pinned OpenseesPy version exactly once +so neither is duplicated as a literal anywhere else in the source. +""" + +from __future__ import annotations + +__version__ = "0.1.0" + +#: Pinned OpenseesPy version the application targets. +OPENSEESPY_VERSION = "3.8.0.0" diff --git a/src/otko/commands/__init__.py b/src/otko/commands/__init__.py index 4d6647e..eddf86f 100644 --- a/src/otko/commands/__init__.py +++ b/src/otko/commands/__init__.py @@ -6,6 +6,11 @@ from otko.commands.analysis import ( UpdateAnalysisCaseCommand, ) from otko.commands.base import ProjectCommand +from otko.commands.combinations import ( + AddCombinationsCommand, + DeleteCombinationsCommand, + UpdateCombinationCommand, +) from otko.commands.constraints import ( AddEqualDOFConstraintCommand, DeleteEqualDOFConstraintCommand, @@ -24,14 +29,6 @@ from otko.commands.grid import ( SetCoordSystemsCommand, SetGridSystemCommand, ) -from otko.commands.series_and_patterns import ( - AddLoadPatternCommand, - AddTimeSeriesCommand, - DeleteLoadPatternCommand, - DeleteTimeSeriesCommand, - UpdateLoadPatternCommand, - UpdateTimeSeriesCommand, -) from otko.commands.loads import ( AddElementLoadsCommand, AddNodalLoadsCommand, @@ -58,6 +55,14 @@ from otko.commands.sections import ( DeleteSectionsCommand, UpdateSectionCommand, ) +from otko.commands.series_and_patterns import ( + AddLoadPatternCommand, + AddTimeSeriesCommand, + DeleteLoadPatternCommand, + DeleteTimeSeriesCommand, + UpdateLoadPatternCommand, + UpdateTimeSeriesCommand, +) from otko.commands.transforms import ( MirrorCommand, MoveNodesCommand, @@ -67,25 +72,50 @@ from otko.commands.transforms import ( __all__ = [ "ProjectCommand", - "AddNodesCommand", "DeleteNodesCommand", "SetRestraintCommand", "SetMassCommand", + "AddNodesCommand", + "DeleteNodesCommand", + "SetRestraintCommand", + "SetMassCommand", "UpdateNodeFieldsCommand", - "AddElementsCommand", "DeleteElementsCommand", - "AssignSectionCommand", "AssignMaterialCommand", - "ReplaceElementsCommand", "ConvertElementTypeCommand", + "AddElementsCommand", + "DeleteElementsCommand", + "AssignSectionCommand", + "AssignMaterialCommand", + "ReplaceElementsCommand", + "ConvertElementTypeCommand", "UpdateElementFieldsCommand", - "AddMaterialsCommand", "DeleteMaterialsCommand", "UpdateMaterialCommand", - "AddSectionsCommand", "DeleteSectionsCommand", "UpdateSectionCommand", - "AddNodalLoadsCommand", "AddElementLoadsCommand", - "UpdateNodalLoadCommand", "DeleteNodalLoadsCommand", - "UpdateElementLoadCommand", "DeleteElementLoadsCommand", - "MoveNodesCommand", "ReplicateCommand", "MirrorCommand", "Plane", - "AddAnalysisCasesCommand", "DeleteAnalysisCasesCommand", "UpdateAnalysisCaseCommand", + "AddMaterialsCommand", + "DeleteMaterialsCommand", + "UpdateMaterialCommand", + "AddSectionsCommand", + "DeleteSectionsCommand", + "UpdateSectionCommand", + "AddNodalLoadsCommand", + "AddElementLoadsCommand", + "UpdateNodalLoadCommand", + "DeleteNodalLoadsCommand", + "UpdateElementLoadCommand", + "DeleteElementLoadsCommand", + "MoveNodesCommand", + "ReplicateCommand", + "MirrorCommand", + "Plane", + "AddAnalysisCasesCommand", + "DeleteAnalysisCasesCommand", + "UpdateAnalysisCaseCommand", + "AddCombinationsCommand", + "DeleteCombinationsCommand", + "UpdateCombinationCommand", "SetGridSystemCommand", "SetCoordSystemsCommand", - "AddTimeSeriesCommand", "AddLoadPatternCommand", - "UpdateTimeSeriesCommand", "DeleteTimeSeriesCommand", - "UpdateLoadPatternCommand", "DeleteLoadPatternCommand", + "AddTimeSeriesCommand", + "AddLoadPatternCommand", + "UpdateTimeSeriesCommand", + "DeleteTimeSeriesCommand", + "UpdateLoadPatternCommand", + "DeleteLoadPatternCommand", "AddEqualDOFConstraintCommand", - "UpdateEqualDOFConstraintCommand", "DeleteEqualDOFConstraintCommand", + "UpdateEqualDOFConstraintCommand", + "DeleteEqualDOFConstraintCommand", "SetUnitsCommand", ] diff --git a/src/otko/commands/analysis.py b/src/otko/commands/analysis.py index c26dc22..444f34c 100644 --- a/src/otko/commands/analysis.py +++ b/src/otko/commands/analysis.py @@ -13,8 +13,7 @@ if TYPE_CHECKING: class AddAnalysisCasesCommand(ProjectCommand): """Add one or more analysis cases.""" - def __init__(self, vm: "ProjectViewModel", cases: list[Any], *, - text: str | None = None) -> None: + def __init__(self, vm: ProjectViewModel, cases: list[Any], *, text: str | None = None) -> None: super().__init__(vm, text or f"Add {len(cases)} analysis case(s)") self._cases = list(cases) @@ -35,19 +34,16 @@ class AddAnalysisCasesCommand(ProjectCommand): class DeleteAnalysisCasesCommand(ProjectCommand): """Remove a set of analysis cases.""" - def __init__(self, vm: "ProjectViewModel", case_ids: set[int]) -> None: + def __init__(self, vm: ProjectViewModel, case_ids: set[int]) -> None: super().__init__(vm, f"Delete {len(case_ids)} analysis case(s)") self._case_ids = set(case_ids) self._removed: list[tuple[int, Any]] = [] def redo(self) -> None: self._removed = [ - (i, c) for i, c in enumerate(self.project.analyses) - if c.id in self._case_ids - ] - self.project.analyses[:] = [ - c for c in self.project.analyses if c.id not in self._case_ids + (i, c) for i, c in enumerate(self.project.analyses) if c.id in self._case_ids ] + self.project.analyses[:] = [c for c in self.project.analyses if c.id not in self._case_ids] self._notify() def undo(self) -> None: @@ -60,7 +56,7 @@ class DeleteAnalysisCasesCommand(ProjectCommand): class UpdateAnalysisCaseCommand(ProjectCommand): """Replace an analysis case at a given id.""" - def __init__(self, vm: "ProjectViewModel", new_case: Any) -> None: + def __init__(self, vm: ProjectViewModel, new_case: Any) -> None: super().__init__(vm, f"Edit analysis case {new_case.id}") self._new = new_case self._old: Any | None = None diff --git a/src/otko/commands/base.py b/src/otko/commands/base.py index c740057..0e8b3dd 100644 --- a/src/otko/commands/base.py +++ b/src/otko/commands/base.py @@ -27,20 +27,18 @@ class ProjectCommand(QUndoCommand): :meth:`_notify` exactly once at the end of each. """ - def __init__(self, vm: "ProjectViewModel", text: str) -> None: + def __init__(self, vm: ProjectViewModel, text: str) -> None: super().__init__(text) self._vm = vm @property - def vm(self) -> "ProjectViewModel": + def vm(self) -> ProjectViewModel: return self._vm @property - def project(self) -> "Project": + def project(self) -> Project: if self._vm.project is None: - raise RuntimeError( - f"Cannot apply '{self.text()}': no active project." - ) + raise RuntimeError(f"Cannot apply '{self.text()}': no active project.") return self._vm.project def _notify(self) -> None: diff --git a/src/otko/commands/combinations.py b/src/otko/commands/combinations.py new file mode 100644 index 0000000..23f1aa8 --- /dev/null +++ b/src/otko/commands/combinations.py @@ -0,0 +1,80 @@ +"""Commands for load-combination management.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from otko.commands.base import ProjectCommand + +if TYPE_CHECKING: + from otko.viewmodels import ProjectViewModel + + +class AddCombinationsCommand(ProjectCommand): + """Add one or more load combinations.""" + + def __init__(self, vm: ProjectViewModel, combos: list[Any], *, text: str | None = None) -> None: + super().__init__(vm, text or f"Add {len(combos)} load combination(s)") + self._combos = list(combos) + + def redo(self) -> None: + existing = {c.id for c in self.project.combinations} + for combo in self._combos: + if combo.id in existing: + raise ValueError(f"Load combination id {combo.id} already exists.") + self.project.combinations.extend(self._combos) + self._notify() + + def undo(self) -> None: + ids = {c.id for c in self._combos} + self.project.combinations[:] = [c for c in self.project.combinations if c.id not in ids] + self._notify() + + +class DeleteCombinationsCommand(ProjectCommand): + """Remove a set of load combinations.""" + + def __init__(self, vm: ProjectViewModel, combo_ids: set[int]) -> None: + super().__init__(vm, f"Delete {len(combo_ids)} load combination(s)") + self._combo_ids = set(combo_ids) + self._removed: list[tuple[int, Any]] = [] + + def redo(self) -> None: + self._removed = [ + (i, c) for i, c in enumerate(self.project.combinations) if c.id in self._combo_ids + ] + self.project.combinations[:] = [ + c for c in self.project.combinations if c.id not in self._combo_ids + ] + self._notify() + + def undo(self) -> None: + for i, c in self._removed: + self.project.combinations.insert(min(i, len(self.project.combinations)), c) + self._removed.clear() + self._notify() + + +class UpdateCombinationCommand(ProjectCommand): + """Replace a load combination at a given id.""" + + def __init__(self, vm: ProjectViewModel, new_combo: Any) -> None: + super().__init__(vm, f"Edit load combination {new_combo.id}") + self._new = new_combo + self._old: Any | None = None + self._index: int | None = None + + def redo(self) -> None: + for i, c in enumerate(self.project.combinations): + if c.id == self._new.id: + self._old = c + self._index = i + self.project.combinations[i] = self._new + self._notify() + return + raise KeyError(f"Load combination with id={self._new.id} not found.") + + def undo(self) -> None: + if self._old is not None and self._index is not None: + self.project.combinations[self._index] = self._old + self._notify() diff --git a/src/otko/commands/constraints.py b/src/otko/commands/constraints.py index 2fb051b..3f94205 100644 --- a/src/otko/commands/constraints.py +++ b/src/otko/commands/constraints.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: class AddEqualDOFConstraintCommand(ProjectCommand): """Append an ``equalDOF`` constraint to the project (undoable).""" - def __init__(self, vm: "ProjectViewModel", constraint: EqualDOFConstraint) -> None: + def __init__(self, vm: ProjectViewModel, constraint: EqualDOFConstraint) -> None: super().__init__( vm, f"Add equalDOF {constraint.retained_node}->{constraint.constrained_node}", @@ -34,8 +34,7 @@ class AddEqualDOFConstraintCommand(ProjectCommand): def undo(self) -> None: self.project.mp_constraints[:] = [ - mp for mp in self.project.mp_constraints - if mp != self._constraint + mp for mp in self.project.mp_constraints if mp != self._constraint ] self._notify() @@ -49,7 +48,7 @@ class UpdateEqualDOFConstraintCommand(ProjectCommand): """ def __init__( - self, vm: "ProjectViewModel", index: int, new_constraint: EqualDOFConstraint + self, vm: ProjectViewModel, index: int, new_constraint: EqualDOFConstraint ) -> None: super().__init__(vm, f"Edit equalDOF row {index + 1}") self._index = index @@ -72,7 +71,7 @@ class UpdateEqualDOFConstraintCommand(ProjectCommand): class DeleteEqualDOFConstraintCommand(ProjectCommand): """Remove ``equalDOF`` constraints by table-row index (no cascade).""" - def __init__(self, vm: "ProjectViewModel", indices: set[int]) -> None: + def __init__(self, vm: ProjectViewModel, indices: set[int]) -> None: super().__init__(vm, f"Delete {len(indices)} constraint(s)") self._indices = set(indices) self._removed: list[tuple[int, EqualDOFConstraint]] = [] diff --git a/src/otko/commands/elements.py b/src/otko/commands/elements.py index 6487a9d..f1967ba 100644 --- a/src/otko/commands/elements.py +++ b/src/otko/commands/elements.py @@ -14,7 +14,7 @@ class AddElementsCommand(ProjectCommand): """Add one or more elements in a single undoable step.""" def __init__( - self, vm: "ProjectViewModel", elements: list[Any], *, text: str | None = None + self, vm: ProjectViewModel, elements: list[Any], *, text: str | None = None ) -> None: super().__init__(vm, text or f"Add {len(elements)} element(s)") self._elements = list(elements) @@ -40,7 +40,7 @@ class AddElementsCommand(ProjectCommand): class DeleteElementsCommand(ProjectCommand): """Remove a set of elements (no cascade — nodes are not affected).""" - def __init__(self, vm: "ProjectViewModel", element_ids: set[int]) -> None: + def __init__(self, vm: ProjectViewModel, element_ids: set[int]) -> None: super().__init__(vm, f"Delete {len(element_ids)} element(s)") self._element_ids = set(element_ids) self._removed: list[tuple[int, Any]] = [] @@ -68,7 +68,7 @@ class AssignSectionCommand(ProjectCommand): (e.g. trusses, zero-length elements). """ - def __init__(self, vm: "ProjectViewModel", element_ids: set[int], section_id: int) -> None: + def __init__(self, vm: ProjectViewModel, element_ids: set[int], section_id: int) -> None: super().__init__(vm, f"Assign section {section_id} to {len(element_ids)} element(s)") self._element_ids = set(element_ids) self._section_id = section_id @@ -106,7 +106,7 @@ class ConvertElementTypeCommand(ProjectCommand): def __init__( self, - vm: "ProjectViewModel", + vm: ProjectViewModel, element_ids: set[int], target_type: str, defaults: dict[str, Any] | None = None, @@ -185,7 +185,7 @@ class ReplaceElementsCommand(ProjectCommand): for another while preserving the id and node connectivity. """ - def __init__(self, vm: "ProjectViewModel", replacements: list[Any]) -> None: + def __init__(self, vm: ProjectViewModel, replacements: list[Any]) -> None: super().__init__(vm, f"Replace {len(replacements)} element(s)") self._replacements = {el.id: el for el in replacements} self._previous: dict[int, Any] = {} @@ -217,7 +217,7 @@ class UpdateElementFieldsCommand(ProjectCommand): def __init__( self, - vm: "ProjectViewModel", + vm: ProjectViewModel, element_id: int, fields: dict[str, Any], ) -> None: @@ -254,7 +254,7 @@ class UpdateElementFieldsCommand(ProjectCommand): class AssignMaterialCommand(ProjectCommand): """Set ``material_id`` on a set of elements (truss-style).""" - def __init__(self, vm: "ProjectViewModel", element_ids: set[int], material_id: int) -> None: + def __init__(self, vm: ProjectViewModel, element_ids: set[int], material_id: int) -> None: super().__init__(vm, f"Assign material {material_id} to {len(element_ids)} element(s)") self._element_ids = set(element_ids) self._material_id = material_id diff --git a/src/otko/commands/grid.py b/src/otko/commands/grid.py index 894c56b..e271746 100644 --- a/src/otko/commands/grid.py +++ b/src/otko/commands/grid.py @@ -20,7 +20,7 @@ class SetCoordSystemsCommand(ProjectCommand): def __init__( self, - vm: "ProjectViewModel", + vm: ProjectViewModel, new_systems: list[CoordinateGridSystem], ) -> None: super().__init__(vm, "Update coordinate/grid systems") @@ -46,7 +46,7 @@ class SetGridSystemCommand(ProjectCommand): systems at once. """ - def __init__(self, vm: "ProjectViewModel", new_grid: GridSystem) -> None: + def __init__(self, vm: ProjectViewModel, new_grid: GridSystem) -> None: super().__init__(vm, "Update grid system") self._new_grid = new_grid self._previous: GridSystem | None = None diff --git a/src/otko/commands/loads.py b/src/otko/commands/loads.py index 7822caf..789b6c8 100644 --- a/src/otko/commands/loads.py +++ b/src/otko/commands/loads.py @@ -35,7 +35,7 @@ class AddNodalLoadsCommand(ProjectCommand): def __init__( self, - vm: "ProjectViewModel", + vm: ProjectViewModel, node_ids: set[int], forces: tuple[float, float, float, float, float, float], pattern_id: int | None = None, @@ -47,7 +47,7 @@ class AddNodalLoadsCommand(ProjectCommand): self._forces = forces self._pattern_id = pattern_id self._new_pattern_name = new_pattern_name - self._new_ts_type = new_ts_type # "Linear" or "Constant" + self._new_ts_type = new_ts_type # "Linear" or "Constant" self._created_ts: TimeSeries | None = None self._created_pattern: PlainLoadPattern | None = None self._added_loads: list[tuple[int, NodalLoad]] = [] # (pattern_id, load) @@ -101,7 +101,10 @@ class AddNodalLoadsCommand(ProjectCommand): break self._added_loads.clear() # Roll back any infrastructure we created. - if self._created_pattern is not None and self._created_pattern in self.project.load_patterns: + if ( + self._created_pattern is not None + and self._created_pattern in self.project.load_patterns + ): self.project.load_patterns.remove(self._created_pattern) self._created_pattern = None if self._created_ts is not None and self._created_ts in self.project.time_series: @@ -119,7 +122,7 @@ class AddElementLoadsCommand(ProjectCommand): def __init__( self, - vm: "ProjectViewModel", + vm: ProjectViewModel, element_ids: set[int], wy: float = 0.0, wz: float = 0.0, @@ -157,7 +160,10 @@ class AddElementLoadsCommand(ProjectCommand): pattern = self._resolve_pattern() for eid in self._element_ids: load = UniformElementLoad( - element_id=eid, wy=self._wy, wz=self._wz, wx=self._wx, + element_id=eid, + wy=self._wy, + wz=self._wz, + wx=self._wx, ) pattern.element_loads.append(load) self._added_loads.append((pattern.id, load)) @@ -171,7 +177,10 @@ class AddElementLoadsCommand(ProjectCommand): pat.element_loads.remove(load) break self._added_loads.clear() - if self._created_pattern is not None and self._created_pattern in self.project.load_patterns: + if ( + self._created_pattern is not None + and self._created_pattern in self.project.load_patterns + ): self.project.load_patterns.remove(self._created_pattern) self._created_pattern = None if self._created_ts is not None and self._created_ts in self.project.time_series: diff --git a/src/otko/commands/materials.py b/src/otko/commands/materials.py index bce4088..9729ced 100644 --- a/src/otko/commands/materials.py +++ b/src/otko/commands/materials.py @@ -13,7 +13,9 @@ if TYPE_CHECKING: class AddMaterialsCommand(ProjectCommand): """Add one or more materials in a single undoable step.""" - def __init__(self, vm: "ProjectViewModel", materials: list[Any], *, text: str | None = None) -> None: + def __init__( + self, vm: ProjectViewModel, materials: list[Any], *, text: str | None = None + ) -> None: super().__init__(vm, text or f"Add {len(materials)} material(s)") self._materials = list(materials) @@ -34,15 +36,14 @@ class AddMaterialsCommand(ProjectCommand): class DeleteMaterialsCommand(ProjectCommand): """Remove materials (no cascade).""" - def __init__(self, vm: "ProjectViewModel", material_ids: set[int]) -> None: + def __init__(self, vm: ProjectViewModel, material_ids: set[int]) -> None: super().__init__(vm, f"Delete {len(material_ids)} material(s)") self._material_ids = set(material_ids) self._removed: list[tuple[int, Any]] = [] def redo(self) -> None: self._removed = [ - (i, m) for i, m in enumerate(self.project.materials) - if m.id in self._material_ids + (i, m) for i, m in enumerate(self.project.materials) if m.id in self._material_ids ] self.project.materials[:] = [ m for m in self.project.materials if m.id not in self._material_ids @@ -63,7 +64,7 @@ class UpdateMaterialCommand(ProjectCommand): otherwise this is an Add+Delete, not an update. """ - def __init__(self, vm: "ProjectViewModel", new_material: Any) -> None: + def __init__(self, vm: ProjectViewModel, new_material: Any) -> None: super().__init__(vm, f"Edit material {new_material.id}") self._new = new_material self._old: Any | None = None diff --git a/src/otko/commands/nodes.py b/src/otko/commands/nodes.py index b285159..e24840c 100644 --- a/src/otko/commands/nodes.py +++ b/src/otko/commands/nodes.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: class AddNodesCommand(ProjectCommand): """Add one or more nodes in a single undoable step.""" - def __init__(self, vm: "ProjectViewModel", nodes: list[Node], *, text: str | None = None) -> None: + def __init__(self, vm: ProjectViewModel, nodes: list[Node], *, text: str | None = None) -> None: super().__init__(vm, text or f"Add {len(nodes)} node(s)") self._nodes = list(nodes) @@ -39,17 +39,18 @@ class DeleteNodesCommand(ProjectCommand): them in their original positions. """ - def __init__(self, vm: "ProjectViewModel", node_ids: set[int]) -> None: + def __init__(self, vm: ProjectViewModel, node_ids: set[int]) -> None: super().__init__(vm, f"Delete {len(node_ids)} node(s)") self._node_ids = set(node_ids) - self._removed_nodes: list[tuple[int, Node]] = [] # (index, node) - self._removed_elements: list[tuple[int, object]] = [] # (index, element) + self._removed_nodes: list[tuple[int, Node]] = [] # (index, node) + self._removed_elements: list[tuple[int, object]] = [] # (index, element) self._removed_mp_constraints: list[tuple[int, object]] = [] def redo(self) -> None: # Cascade: snapshot every element that references a doomed node. self._removed_elements = [ - (i, el) for i, el in enumerate(self.project.elements) + (i, el) + for i, el in enumerate(self.project.elements) if any(nid in self._node_ids for nid in el.nodes) ] doomed_elem_ids = {el.id for _, el in self._removed_elements} @@ -58,7 +59,8 @@ class DeleteNodesCommand(ProjectCommand): ] self._removed_mp_constraints = [ - (i, mp) for i, mp in enumerate(self.project.mp_constraints) + (i, mp) + for i, mp in enumerate(self.project.mp_constraints) if mp.retained_node in self._node_ids or mp.constrained_node in self._node_ids ] doomed_mp = {id(mp) for _, mp in self._removed_mp_constraints} @@ -69,9 +71,7 @@ class DeleteNodesCommand(ProjectCommand): self._removed_nodes = [ (i, n) for i, n in enumerate(self.project.nodes) if n.id in self._node_ids ] - self.project.nodes[:] = [ - n for n in self.project.nodes if n.id not in self._node_ids - ] + self.project.nodes[:] = [n for n in self.project.nodes if n.id not in self._node_ids] self._notify() def undo(self) -> None: @@ -93,7 +93,7 @@ class SetRestraintCommand(ProjectCommand): def __init__( self, - vm: "ProjectViewModel", + vm: ProjectViewModel, node_ids: set[int], restraint: tuple[bool, bool, bool, bool, bool, bool], ) -> None: @@ -128,7 +128,7 @@ class UpdateNodeFieldsCommand(ProjectCommand): def __init__( self, - vm: "ProjectViewModel", + vm: ProjectViewModel, node_id: int, fields: dict[str, Any], ) -> None: @@ -166,7 +166,7 @@ class SetMassCommand(ProjectCommand): def __init__( self, - vm: "ProjectViewModel", + vm: ProjectViewModel, node_ids: set[int], mass: tuple[float, float, float, float, float, float], ) -> None: diff --git a/src/otko/commands/project.py b/src/otko/commands/project.py index 362e2a6..3fd0065 100644 --- a/src/otko/commands/project.py +++ b/src/otko/commands/project.py @@ -19,7 +19,7 @@ class SetUnitsCommand(ProjectCommand): so both redo and undo notify (dirty + modelMutated) to refresh. """ - def __init__(self, vm: "ProjectViewModel", units: UnitSystem) -> None: + def __init__(self, vm: ProjectViewModel, units: UnitSystem) -> None: super().__init__(vm, f"Set display units to {units.value}") self._units = units self._previous: UnitSystem | None = None diff --git a/src/otko/commands/sections.py b/src/otko/commands/sections.py index ecd6054..94142a6 100644 --- a/src/otko/commands/sections.py +++ b/src/otko/commands/sections.py @@ -13,7 +13,9 @@ if TYPE_CHECKING: class AddSectionsCommand(ProjectCommand): """Add one or more sections in a single undoable step.""" - def __init__(self, vm: "ProjectViewModel", sections: list[Any], *, text: str | None = None) -> None: + def __init__( + self, vm: ProjectViewModel, sections: list[Any], *, text: str | None = None + ) -> None: super().__init__(vm, text or f"Add {len(sections)} section(s)") self._sections = list(sections) @@ -34,15 +36,14 @@ class AddSectionsCommand(ProjectCommand): class DeleteSectionsCommand(ProjectCommand): """Remove sections (no cascade — elements referencing them must be cleaned separately).""" - def __init__(self, vm: "ProjectViewModel", section_ids: set[int]) -> None: + def __init__(self, vm: ProjectViewModel, section_ids: set[int]) -> None: super().__init__(vm, f"Delete {len(section_ids)} section(s)") self._section_ids = set(section_ids) self._removed: list[tuple[int, Any]] = [] def redo(self) -> None: self._removed = [ - (i, s) for i, s in enumerate(self.project.sections) - if s.id in self._section_ids + (i, s) for i, s in enumerate(self.project.sections) if s.id in self._section_ids ] self.project.sections[:] = [ s for s in self.project.sections if s.id not in self._section_ids @@ -59,7 +60,7 @@ class DeleteSectionsCommand(ProjectCommand): class UpdateSectionCommand(ProjectCommand): """Replace a section's parameters at a given id.""" - def __init__(self, vm: "ProjectViewModel", new_section: Any) -> None: + def __init__(self, vm: ProjectViewModel, new_section: Any) -> None: super().__init__(vm, f"Edit section {new_section.id}") self._new = new_section self._old: Any | None = None diff --git a/src/otko/commands/series_and_patterns.py b/src/otko/commands/series_and_patterns.py index 8e11b6b..1d03ab5 100644 --- a/src/otko/commands/series_and_patterns.py +++ b/src/otko/commands/series_and_patterns.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: class AddTimeSeriesCommand(ProjectCommand): """Append a :class:`TimeSeries` to the project (undoable).""" - def __init__(self, vm: "ProjectViewModel", ts: "TimeSeries") -> None: + def __init__(self, vm: ProjectViewModel, ts: TimeSeries) -> None: super().__init__(vm, f"Add time series '{ts.name or ts.id}'") self._ts = ts @@ -31,9 +31,7 @@ class AddTimeSeriesCommand(ProjectCommand): self._notify() def undo(self) -> None: - self.project.time_series[:] = [ - t for t in self.project.time_series if t.id != self._ts.id - ] + self.project.time_series[:] = [t for t in self.project.time_series if t.id != self._ts.id] self._notify() @@ -43,7 +41,7 @@ class UpdateTimeSeriesCommand(ProjectCommand): The new series must carry the same id as the one being replaced. """ - def __init__(self, vm: "ProjectViewModel", new_ts: Any) -> None: + def __init__(self, vm: ProjectViewModel, new_ts: Any) -> None: super().__init__(vm, f"Edit time series {new_ts.id}") self._new = new_ts self._old: Any | None = None @@ -69,7 +67,7 @@ class DeleteTimeSeriesCommand(ProjectCommand): """Remove time series (no cascade — patterns referencing them must be cleaned separately; ``validate_references`` catches strays before save).""" - def __init__(self, vm: "ProjectViewModel", ts_ids: set[int]) -> None: + def __init__(self, vm: ProjectViewModel, ts_ids: set[int]) -> None: super().__init__(vm, f"Delete {len(ts_ids)} time serie(s)") self._ts_ids = set(ts_ids) self._removed: list[tuple[int, Any]] = [] @@ -93,7 +91,7 @@ class DeleteTimeSeriesCommand(ProjectCommand): class AddLoadPatternCommand(ProjectCommand): """Append a :class:`LoadPattern` to the project (undoable).""" - def __init__(self, vm: "ProjectViewModel", pattern: "LoadPattern") -> None: + def __init__(self, vm: ProjectViewModel, pattern: LoadPattern) -> None: super().__init__(vm, f"Add pattern '{pattern.name or pattern.id}'") self._pattern = pattern @@ -119,7 +117,7 @@ class UpdateLoadPatternCommand(ProjectCommand): drops the pattern's stored loads. """ - def __init__(self, vm: "ProjectViewModel", new_pattern: Any) -> None: + def __init__(self, vm: ProjectViewModel, new_pattern: Any) -> None: super().__init__(vm, f"Edit pattern {new_pattern.id}") self._new = new_pattern self._old: Any | None = None @@ -144,7 +142,7 @@ class UpdateLoadPatternCommand(ProjectCommand): class DeleteLoadPatternCommand(ProjectCommand): """Remove load patterns (their child nodal/element loads go with them).""" - def __init__(self, vm: "ProjectViewModel", pattern_ids: set[int]) -> None: + def __init__(self, vm: ProjectViewModel, pattern_ids: set[int]) -> None: super().__init__(vm, f"Delete {len(pattern_ids)} pattern(s)") self._pattern_ids = set(pattern_ids) self._removed: list[tuple[int, Any]] = [] diff --git a/src/otko/commands/transforms.py b/src/otko/commands/transforms.py index b8d60cc..07c335d 100644 --- a/src/otko/commands/transforms.py +++ b/src/otko/commands/transforms.py @@ -15,10 +15,9 @@ All copies inherit the source node's restraint, mass, and name. from __future__ import annotations -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING, Literal from otko.commands.base import ProjectCommand -from otko.core import Node if TYPE_CHECKING: from otko.viewmodels import ProjectViewModel @@ -30,7 +29,7 @@ class MoveNodesCommand(ProjectCommand): def __init__( self, - vm: "ProjectViewModel", + vm: ProjectViewModel, node_ids: set[int], offset: tuple[float, float, float], ) -> None: @@ -45,9 +44,9 @@ class MoveNodesCommand(ProjectCommand): for i, n in enumerate(self.project.nodes): if n.id in self._node_ids: self._previous[n.id] = n.coords - self.project.nodes[i] = n.model_copy(update={ - "coords": (n.coords[0] + dx, n.coords[1] + dy, n.coords[2] + dz) - }) + self.project.nodes[i] = n.model_copy( + update={"coords": (n.coords[0] + dx, n.coords[1] + dy, n.coords[2] + dz)} + ) self._notify() def undo(self) -> None: @@ -63,7 +62,7 @@ class ReplicateCommand(ProjectCommand): def __init__( self, - vm: "ProjectViewModel", + vm: ProjectViewModel, node_ids: set[int], element_ids: set[int], offset: tuple[float, float, float], @@ -84,7 +83,8 @@ class ReplicateCommand(ProjectCommand): # Snapshot the source nodes/elements once (won't change during redo). src_nodes = [n for n in self.project.nodes if n.id in self._node_ids] src_elements = [ - e for e in self.project.elements + e + for e in self.project.elements if e.id in self._element_ids and all(nid in self._node_ids for nid in e.nodes) ] next_node_id = self.project.next_node_id() @@ -95,30 +95,34 @@ class ReplicateCommand(ProjectCommand): for k in range(1, self._n_copies + 1): mapping: dict[int, int] = {} for orig in src_nodes: - new_node = orig.model_copy(update={ - "id": next_node_id, - "coords": (orig.coords[0] + k * dx, - orig.coords[1] + k * dy, - orig.coords[2] + k * dz), - }) + new_node = orig.model_copy( + update={ + "id": next_node_id, + "coords": ( + orig.coords[0] + k * dx, + orig.coords[1] + k * dy, + orig.coords[2] + k * dz, + ), + } + ) self.project.nodes.append(new_node) self._added_node_ids.add(next_node_id) mapping[orig.id] = next_node_id next_node_id += 1 for orig in src_elements: - new_elem = orig.model_copy(update={ - "id": next_elem_id, - "nodes": tuple(mapping[nid] for nid in orig.nodes), - }) + new_elem = orig.model_copy( + update={ + "id": next_elem_id, + "nodes": tuple(mapping[nid] for nid in orig.nodes), + } + ) self.project.elements.append(new_elem) self._added_element_ids.add(next_elem_id) next_elem_id += 1 self._notify() def undo(self) -> None: - self.project.nodes[:] = [ - n for n in self.project.nodes if n.id not in self._added_node_ids - ] + self.project.nodes[:] = [n for n in self.project.nodes if n.id not in self._added_node_ids] self.project.elements[:] = [ e for e in self.project.elements if e.id not in self._added_element_ids ] @@ -142,7 +146,7 @@ class MirrorCommand(ProjectCommand): def __init__( self, - vm: "ProjectViewModel", + vm: ProjectViewModel, node_ids: set[int], element_ids: set[int], plane: Plane, @@ -166,7 +170,8 @@ class MirrorCommand(ProjectCommand): def redo(self) -> None: src_nodes = [n for n in self.project.nodes if n.id in self._node_ids] src_elements = [ - e for e in self.project.elements + e + for e in self.project.elements if e.id in self._element_ids and all(nid in self._node_ids for nid in e.nodes) ] next_node_id = self.project.next_node_id() @@ -176,28 +181,30 @@ class MirrorCommand(ProjectCommand): mapping: dict[int, int] = {} for orig in src_nodes: - new_node = orig.model_copy(update={ - "id": next_node_id, - "coords": self._reflect(orig.coords, self._plane), - }) + new_node = orig.model_copy( + update={ + "id": next_node_id, + "coords": self._reflect(orig.coords, self._plane), + } + ) self.project.nodes.append(new_node) self._added_node_ids.add(next_node_id) mapping[orig.id] = next_node_id next_node_id += 1 for orig in src_elements: - new_elem = orig.model_copy(update={ - "id": next_elem_id, - "nodes": tuple(mapping[nid] for nid in orig.nodes), - }) + new_elem = orig.model_copy( + update={ + "id": next_elem_id, + "nodes": tuple(mapping[nid] for nid in orig.nodes), + } + ) self.project.elements.append(new_elem) self._added_element_ids.add(next_elem_id) next_elem_id += 1 self._notify() def undo(self) -> None: - self.project.nodes[:] = [ - n for n in self.project.nodes if n.id not in self._added_node_ids - ] + self.project.nodes[:] = [n for n in self.project.nodes if n.id not in self._added_node_ids] self.project.elements[:] = [ e for e in self.project.elements if e.id not in self._added_element_ids ] diff --git a/src/otko/core/__init__.py b/src/otko/core/__init__.py index 9443696..34e7482 100644 --- a/src/otko/core/__init__.py +++ b/src/otko/core/__init__.py @@ -50,6 +50,8 @@ from otko.core.loads import ( ConstantTimeSeries, ImposedSupportMotionPattern, LinearTimeSeries, + LoadCombination, + LoadCombinationItem, LoadPattern, NodalLoad, PathTimeSeries, @@ -165,6 +167,8 @@ __all__ = [ "PlainLoadPattern", "UniformExcitationPattern", "ImposedSupportMotionPattern", + "LoadCombination", + "LoadCombinationItem", "NodalLoad", "UniformElementLoad", "ResponseSpectrum", diff --git a/src/otko/core/_base.py b/src/otko/core/_base.py index 72e9d2f..da3e9b5 100644 --- a/src/otko/core/_base.py +++ b/src/otko/core/_base.py @@ -16,11 +16,13 @@ class Entity(BaseModel): """Base class for every persisted domain object.""" model_config = ConfigDict( - frozen=False, # individual setters allowed; we lock at the Project boundary - extra="forbid", # unknown JSON keys are an error, not a silent ignore + frozen=False, # individual setters allowed; we lock at the Project boundary + extra="forbid", # unknown JSON keys are an error, not a silent ignore validate_assignment=True, populate_by_name=True, ) - id: PositiveInt = Field(..., description="Unique tag within its kind. Used as the OpenSees tag.") + id: PositiveInt = Field( + ..., description="Unique tag within its kind. Used as the OpenSees tag." + ) name: str = Field(default="", description="Optional human-readable label.") diff --git a/src/otko/core/analysis/__init__.py b/src/otko/core/analysis/__init__.py index eba5330..e8835b7 100644 --- a/src/otko/core/analysis/__init__.py +++ b/src/otko/core/analysis/__init__.py @@ -9,7 +9,7 @@ in Phase 6/8. from __future__ import annotations -from typing import Annotated, Literal, Union +from typing import Annotated, Literal from pydantic import Field, PositiveFloat, PositiveInt, model_validator @@ -83,7 +83,8 @@ class TransientCase(Entity): constraints: str = "Plain" integrator: str = "Newmark" integrator_params: tuple[float, float] = Field( - default=(0.5, 0.25), description="Newmark gamma, beta (default = average acceleration).", + default=(0.5, 0.25), + description="Newmark gamma, beta (default = average acceleration).", ) algorithm: str = "Newton" test: str = "NormDispIncr" @@ -100,7 +101,7 @@ class TransientCase(Entity): rayleigh_beta_k: float = Field( default=0.0, description="Stiffness-proportional βK on CURRENT/tangent stiffness — " - "``rayleigh`` slot 2 (damps high frequencies).", + "``rayleigh`` slot 2 (damps high frequencies).", ) rayleigh_beta_k_init: float = Field( default=0.0, @@ -114,11 +115,11 @@ class TransientCase(Entity): ) rayleigh_beta_k_comm: float = Field( default=0.0, - description="Stiffness-proportional βK on COMMITTED stiffness — " - "``rayleigh`` slot 4.", + description="Stiffness-proportional βK on COMMITTED stiffness — " "``rayleigh`` slot 4.", ) rayleigh_mode1_damping: float | None = Field( - default=None, ge=0.0, + default=None, + ge=0.0, description=( "If set, βK is computed as 2·ζ/√λ₁ (first-mode eigenvalue) and " "overrides ``rayleigh_beta_k``. ``rayleigh_alpha_m`` still applies." @@ -195,7 +196,7 @@ class PushoverCase(Entity): base_nodes: list[PositiveInt] = Field( default_factory=list, description="Nodes whose reactions sum into the 'base shear' for the curve. " - "Leave empty to use every restrained node in the project.", + "Leave empty to use every restrained node in the project.", ) system: str = "BandGeneral" constraints: str = "Plain" @@ -244,23 +245,30 @@ class ResponseSpectrumCase(Entity): type: Literal["ResponseSpectrum"] = "ResponseSpectrum" modal_case_id: PositiveInt = Field( - ..., description="ID of the ModalCase whose mode shapes drive this analysis.", + ..., + description="ID of the ModalCase whose mode shapes drive this analysis.", ) spectrum_id: PositiveInt = Field( - ..., description="ID of the ResponseSpectrum to apply.", + ..., + description="ID of the ResponseSpectrum to apply.", ) direction: int = Field( - ..., ge=1, le=6, description="DOF direction (1..6) for the seismic excitation.", + ..., + ge=1, + le=6, + description="DOF direction (1..6) for the seismic excitation.", ) combination: Literal["SRSS", "CQC"] = "SRSS" damping_ratio: float | None = Field( - default=None, ge=0.0, le=1.0, + default=None, + ge=0.0, + le=1.0, description="Override the spectrum's damping for CQC correlation. " - "Defaults to the spectrum's damping_ratio.", + "Defaults to the spectrum's damping_ratio.", ) AnalysisCase = Annotated[ - Union[StaticCase, ModalCase, TransientCase, PushoverCase, ResponseSpectrumCase], + StaticCase | ModalCase | TransientCase | PushoverCase | ResponseSpectrumCase, Field(discriminator="type"), ] diff --git a/src/otko/core/catalog/__init__.py b/src/otko/core/catalog/__init__.py index 1581566..31978fc 100644 --- a/src/otko/core/catalog/__init__.py +++ b/src/otko/core/catalog/__init__.py @@ -8,7 +8,9 @@ from pydantic import BaseModel from otko.core.catalog.generated.elastic import ElasticSpec from otko.core.catalog.generated.elastic_perfectly_plastic import ElasticPerfectlyPlasticSpec -from otko.core.catalog.generated.elastic_perfectly_plastic_with_gap import ElasticPerfectlyPlasticWithGapSpec +from otko.core.catalog.generated.elastic_perfectly_plastic_with_gap import ( + ElasticPerfectlyPlasticWithGapSpec, +) from otko.core.catalog.generated.viscous import ViscousSpec from otko.core.catalog.generated.viscous_damper import ViscousDamperSpec from otko.core.catalog.generated.hyperbolic_gap import HyperbolicGapSpec @@ -21,8 +23,12 @@ from otko.core.catalog.generated.steel02 import Steel02Spec from otko.core.catalog.generated.reinforcing_steel import ReinforcingSteelSpec from otko.core.catalog.generated.hysteretic import HystereticSpec from otko.core.catalog.generated.ramberg_osgood_steel import RambergOsgoodSteelSpec -from otko.core.catalog.generated.concrete01_zero_tensile_strength import Concrete01ZeroTensileStrengthSpec -from otko.core.catalog.generated.concrete02_linear_tension_softening import Concrete02LinearTensionSofteningSpec +from otko.core.catalog.generated.concrete01_zero_tensile_strength import ( + Concrete01ZeroTensileStrengthSpec, +) +from otko.core.catalog.generated.concrete02_linear_tension_softening import ( + Concrete02LinearTensionSofteningSpec, +) from otko.core.catalog.generated.concrete04_popovics_concrete import Concrete04PopovicsConcreteSpec from otko.core.catalog.generated.concrete06 import Concrete06Spec from otko.core.catalog.generated.concretecm import ConcretecmSpec @@ -50,10 +56,16 @@ from otko.core.catalog.generated.elastic_membrane_plate import ElasticMembranePl from otko.core.catalog.generated.layeredshell import LayeredshellSpec from otko.core.catalog.generated.ground_motion_record import GroundMotionRecordSpec from otko.core.catalog.generated.elastic_beam_column import ElasticBeamColumnSpec -from otko.core.catalog.generated.elastic_timoshenko_beam_column import ElasticTimoshenkoBeamColumnSpec +from otko.core.catalog.generated.elastic_timoshenko_beam_column import ( + ElasticTimoshenkoBeamColumnSpec, +) from otko.core.catalog.generated.force_based_beam_column import ForceBasedBeamColumnSpec -from otko.core.catalog.generated.displacement_based_beam_column import DisplacementBasedBeamColumnSpec -from otko.core.catalog.generated.flexure_shear_interaction_displacement_based_beam_column import FlexureShearInteractionDisplacementBasedBeamColumnSpec +from otko.core.catalog.generated.displacement_based_beam_column import ( + DisplacementBasedBeamColumnSpec, +) +from otko.core.catalog.generated.flexure_shear_interaction_displacement_based_beam_column import ( + FlexureShearInteractionDisplacementBasedBeamColumnSpec, +) from otko.core.catalog.generated.truss import TrussSpec from otko.core.catalog.generated.corotational_truss import CorotationalTrussSpec from otko.core.catalog.generated.quad import QuadSpec @@ -67,64 +79,124 @@ from otko.core.catalog.generated.auto_equal_constraint import AutoEqualConstrain CATALOG: dict[str, type[BaseModel]] = { - 'Elastic': ElasticSpec, - 'Elastic_Perfectly_Plastic': ElasticPerfectlyPlasticSpec, - 'Elastic_Perfectly_Plastic_with_Gap': ElasticPerfectlyPlasticWithGapSpec, - 'Viscous': ViscousSpec, - 'Viscous_Damper': ViscousDamperSpec, - 'Hyperbolic_Gap': HyperbolicGapSpec, - 'PySimple1': Pysimple1Spec, - 'TzSimple1': Tzsimple1Spec, - 'QzSimple1': Qzsimple1Spec, - 'BondSP01': Bondsp01Spec, - 'Steel01': Steel01Spec, - 'Steel02': Steel02Spec, - 'Reinforcing_steel': ReinforcingSteelSpec, - 'Hysteretic': HystereticSpec, - 'Ramberg-Osgood steel': RambergOsgoodSteelSpec, - 'Concrete01_(Zero_tensile_strength)': Concrete01ZeroTensileStrengthSpec, - 'Concrete02_(Linear_tension_softening)': Concrete02LinearTensionSofteningSpec, - 'Concrete04_(Popovics_concrete)': Concrete04PopovicsConcreteSpec, - 'Concrete06': Concrete06Spec, - 'ConcreteCM': ConcretecmSpec, - 'Series': SeriesSpec, - 'Parallel': ParallelSpec, - 'Section_Aggregator': SectionAggregatorSpec, - 'Initial_Strain': InitialStrainSpec, - 'Initial_Stress': InitialStressSpec, - 'MinMax': MinmaxSpec, - 'User_Material': UserMaterialSpec, - 'Elastic_Isotropic': ElasticIsotropicSpec, - 'Elastic_Orthotropic': ElasticOrthotropicSpec, - 'J2Plasticity': J2plasticitySpec, - 'Damage2p': Damage2pSpec, - 'PressureIndependMultiYield': PressureindependmultiyieldSpec, - 'PressureDependMultiYield': PressuredependmultiyieldSpec, - 'PressureDependMultiYield02': Pressuredependmultiyield02Spec, - 'Contact': ContactSpec, - 'Elastic_Section': ElasticSectionSpec, - 'Fiber': FiberSpec, - 'Fiber_Custom': FiberCustomSpec, - 'FiberInt': FiberintSpec, - 'Plate_Fiber': PlateFiberSpec, - 'Elastic_Membrane_Plate': ElasticMembranePlateSpec, - 'LayeredShell': LayeredshellSpec, - 'Ground_Motion_Record': GroundMotionRecordSpec, - 'Elastic_Beam-Column': ElasticBeamColumnSpec, - 'Elastic_Timoshenko_Beam-Column': ElasticTimoshenkoBeamColumnSpec, - 'Force-Based_Beam-Column': ForceBasedBeamColumnSpec, - 'Displacement-Based_Beam-Column': DisplacementBasedBeamColumnSpec, - 'Flexure-Shear_Interaction_Displacement-Based_Beam-Column': FlexureShearInteractionDisplacementBasedBeamColumnSpec, - 'Truss': TrussSpec, - 'Corotational_Truss': CorotationalTrussSpec, - 'Quad': QuadSpec, - 'Shell': ShellSpec, - 'ShellDKGQ': ShelldkgqSpec, - 'Tri31': Tri31Spec, - 'QuadUP': QuadupSpec, - 'Standard_Brick_Element': StandardBrickElementSpec, - 'Auto_Zero_Length': AutoZeroLengthSpec, - 'Auto_equal_constraint': AutoEqualConstraintSpec, + "Elastic": ElasticSpec, + "Elastic_Perfectly_Plastic": ElasticPerfectlyPlasticSpec, + "Elastic_Perfectly_Plastic_with_Gap": ElasticPerfectlyPlasticWithGapSpec, + "Viscous": ViscousSpec, + "Viscous_Damper": ViscousDamperSpec, + "Hyperbolic_Gap": HyperbolicGapSpec, + "PySimple1": Pysimple1Spec, + "TzSimple1": Tzsimple1Spec, + "QzSimple1": Qzsimple1Spec, + "BondSP01": Bondsp01Spec, + "Steel01": Steel01Spec, + "Steel02": Steel02Spec, + "Reinforcing_steel": ReinforcingSteelSpec, + "Hysteretic": HystereticSpec, + "Ramberg-Osgood steel": RambergOsgoodSteelSpec, + "Concrete01_(Zero_tensile_strength)": Concrete01ZeroTensileStrengthSpec, + "Concrete02_(Linear_tension_softening)": Concrete02LinearTensionSofteningSpec, + "Concrete04_(Popovics_concrete)": Concrete04PopovicsConcreteSpec, + "Concrete06": Concrete06Spec, + "ConcreteCM": ConcretecmSpec, + "Series": SeriesSpec, + "Parallel": ParallelSpec, + "Section_Aggregator": SectionAggregatorSpec, + "Initial_Strain": InitialStrainSpec, + "Initial_Stress": InitialStressSpec, + "MinMax": MinmaxSpec, + "User_Material": UserMaterialSpec, + "Elastic_Isotropic": ElasticIsotropicSpec, + "Elastic_Orthotropic": ElasticOrthotropicSpec, + "J2Plasticity": J2plasticitySpec, + "Damage2p": Damage2pSpec, + "PressureIndependMultiYield": PressureindependmultiyieldSpec, + "PressureDependMultiYield": PressuredependmultiyieldSpec, + "PressureDependMultiYield02": Pressuredependmultiyield02Spec, + "Contact": ContactSpec, + "Elastic_Section": ElasticSectionSpec, + "Fiber": FiberSpec, + "Fiber_Custom": FiberCustomSpec, + "FiberInt": FiberintSpec, + "Plate_Fiber": PlateFiberSpec, + "Elastic_Membrane_Plate": ElasticMembranePlateSpec, + "LayeredShell": LayeredshellSpec, + "Ground_Motion_Record": GroundMotionRecordSpec, + "Elastic_Beam-Column": ElasticBeamColumnSpec, + "Elastic_Timoshenko_Beam-Column": ElasticTimoshenkoBeamColumnSpec, + "Force-Based_Beam-Column": ForceBasedBeamColumnSpec, + "Displacement-Based_Beam-Column": DisplacementBasedBeamColumnSpec, + "Flexure-Shear_Interaction_Displacement-Based_Beam-Column": FlexureShearInteractionDisplacementBasedBeamColumnSpec, + "Truss": TrussSpec, + "Corotational_Truss": CorotationalTrussSpec, + "Quad": QuadSpec, + "Shell": ShellSpec, + "ShellDKGQ": ShelldkgqSpec, + "Tri31": Tri31Spec, + "QuadUP": QuadupSpec, + "Standard_Brick_Element": StandardBrickElementSpec, + "Auto_Zero_Length": AutoZeroLengthSpec, + "Auto_equal_constraint": AutoEqualConstraintSpec, } -__all__ = ["CATALOG", 'ElasticSpec', 'ElasticPerfectlyPlasticSpec', 'ElasticPerfectlyPlasticWithGapSpec', 'ViscousSpec', 'ViscousDamperSpec', 'HyperbolicGapSpec', 'Pysimple1Spec', 'Tzsimple1Spec', 'Qzsimple1Spec', 'Bondsp01Spec', 'Steel01Spec', 'Steel02Spec', 'ReinforcingSteelSpec', 'HystereticSpec', 'RambergOsgoodSteelSpec', 'Concrete01ZeroTensileStrengthSpec', 'Concrete02LinearTensionSofteningSpec', 'Concrete04PopovicsConcreteSpec', 'Concrete06Spec', 'ConcretecmSpec', 'SeriesSpec', 'ParallelSpec', 'SectionAggregatorSpec', 'InitialStrainSpec', 'InitialStressSpec', 'MinmaxSpec', 'UserMaterialSpec', 'ElasticIsotropicSpec', 'ElasticOrthotropicSpec', 'J2plasticitySpec', 'Damage2pSpec', 'PressureindependmultiyieldSpec', 'PressuredependmultiyieldSpec', 'Pressuredependmultiyield02Spec', 'ContactSpec', 'ElasticSectionSpec', 'FiberSpec', 'FiberCustomSpec', 'FiberintSpec', 'PlateFiberSpec', 'ElasticMembranePlateSpec', 'LayeredshellSpec', 'GroundMotionRecordSpec', 'ElasticBeamColumnSpec', 'ElasticTimoshenkoBeamColumnSpec', 'ForceBasedBeamColumnSpec', 'DisplacementBasedBeamColumnSpec', 'FlexureShearInteractionDisplacementBasedBeamColumnSpec', 'TrussSpec', 'CorotationalTrussSpec', 'QuadSpec', 'ShellSpec', 'ShelldkgqSpec', 'Tri31Spec', 'QuadupSpec', 'StandardBrickElementSpec', 'AutoZeroLengthSpec', 'AutoEqualConstraintSpec'] +__all__ = [ + "CATALOG", + "ElasticSpec", + "ElasticPerfectlyPlasticSpec", + "ElasticPerfectlyPlasticWithGapSpec", + "ViscousSpec", + "ViscousDamperSpec", + "HyperbolicGapSpec", + "Pysimple1Spec", + "Tzsimple1Spec", + "Qzsimple1Spec", + "Bondsp01Spec", + "Steel01Spec", + "Steel02Spec", + "ReinforcingSteelSpec", + "HystereticSpec", + "RambergOsgoodSteelSpec", + "Concrete01ZeroTensileStrengthSpec", + "Concrete02LinearTensionSofteningSpec", + "Concrete04PopovicsConcreteSpec", + "Concrete06Spec", + "ConcretecmSpec", + "SeriesSpec", + "ParallelSpec", + "SectionAggregatorSpec", + "InitialStrainSpec", + "InitialStressSpec", + "MinmaxSpec", + "UserMaterialSpec", + "ElasticIsotropicSpec", + "ElasticOrthotropicSpec", + "J2plasticitySpec", + "Damage2pSpec", + "PressureindependmultiyieldSpec", + "PressuredependmultiyieldSpec", + "Pressuredependmultiyield02Spec", + "ContactSpec", + "ElasticSectionSpec", + "FiberSpec", + "FiberCustomSpec", + "FiberintSpec", + "PlateFiberSpec", + "ElasticMembranePlateSpec", + "LayeredshellSpec", + "GroundMotionRecordSpec", + "ElasticBeamColumnSpec", + "ElasticTimoshenkoBeamColumnSpec", + "ForceBasedBeamColumnSpec", + "DisplacementBasedBeamColumnSpec", + "FlexureShearInteractionDisplacementBasedBeamColumnSpec", + "TrussSpec", + "CorotationalTrussSpec", + "QuadSpec", + "ShellSpec", + "ShelldkgqSpec", + "Tri31Spec", + "QuadupSpec", + "StandardBrickElementSpec", + "AutoZeroLengthSpec", + "AutoEqualConstraintSpec", +] diff --git a/src/otko/core/catalog/generated/__init__.py b/src/otko/core/catalog/generated/__init__.py index d0aed63..170a391 100644 --- a/src/otko/core/catalog/generated/__init__.py +++ b/src/otko/core/catalog/generated/__init__.py @@ -7,7 +7,9 @@ from typing import Union from otko.core.catalog.generated.elastic import ElasticSpec from otko.core.catalog.generated.elastic_perfectly_plastic import ElasticPerfectlyPlasticSpec -from otko.core.catalog.generated.elastic_perfectly_plastic_with_gap import ElasticPerfectlyPlasticWithGapSpec +from otko.core.catalog.generated.elastic_perfectly_plastic_with_gap import ( + ElasticPerfectlyPlasticWithGapSpec, +) from otko.core.catalog.generated.viscous import ViscousSpec from otko.core.catalog.generated.viscous_damper import ViscousDamperSpec from otko.core.catalog.generated.hyperbolic_gap import HyperbolicGapSpec @@ -20,8 +22,12 @@ from otko.core.catalog.generated.steel02 import Steel02Spec from otko.core.catalog.generated.reinforcing_steel import ReinforcingSteelSpec from otko.core.catalog.generated.hysteretic import HystereticSpec from otko.core.catalog.generated.ramberg_osgood_steel import RambergOsgoodSteelSpec -from otko.core.catalog.generated.concrete01_zero_tensile_strength import Concrete01ZeroTensileStrengthSpec -from otko.core.catalog.generated.concrete02_linear_tension_softening import Concrete02LinearTensionSofteningSpec +from otko.core.catalog.generated.concrete01_zero_tensile_strength import ( + Concrete01ZeroTensileStrengthSpec, +) +from otko.core.catalog.generated.concrete02_linear_tension_softening import ( + Concrete02LinearTensionSofteningSpec, +) from otko.core.catalog.generated.concrete04_popovics_concrete import Concrete04PopovicsConcreteSpec from otko.core.catalog.generated.concrete06 import Concrete06Spec from otko.core.catalog.generated.concretecm import ConcretecmSpec @@ -49,10 +55,16 @@ from otko.core.catalog.generated.elastic_membrane_plate import ElasticMembranePl from otko.core.catalog.generated.layeredshell import LayeredshellSpec from otko.core.catalog.generated.ground_motion_record import GroundMotionRecordSpec from otko.core.catalog.generated.elastic_beam_column import ElasticBeamColumnSpec -from otko.core.catalog.generated.elastic_timoshenko_beam_column import ElasticTimoshenkoBeamColumnSpec +from otko.core.catalog.generated.elastic_timoshenko_beam_column import ( + ElasticTimoshenkoBeamColumnSpec, +) from otko.core.catalog.generated.force_based_beam_column import ForceBasedBeamColumnSpec -from otko.core.catalog.generated.displacement_based_beam_column import DisplacementBasedBeamColumnSpec -from otko.core.catalog.generated.flexure_shear_interaction_displacement_based_beam_column import FlexureShearInteractionDisplacementBasedBeamColumnSpec +from otko.core.catalog.generated.displacement_based_beam_column import ( + DisplacementBasedBeamColumnSpec, +) +from otko.core.catalog.generated.flexure_shear_interaction_displacement_based_beam_column import ( + FlexureShearInteractionDisplacementBasedBeamColumnSpec, +) from otko.core.catalog.generated.truss import TrussSpec from otko.core.catalog.generated.corotational_truss import CorotationalTrussSpec from otko.core.catalog.generated.quad import QuadSpec @@ -65,19 +77,141 @@ from otko.core.catalog.generated.auto_zero_length import AutoZeroLengthSpec from otko.core.catalog.generated.auto_equal_constraint import AutoEqualConstraintSpec -StandardUniaxialMaterials = Union[ElasticSpec, ElasticPerfectlyPlasticSpec, ElasticPerfectlyPlasticWithGapSpec, ViscousSpec, ViscousDamperSpec, HyperbolicGapSpec, Pysimple1Spec, Tzsimple1Spec, Qzsimple1Spec, Bondsp01Spec] -UniaxialSteelMaterials = Union[Steel01Spec, Steel02Spec, ReinforcingSteelSpec, HystereticSpec, RambergOsgoodSteelSpec] -UniaxialConcreteMaterials = Union[Concrete01ZeroTensileStrengthSpec, Concrete02LinearTensionSofteningSpec, Concrete04PopovicsConcreteSpec, Concrete06Spec, ConcretecmSpec] -CombinedMaterials = Union[SeriesSpec, ParallelSpec, SectionAggregatorSpec, InitialStrainSpec, InitialStressSpec, MinmaxSpec] +StandardUniaxialMaterials = Union[ + ElasticSpec, + ElasticPerfectlyPlasticSpec, + ElasticPerfectlyPlasticWithGapSpec, + ViscousSpec, + ViscousDamperSpec, + HyperbolicGapSpec, + Pysimple1Spec, + Tzsimple1Spec, + Qzsimple1Spec, + Bondsp01Spec, +] +UniaxialSteelMaterials = Union[ + Steel01Spec, Steel02Spec, ReinforcingSteelSpec, HystereticSpec, RambergOsgoodSteelSpec +] +UniaxialConcreteMaterials = Union[ + Concrete01ZeroTensileStrengthSpec, + Concrete02LinearTensionSofteningSpec, + Concrete04PopovicsConcreteSpec, + Concrete06Spec, + ConcretecmSpec, +] +CombinedMaterials = Union[ + SeriesSpec, + ParallelSpec, + SectionAggregatorSpec, + InitialStrainSpec, + InitialStressSpec, + MinmaxSpec, +] UserMaterials = UserMaterialSpec -MultidimensionalNdMaterials = Union[ElasticIsotropicSpec, ElasticOrthotropicSpec, J2plasticitySpec, Damage2pSpec, PressureindependmultiyieldSpec, PressuredependmultiyieldSpec, Pressuredependmultiyield02Spec, ContactSpec] -SectionForceDeformation = Union[ElasticSectionSpec, FiberSpec, FiberCustomSpec, FiberintSpec, PlateFiberSpec, ElasticMembranePlateSpec, LayeredshellSpec] +MultidimensionalNdMaterials = Union[ + ElasticIsotropicSpec, + ElasticOrthotropicSpec, + J2plasticitySpec, + Damage2pSpec, + PressureindependmultiyieldSpec, + PressuredependmultiyieldSpec, + Pressuredependmultiyield02Spec, + ContactSpec, +] +SectionForceDeformation = Union[ + ElasticSectionSpec, + FiberSpec, + FiberCustomSpec, + FiberintSpec, + PlateFiberSpec, + ElasticMembranePlateSpec, + LayeredshellSpec, +] Records = GroundMotionRecordSpec -BeamColumnElements = Union[ElasticBeamColumnSpec, ElasticTimoshenkoBeamColumnSpec, ForceBasedBeamColumnSpec, DisplacementBasedBeamColumnSpec, FlexureShearInteractionDisplacementBasedBeamColumnSpec] +BeamColumnElements = Union[ + ElasticBeamColumnSpec, + ElasticTimoshenkoBeamColumnSpec, + ForceBasedBeamColumnSpec, + DisplacementBasedBeamColumnSpec, + FlexureShearInteractionDisplacementBasedBeamColumnSpec, +] TrussElements = Union[TrussSpec, CorotationalTrussSpec] SurfaceElements = Union[QuadSpec, ShellSpec, ShelldkgqSpec, Tri31Spec, QuadupSpec] SolidElements = StandardBrickElementSpec Autozl = AutoZeroLengthSpec Autoedof = AutoEqualConstraintSpec -__all__ = ['ElasticSpec', 'ElasticPerfectlyPlasticSpec', 'ElasticPerfectlyPlasticWithGapSpec', 'ViscousSpec', 'ViscousDamperSpec', 'HyperbolicGapSpec', 'Pysimple1Spec', 'Tzsimple1Spec', 'Qzsimple1Spec', 'Bondsp01Spec', 'Steel01Spec', 'Steel02Spec', 'ReinforcingSteelSpec', 'HystereticSpec', 'RambergOsgoodSteelSpec', 'Concrete01ZeroTensileStrengthSpec', 'Concrete02LinearTensionSofteningSpec', 'Concrete04PopovicsConcreteSpec', 'Concrete06Spec', 'ConcretecmSpec', 'SeriesSpec', 'ParallelSpec', 'SectionAggregatorSpec', 'InitialStrainSpec', 'InitialStressSpec', 'MinmaxSpec', 'UserMaterialSpec', 'ElasticIsotropicSpec', 'ElasticOrthotropicSpec', 'J2plasticitySpec', 'Damage2pSpec', 'PressureindependmultiyieldSpec', 'PressuredependmultiyieldSpec', 'Pressuredependmultiyield02Spec', 'ContactSpec', 'ElasticSectionSpec', 'FiberSpec', 'FiberCustomSpec', 'FiberintSpec', 'PlateFiberSpec', 'ElasticMembranePlateSpec', 'LayeredshellSpec', 'GroundMotionRecordSpec', 'ElasticBeamColumnSpec', 'ElasticTimoshenkoBeamColumnSpec', 'ForceBasedBeamColumnSpec', 'DisplacementBasedBeamColumnSpec', 'FlexureShearInteractionDisplacementBasedBeamColumnSpec', 'TrussSpec', 'CorotationalTrussSpec', 'QuadSpec', 'ShellSpec', 'ShelldkgqSpec', 'Tri31Spec', 'QuadupSpec', 'StandardBrickElementSpec', 'AutoZeroLengthSpec', 'AutoEqualConstraintSpec', 'StandardUniaxialMaterials', 'UniaxialSteelMaterials', 'UniaxialConcreteMaterials', 'CombinedMaterials', 'UserMaterials', 'MultidimensionalNdMaterials', 'SectionForceDeformation', 'Records', 'BeamColumnElements', 'TrussElements', 'SurfaceElements', 'SolidElements', 'Autozl', 'Autoedof'] +__all__ = [ + "ElasticSpec", + "ElasticPerfectlyPlasticSpec", + "ElasticPerfectlyPlasticWithGapSpec", + "ViscousSpec", + "ViscousDamperSpec", + "HyperbolicGapSpec", + "Pysimple1Spec", + "Tzsimple1Spec", + "Qzsimple1Spec", + "Bondsp01Spec", + "Steel01Spec", + "Steel02Spec", + "ReinforcingSteelSpec", + "HystereticSpec", + "RambergOsgoodSteelSpec", + "Concrete01ZeroTensileStrengthSpec", + "Concrete02LinearTensionSofteningSpec", + "Concrete04PopovicsConcreteSpec", + "Concrete06Spec", + "ConcretecmSpec", + "SeriesSpec", + "ParallelSpec", + "SectionAggregatorSpec", + "InitialStrainSpec", + "InitialStressSpec", + "MinmaxSpec", + "UserMaterialSpec", + "ElasticIsotropicSpec", + "ElasticOrthotropicSpec", + "J2plasticitySpec", + "Damage2pSpec", + "PressureindependmultiyieldSpec", + "PressuredependmultiyieldSpec", + "Pressuredependmultiyield02Spec", + "ContactSpec", + "ElasticSectionSpec", + "FiberSpec", + "FiberCustomSpec", + "FiberintSpec", + "PlateFiberSpec", + "ElasticMembranePlateSpec", + "LayeredshellSpec", + "GroundMotionRecordSpec", + "ElasticBeamColumnSpec", + "ElasticTimoshenkoBeamColumnSpec", + "ForceBasedBeamColumnSpec", + "DisplacementBasedBeamColumnSpec", + "FlexureShearInteractionDisplacementBasedBeamColumnSpec", + "TrussSpec", + "CorotationalTrussSpec", + "QuadSpec", + "ShellSpec", + "ShelldkgqSpec", + "Tri31Spec", + "QuadupSpec", + "StandardBrickElementSpec", + "AutoZeroLengthSpec", + "AutoEqualConstraintSpec", + "StandardUniaxialMaterials", + "UniaxialSteelMaterials", + "UniaxialConcreteMaterials", + "CombinedMaterials", + "UserMaterials", + "MultidimensionalNdMaterials", + "SectionForceDeformation", + "Records", + "BeamColumnElements", + "TrussElements", + "SurfaceElements", + "SolidElements", + "Autozl", + "Autoedof", +] diff --git a/src/otko/core/catalog/generated/auto_equal_constraint.py b/src/otko/core/catalog/generated/auto_equal_constraint.py index 63a05a4..52c13e1 100644 --- a/src/otko/core/catalog/generated/auto_equal_constraint.py +++ b/src/otko/core/catalog/generated/auto_equal_constraint.py @@ -17,478 +17,478 @@ from pydantic import BaseModel, ConfigDict, Field class AutoEqualConstraintSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Auto_equal_constraint', - 'x-book': 'AutoEDOF', - 'dependencies': [ + "x-gid-name": "Auto_equal_constraint", + "x-book": "AutoEDOF", + "dependencies": [ { - 'field': 'source', - 'gid_name': 'Source', - 'rules': [ + "field": "source", + "gid_name": "Source", + "rules": [ { - 'trigger': 'Layer', - 'actions': [ + "trigger": "Layer", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Layer_name', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Layer_name", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'All', - 'actions': [ + "trigger": "All", + "actions": [ { - 'action': 'HIDE', - 'field': 'Layer_name', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Layer_name", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Visible', - 'actions': [ + "trigger": "Visible", + "actions": [ { - 'action': 'HIDE', - 'field': 'Layer_name', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Layer_name", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'x_criteria', - 'gid_name': 'X-Criteria', - 'rules': [ + "field": "x_criteria", + "gid_name": "X-Criteria", + "rules": [ { - 'trigger': 'Any', - 'actions': [ + "trigger": "Any", + "actions": [ { - 'action': 'HIDE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Fixed', - 'actions': [ + "trigger": "Fixed", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'GT', - 'actions': [ + "trigger": "GT", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'GET', - 'actions': [ + "trigger": "GET", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'LT', - 'actions': [ + "trigger": "LT", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'LET', - 'actions': [ + "trigger": "LET", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Between', - 'actions': [ + "trigger": "Between", + "actions": [ { - 'action': 'HIDE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'y_criteria', - 'gid_name': 'Y-Criteria', - 'rules': [ + "field": "y_criteria", + "gid_name": "Y-Criteria", + "rules": [ { - 'trigger': 'Any', - 'actions': [ + "trigger": "Any", + "actions": [ { - 'action': 'HIDE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Fixed', - 'actions': [ + "trigger": "Fixed", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'GT', - 'actions': [ + "trigger": "GT", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'GET', - 'actions': [ + "trigger": "GET", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'LT', - 'actions': [ + "trigger": "LT", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'LET', - 'actions': [ + "trigger": "LET", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Between', - 'actions': [ + "trigger": "Between", + "actions": [ { - 'action': 'HIDE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'z_criteria', - 'gid_name': 'Z-Criteria', - 'rules': [ + "field": "z_criteria", + "gid_name": "Z-Criteria", + "rules": [ { - 'trigger': 'Any', - 'actions': [ + "trigger": "Any", + "actions": [ { - 'action': 'HIDE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Fixed', - 'actions': [ + "trigger": "Fixed", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'GT', - 'actions': [ + "trigger": "GT", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'GET', - 'actions': [ + "trigger": "GET", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'LT', - 'actions': [ + "trigger": "LT", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'LET', - 'actions': [ + "trigger": "LET", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Between', - 'actions': [ + "trigger": "Between", + "actions": [ { - 'action': 'HIDE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, @@ -498,29 +498,40 @@ class AutoEqualConstraintSpec(BaseModel): } ) - determine_master_node: Literal['Existed', 'Extra'] = 'Existed' - source: Literal['Layer', 'All', 'Visible'] = Field('Layer', description='Layer: Points or Nodes from specific layer regardless the visibility\\nAll: all points or nodes regardless the visibility\\nVisible: Only visible points or nodes') - layer_name: str = Field('Layer0', description='Layer with name "AutoZL" will be ignored.') - x_criteria: Literal['Any', 'Fixed', 'GT', 'GET', 'LT', 'LET', 'Between'] = Field('Any', description='GT: Greater than\\nGET: Greater/Equal than\\nLT: Less than\\nLET: Less/Equal than\\nBetween: inside (min,max) space including boundaries') + determine_master_node: Literal["Existed", "Extra"] = "Existed" + source: Literal["Layer", "All", "Visible"] = Field( + "Layer", + description="Layer: Points or Nodes from specific layer regardless the visibility\\nAll: all points or nodes regardless the visibility\\nVisible: Only visible points or nodes", + ) + layer_name: str = Field("Layer0", description='Layer with name "AutoZL" will be ignored.') + x_criteria: Literal["Any", "Fixed", "GT", "GET", "LT", "LET", "Between"] = Field( + "Any", + description="GT: Greater than\\nGET: Greater/Equal than\\nLT: Less than\\nLET: Less/Equal than\\nBetween: inside (min,max) space including boundaries", + ) x_coordinate: float = 0.0 min_x_coordinate: float = 0.0 max_x_coordinate: float = 0.0 - y_criteria: Literal['Any', 'Fixed', 'GT', 'GET', 'LT', 'LET', 'Between'] = Field('Any', description='GT: Greater than\\nGET: Greater/Equal than\\nLT: Less than\\nLET: Less/Equal than\\nBetween: inside (min,max) space including boundaries') + y_criteria: Literal["Any", "Fixed", "GT", "GET", "LT", "LET", "Between"] = Field( + "Any", + description="GT: Greater than\\nGET: Greater/Equal than\\nLT: Less than\\nLET: Less/Equal than\\nBetween: inside (min,max) space including boundaries", + ) y_coordinate: float = 0.0 min_y_coordinate: float = 0.0 max_y_coordinate: float = 0.0 - z_criteria: Literal['Any', 'Fixed', 'GT', 'GET', 'LT', 'LET', 'Between'] = Field('Any', description='GT: Greater than\\nGET: Greater/Equal than\\nLT: Less than\\nLET: Less/Equal than\\nBetween: inside (min,max) space including boundaries') + z_criteria: Literal["Any", "Fixed", "GT", "GET", "LT", "LET", "Between"] = Field( + "Any", + description="GT: Greater than\\nGET: Greater/Equal than\\nLT: Less than\\nLET: Less/Equal than\\nBetween: inside (min,max) space including boundaries", + ) z_coordinate: float = 0.0 min_z_coordinate: float = 0.0 max_z_coordinate: float = 0.0 - extra_point_node_offset: Literal['_'] = '_' + extra_point_node_offset: Literal["_"] = "_" x_offset: float = 0.0 y_offset: float = 0.0 z_offset: float = 0.0 - x_translation: Literal['1', '0'] = '1' - y_translation: Literal['1', '0'] = '1' - z_translation: Literal['1', '0'] = '1' - x_rotation: Literal['1', '0'] = '1' - y_rotation: Literal['1', '0'] = '1' - z_rotation: Literal['1', '0'] = '1' - + x_translation: Literal["1", "0"] = "1" + y_translation: Literal["1", "0"] = "1" + z_translation: Literal["1", "0"] = "1" + x_rotation: Literal["1", "0"] = "1" + y_rotation: Literal["1", "0"] = "1" + z_rotation: Literal["1", "0"] = "1" diff --git a/src/otko/core/catalog/generated/auto_zero_length.py b/src/otko/core/catalog/generated/auto_zero_length.py index 880646f..e25d1d4 100644 --- a/src/otko/core/catalog/generated/auto_zero_length.py +++ b/src/otko/core/catalog/generated/auto_zero_length.py @@ -17,634 +17,634 @@ from pydantic import BaseModel, ConfigDict, Field class AutoZeroLengthSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Auto_Zero_Length', - 'x-book': 'AutoZL', - 'dependencies': [ + "x-gid-name": "Auto_Zero_Length", + "x-book": "AutoZL", + "dependencies": [ { - 'field': 'source', - 'gid_name': 'Source', - 'rules': [ + "field": "source", + "gid_name": "Source", + "rules": [ { - 'trigger': 'Layer', - 'actions': [ + "trigger": "Layer", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Layer_name', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Layer_name", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'All', - 'actions': [ + "trigger": "All", + "actions": [ { - 'action': 'HIDE', - 'field': 'Layer_name', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Layer_name", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Visible', - 'actions': [ + "trigger": "Visible", + "actions": [ { - 'action': 'HIDE', - 'field': 'Layer_name', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Layer_name", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'x_criteria', - 'gid_name': 'X-Criteria', - 'rules': [ + "field": "x_criteria", + "gid_name": "X-Criteria", + "rules": [ { - 'trigger': 'Any', - 'actions': [ + "trigger": "Any", + "actions": [ { - 'action': 'HIDE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Fixed', - 'actions': [ + "trigger": "Fixed", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'GT', - 'actions': [ + "trigger": "GT", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'GET', - 'actions': [ + "trigger": "GET", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'LT', - 'actions': [ + "trigger": "LT", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'LET', - 'actions': [ + "trigger": "LET", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Between', - 'actions': [ + "trigger": "Between", + "actions": [ { - 'action': 'HIDE', - 'field': 'X-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Min_X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Min_X-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Max_X-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Max_X-Coordinate", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'y_criteria', - 'gid_name': 'Y-Criteria', - 'rules': [ + "field": "y_criteria", + "gid_name": "Y-Criteria", + "rules": [ { - 'trigger': 'Any', - 'actions': [ + "trigger": "Any", + "actions": [ { - 'action': 'HIDE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Fixed', - 'actions': [ + "trigger": "Fixed", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'GT', - 'actions': [ + "trigger": "GT", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'GET', - 'actions': [ + "trigger": "GET", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'LT', - 'actions': [ + "trigger": "LT", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'LET', - 'actions': [ + "trigger": "LET", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Between', - 'actions': [ + "trigger": "Between", + "actions": [ { - 'action': 'HIDE', - 'field': 'Y-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Min_Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Min_Y-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Max_Y-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Max_Y-Coordinate", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'z_criteria', - 'gid_name': 'Z-Criteria', - 'rules': [ + "field": "z_criteria", + "gid_name": "Z-Criteria", + "rules": [ { - 'trigger': 'Any', - 'actions': [ + "trigger": "Any", + "actions": [ { - 'action': 'HIDE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Fixed', - 'actions': [ + "trigger": "Fixed", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'GT', - 'actions': [ + "trigger": "GT", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'GET', - 'actions': [ + "trigger": "GET", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'LT', - 'actions': [ + "trigger": "LT", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'LET', - 'actions': [ + "trigger": "LET", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Between', - 'actions': [ + "trigger": "Between", + "actions": [ { - 'action': 'HIDE', - 'field': 'Z-Coordinate', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Min_Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Min_Z-Coordinate", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Max_Z-Coordinate', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Max_Z-Coordinate", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_ux', - 'gid_name': 'Activate_Ux', - 'rules': [ + "field": "activate_ux", + "gid_name": "Activate_Ux", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Ux_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Ux_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Ux_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Ux_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_uy', - 'gid_name': 'Activate_Uy', - 'rules': [ + "field": "activate_uy", + "gid_name": "Activate_Uy", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Uy_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Uy_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Uy_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Uy_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_uz', - 'gid_name': 'Activate_Uz', - 'rules': [ + "field": "activate_uz", + "gid_name": "Activate_Uz", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Uz_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Uz_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Uz_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Uz_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_rx', - 'gid_name': 'Activate_Rx', - 'rules': [ + "field": "activate_rx", + "gid_name": "Activate_Rx", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Rx_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Rx_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Rx_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rx_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_ry', - 'gid_name': 'Activate_Ry', - 'rules': [ + "field": "activate_ry", + "gid_name": "Activate_Ry", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Ry_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Ry_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Ry_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Ry_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_rz', - 'gid_name': 'Activate_Rz', - 'rules': [ + "field": "activate_rz", + "gid_name": "Activate_Rz", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Rz_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Rz_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Rz_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rz_material", + "target": "#CURRENT#", }, ], }, @@ -654,34 +654,45 @@ class AutoZeroLengthSpec(BaseModel): } ) - source: Literal['Layer', 'All', 'Visible'] = Field('Layer', description='Layer: Points or Nodes from specific layer regardless the visibility\\nAll: all points or nodes regardless the visibility\\nVisible: Only visible points or nodes') - layer_name: str = Field('Layer0', description='Layer with name "AutoZL" will be ignored.') - x_criteria: Literal['Any', 'Fixed', 'GT', 'GET', 'LT', 'LET', 'Between'] = Field('Any', description='GT: Greater than\\nGET: Greater/Equal than\\nLT: Less than\\nLET: Less/Equal than\\nBetween: inside (min,max) space including boundaries') + source: Literal["Layer", "All", "Visible"] = Field( + "Layer", + description="Layer: Points or Nodes from specific layer regardless the visibility\\nAll: all points or nodes regardless the visibility\\nVisible: Only visible points or nodes", + ) + layer_name: str = Field("Layer0", description='Layer with name "AutoZL" will be ignored.') + x_criteria: Literal["Any", "Fixed", "GT", "GET", "LT", "LET", "Between"] = Field( + "Any", + description="GT: Greater than\\nGET: Greater/Equal than\\nLT: Less than\\nLET: Less/Equal than\\nBetween: inside (min,max) space including boundaries", + ) x_coordinate: float = 0.0 min_x_coordinate: float = 0.0 max_x_coordinate: float = 0.0 - y_criteria: Literal['Any', 'Fixed', 'GT', 'GET', 'LT', 'LET', 'Between'] = Field('Any', description='GT: Greater than\\nGET: Greater/Equal than\\nLT: Less than\\nLET: Less/Equal than\\nBetween: inside (min,max) space including boundaries') + y_criteria: Literal["Any", "Fixed", "GT", "GET", "LT", "LET", "Between"] = Field( + "Any", + description="GT: Greater than\\nGET: Greater/Equal than\\nLT: Less than\\nLET: Less/Equal than\\nBetween: inside (min,max) space including boundaries", + ) y_coordinate: float = 0.0 min_y_coordinate: float = 0.0 max_y_coordinate: float = 0.0 - z_criteria: Literal['Any', 'Fixed', 'GT', 'GET', 'LT', 'LET', 'Between'] = Field('Any', description='GT: Greater than\\nGET: Greater/Equal than\\nLT: Less than\\nLET: Less/Equal than\\nBetween: inside (min,max) space including boundaries') + z_criteria: Literal["Any", "Fixed", "GT", "GET", "LT", "LET", "Between"] = Field( + "Any", + description="GT: Greater than\\nGET: Greater/Equal than\\nLT: Less than\\nLET: Less/Equal than\\nBetween: inside (min,max) space including boundaries", + ) z_coordinate: float = 0.0 min_z_coordinate: float = 0.0 max_z_coordinate: float = 0.0 - extra_point_node_offset: Literal['_'] = '_' + extra_point_node_offset: Literal["_"] = "_" x_offset: float = 0.0 y_offset: float = 0.0 z_offset: float = 0.0 - activate_ux: Literal['0', '1'] = '1' - ux_material: str = 'Elastic' - activate_uy: Literal['0', '1'] = '0' - uy_material: str = 'Elastic' - activate_uz: Literal['0', '1'] = '0' - uz_material: str = 'Elastic' - activate_rx: Literal['0', '1'] = '0' - rx_material: str = 'Elastic' - activate_ry: Literal['0', '1'] = '0' - ry_material: str = 'Elastic' - activate_rz: Literal['0', '1'] = '0' - rz_material: str = 'Elastic' - + activate_ux: Literal["0", "1"] = "1" + ux_material: str = "Elastic" + activate_uy: Literal["0", "1"] = "0" + uy_material: str = "Elastic" + activate_uz: Literal["0", "1"] = "0" + uz_material: str = "Elastic" + activate_rx: Literal["0", "1"] = "0" + rx_material: str = "Elastic" + activate_ry: Literal["0", "1"] = "0" + ry_material: str = "Elastic" + activate_rz: Literal["0", "1"] = "0" + rz_material: str = "Elastic" diff --git a/src/otko/core/catalog/generated/bondsp01.py b/src/otko/core/catalog/generated/bondsp01.py index 884e4f0..032d37f 100644 --- a/src/otko/core/catalog/generated/bondsp01.py +++ b/src/otko/core/catalog/generated/bondsp01.py @@ -23,86 +23,86 @@ class CyclicDataRow(BaseModel): class Bondsp01Spec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'BondSP01', - 'x-book': 'Standard_Uniaxial_Materials', - 'dependencies': [ + "x-gid-name": "BondSP01", + "x-book": "Standard_Uniaxial_Materials", + "dependencies": [ { - 'field': 'formulation', - 'gid_name': 'Formulation', - 'rules': [ + "field": "formulation", + "gid_name": "Formulation", + "rules": [ { - 'trigger': 'Stress-Slip', - 'actions': [ + "trigger": "Stress-Slip", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Yield_stress_Fy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Yield_stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Ultimate_stress_Fu', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Ultimate_stress_Fu", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Yield_force_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Yield_force_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Ultimate_Force_Fu', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Ultimate_Force_Fu", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Force-Slip', - 'actions': [ + "trigger": "Force-Slip", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Yield_force_Fy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Yield_force_Fy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Ultimate_Force_Fu', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Ultimate_Force_Fu", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Yield_stress_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Yield_stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Ultimate_stress_Fu', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Ultimate_stress_Fu", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -112,16 +112,15 @@ class Bondsp01Spec(BaseModel): } ) - material: Literal['_'] = '_' - formulation: Literal['Stress-Slip', 'Force-Slip'] = 'Stress-Slip' - yield_stress_fy: str = '500 MPa' # TODO: unit-aware type - yield_force_fy: str = '157 kN' # TODO: unit-aware type - rebar_slip_at_fy: str = '1 mm' # TODO: unit-aware type - ultimate_stress_fu: str = '675 MPa' # TODO: unit-aware type - ultimate_force_fu: str = '212 kN' # TODO: unit-aware type - rebar_slip_at_the_loaded_end_at_the_bar_fracture_strength: str = '2 mm' # TODO: unit-aware type + material: Literal["_"] = "_" + formulation: Literal["Stress-Slip", "Force-Slip"] = "Stress-Slip" + yield_stress_fy: str = "500 MPa" # TODO: unit-aware type + yield_force_fy: str = "157 kN" # TODO: unit-aware type + rebar_slip_at_fy: str = "1 mm" # TODO: unit-aware type + ultimate_stress_fu: str = "675 MPa" # TODO: unit-aware type + ultimate_force_fu: str = "212 kN" # TODO: unit-aware type + rebar_slip_at_the_loaded_end_at_the_bar_fracture_strength: str = "2 mm" # TODO: unit-aware type initial_hardening: float = 0.4 pinching_factor_for_the_cyclic_slip_vs_bar_response: float = 0.5 - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - diff --git a/src/otko/core/catalog/generated/concrete01_zero_tensile_strength.py b/src/otko/core/catalog/generated/concrete01_zero_tensile_strength.py index 9e51c2b..7316257 100644 --- a/src/otko/core/catalog/generated/concrete01_zero_tensile_strength.py +++ b/src/otko/core/catalog/generated/concrete01_zero_tensile_strength.py @@ -23,176 +23,176 @@ class CyclicDataRow(BaseModel): class Concrete01ZeroTensileStrengthSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Concrete01_(Zero_tensile_strength)', - 'x-book': 'Uniaxial_Concrete_Materials', - 'dependencies': [ + "x-gid-name": "Concrete01_(Zero_tensile_strength)", + "x-book": "Uniaxial_Concrete_Materials", + "dependencies": [ { - 'field': 'strength_class', - 'gid_name': 'Strength_class', - 'rules': [ + "field": "strength_class", + "gid_name": "Strength_class", + "rules": [ { - 'trigger': 'C12/15', - 'actions': [ + "trigger": "C12/15", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C16/20', - 'actions': [ + "trigger": "C16/20", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C20/25', - 'actions': [ + "trigger": "C20/25", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C25/30', - 'actions': [ + "trigger": "C25/30", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C30/37', - 'actions': [ + "trigger": "C30/37", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C35/45', - 'actions': [ + "trigger": "C35/45", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C40/50', - 'actions': [ + "trigger": "C40/50", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C45/55', - 'actions': [ + "trigger": "C45/55", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C50/60', - 'actions': [ + "trigger": "C50/60", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C60/75', - 'actions': [ + "trigger": "C60/75", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C70/85', - 'actions': [ + "trigger": "C70/85", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C80/95', - 'actions': [ + "trigger": "C80/95", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C90/105', - 'actions': [ + "trigger": "C90/105", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Custom', - 'actions': [ + "trigger": "Custom", + "actions": [ { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -202,15 +202,36 @@ class Concrete01ZeroTensileStrengthSpec(BaseModel): } ) - material: Literal['Concrete01'] = Field('Concrete01', description='uniaxial Kent-Scott-Park concrete material object with degraded linear unloading/reloading stiffness according to the work of Karsan-Jirsa and no tensile strength') - strength_class: Literal['C12/15', 'C16/20', 'C20/25', 'C25/30', 'C30/37', 'C35/45', 'C40/50', 'C45/55', 'C50/60', 'C55/67', 'C60/75', 'C70/85', 'C80/95', 'C90/105', 'Custom'] = Field('Custom', description='Choose concrete strength category, to take some standard values in the following fields.') - strength_type: Literal['Mean', 'Characteristic'] = 'Characteristic' - compressive_strength_fpc: str = '-20 MPa' # TODO: unit-aware type + material: Literal["Concrete01"] = Field( + "Concrete01", + description="uniaxial Kent-Scott-Park concrete material object with degraded linear unloading/reloading stiffness according to the work of Karsan-Jirsa and no tensile strength", + ) + strength_class: Literal[ + "C12/15", + "C16/20", + "C20/25", + "C25/30", + "C30/37", + "C35/45", + "C40/50", + "C45/55", + "C50/60", + "C55/67", + "C60/75", + "C70/85", + "C80/95", + "C90/105", + "Custom", + ] = Field( + "Custom", + description="Choose concrete strength category, to take some standard values in the following fields.", + ) + strength_type: Literal["Mean", "Characteristic"] = "Characteristic" + compressive_strength_fpc: str = "-20 MPa" # TODO: unit-aware type strain_at_maximum_strength_epsc0: float = -2.0e-3 - crushing_strength_fpcu: str = '-17.0 MPa' # TODO: unit-aware type + crushing_strength_fpcu: str = "-17.0 MPa" # TODO: unit-aware type strain_at_crushing_strength_epscu: float = -3.5e-3 - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/catalog/generated/concrete02_linear_tension_softening.py b/src/otko/core/catalog/generated/concrete02_linear_tension_softening.py index cf44214..13c9c60 100644 --- a/src/otko/core/catalog/generated/concrete02_linear_tension_softening.py +++ b/src/otko/core/catalog/generated/concrete02_linear_tension_softening.py @@ -23,176 +23,176 @@ class CyclicDataRow(BaseModel): class Concrete02LinearTensionSofteningSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Concrete02_(Linear_tension_softening)', - 'x-book': 'Uniaxial_Concrete_Materials', - 'dependencies': [ + "x-gid-name": "Concrete02_(Linear_tension_softening)", + "x-book": "Uniaxial_Concrete_Materials", + "dependencies": [ { - 'field': 'strength_class', - 'gid_name': 'Strength_class', - 'rules': [ + "field": "strength_class", + "gid_name": "Strength_class", + "rules": [ { - 'trigger': 'C12/15', - 'actions': [ + "trigger": "C12/15", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C16/20', - 'actions': [ + "trigger": "C16/20", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C20/25', - 'actions': [ + "trigger": "C20/25", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C25/30', - 'actions': [ + "trigger": "C25/30", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C30/37', - 'actions': [ + "trigger": "C30/37", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C35/45', - 'actions': [ + "trigger": "C35/45", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C40/50', - 'actions': [ + "trigger": "C40/50", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C45/55', - 'actions': [ + "trigger": "C45/55", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C50/60', - 'actions': [ + "trigger": "C50/60", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C60/75', - 'actions': [ + "trigger": "C60/75", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C70/85', - 'actions': [ + "trigger": "C70/85", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C80/95', - 'actions': [ + "trigger": "C80/95", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C90/105', - 'actions': [ + "trigger": "C90/105", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Custom', - 'actions': [ + "trigger": "Custom", + "actions": [ { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -202,18 +202,36 @@ class Concrete02LinearTensionSofteningSpec(BaseModel): } ) - material: Literal['Concrete02'] = 'Concrete02' - strength_class: Literal['C12/15', 'C16/20', 'C20/25', 'C25/30', 'C30/37', 'C35/45', 'C40/50', 'C45/55', 'C50/60', 'C55/67', 'C60/75', 'C70/85', 'C80/95', 'C90/105', 'Custom'] = Field('Custom', description='Choose concrete strength category, to generate fast properties in the following fields.') - strength_type: Literal['Mean', 'Characteristic'] = 'Characteristic' - compressive_strength_fpc: str = '-20 MPa' # TODO: unit-aware type + material: Literal["Concrete02"] = "Concrete02" + strength_class: Literal[ + "C12/15", + "C16/20", + "C20/25", + "C25/30", + "C30/37", + "C35/45", + "C40/50", + "C45/55", + "C50/60", + "C55/67", + "C60/75", + "C70/85", + "C80/95", + "C90/105", + "Custom", + ] = Field( + "Custom", + description="Choose concrete strength category, to generate fast properties in the following fields.", + ) + strength_type: Literal["Mean", "Characteristic"] = "Characteristic" + compressive_strength_fpc: str = "-20 MPa" # TODO: unit-aware type strain_at_maximum_strength_epsc0: float = -2.0e-3 - crushing_strength_fpcu: str = '-17.0 MPa' # TODO: unit-aware type + crushing_strength_fpcu: str = "-17.0 MPa" # TODO: unit-aware type strain_at_crushing_strength_epscu: float = -3.5e-3 ratio_between_unloading_slope_at_epscu_and_initial_slope_lambda: float = 0.5 - tensile_strength_ft: str = '2.2 MPa' # TODO: unit-aware type - tension_softening_stiffness_ets: str = '2 GPa' # TODO: unit-aware type - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + tensile_strength_ft: str = "2.2 MPa" # TODO: unit-aware type + tension_softening_stiffness_ets: str = "2 GPa" # TODO: unit-aware type + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/catalog/generated/concrete04_popovics_concrete.py b/src/otko/core/catalog/generated/concrete04_popovics_concrete.py index 061e782..f4df18b 100644 --- a/src/otko/core/catalog/generated/concrete04_popovics_concrete.py +++ b/src/otko/core/catalog/generated/concrete04_popovics_concrete.py @@ -23,176 +23,176 @@ class CyclicDataRow(BaseModel): class Concrete04PopovicsConcreteSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Concrete04_(Popovics_concrete)', - 'x-book': 'Uniaxial_Concrete_Materials', - 'dependencies': [ + "x-gid-name": "Concrete04_(Popovics_concrete)", + "x-book": "Uniaxial_Concrete_Materials", + "dependencies": [ { - 'field': 'strength_class', - 'gid_name': 'Strength_class', - 'rules': [ + "field": "strength_class", + "gid_name": "Strength_class", + "rules": [ { - 'trigger': 'C12/15', - 'actions': [ + "trigger": "C12/15", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C16/20', - 'actions': [ + "trigger": "C16/20", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C20/25', - 'actions': [ + "trigger": "C20/25", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C25/30', - 'actions': [ + "trigger": "C25/30", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C30/37', - 'actions': [ + "trigger": "C30/37", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C35/45', - 'actions': [ + "trigger": "C35/45", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C40/50', - 'actions': [ + "trigger": "C40/50", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C45/55', - 'actions': [ + "trigger": "C45/55", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C50/60', - 'actions': [ + "trigger": "C50/60", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C60/75', - 'actions': [ + "trigger": "C60/75", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C70/85', - 'actions': [ + "trigger": "C70/85", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C80/95', - 'actions': [ + "trigger": "C80/95", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C90/105', - 'actions': [ + "trigger": "C90/105", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Custom', - 'actions': [ + "trigger": "Custom", + "actions": [ { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -202,17 +202,35 @@ class Concrete04PopovicsConcreteSpec(BaseModel): } ) - material: Literal['Concrete04'] = 'Concrete04' - strength_class: Literal['C12/15', 'C16/20', 'C20/25', 'C25/30', 'C30/37', 'C35/45', 'C40/50', 'C45/55', 'C50/60', 'C55/67', 'C60/75', 'C70/85', 'C80/95', 'C90/105', 'Custom'] = Field('Custom', description='Choose concrete strength category, to generate fast properties in the following fields.') - strength_type: Literal['Mean', 'Characteristic'] = 'Characteristic' - compressive_strength: str = '-20 MPa' # TODO: unit-aware type + material: Literal["Concrete04"] = "Concrete04" + strength_class: Literal[ + "C12/15", + "C16/20", + "C20/25", + "C25/30", + "C30/37", + "C35/45", + "C40/50", + "C45/55", + "C50/60", + "C55/67", + "C60/75", + "C70/85", + "C80/95", + "C90/105", + "Custom", + ] = Field( + "Custom", + description="Choose concrete strength category, to generate fast properties in the following fields.", + ) + strength_type: Literal["Mean", "Characteristic"] = "Characteristic" + compressive_strength: str = "-20 MPa" # TODO: unit-aware type strain_at_maximum_strength: float = -2.0e-3 strain_at_crushing_strength: float = -3.5e-3 - initial_stiffness: str = '30 GPa' # TODO: unit-aware type - maximum_tensile_strength: str = '2.2 MPa' # TODO: unit-aware type + initial_stiffness: str = "30 GPa" # TODO: unit-aware type + maximum_tensile_strength: str = "2.2 MPa" # TODO: unit-aware type ultimate_tensile_strain: float = 1e-3 - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/catalog/generated/concrete06.py b/src/otko/core/catalog/generated/concrete06.py index 4fce8b6..771a25b 100644 --- a/src/otko/core/catalog/generated/concrete06.py +++ b/src/otko/core/catalog/generated/concrete06.py @@ -23,176 +23,176 @@ class CyclicDataRow(BaseModel): class Concrete06Spec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Concrete06', - 'x-book': 'Uniaxial_Concrete_Materials', - 'dependencies': [ + "x-gid-name": "Concrete06", + "x-book": "Uniaxial_Concrete_Materials", + "dependencies": [ { - 'field': 'strength_class', - 'gid_name': 'Strength_class', - 'rules': [ + "field": "strength_class", + "gid_name": "Strength_class", + "rules": [ { - 'trigger': 'C12/15', - 'actions': [ + "trigger": "C12/15", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C16/20', - 'actions': [ + "trigger": "C16/20", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C20/25', - 'actions': [ + "trigger": "C20/25", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C25/30', - 'actions': [ + "trigger": "C25/30", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C30/37', - 'actions': [ + "trigger": "C30/37", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C35/45', - 'actions': [ + "trigger": "C35/45", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C40/50', - 'actions': [ + "trigger": "C40/50", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C45/55', - 'actions': [ + "trigger": "C45/55", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C50/60', - 'actions': [ + "trigger": "C50/60", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C60/75', - 'actions': [ + "trigger": "C60/75", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C70/85', - 'actions': [ + "trigger": "C70/85", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C80/95', - 'actions': [ + "trigger": "C80/95", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C90/105', - 'actions': [ + "trigger": "C90/105", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Custom', - 'actions': [ + "trigger": "Custom", + "actions": [ { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -202,20 +202,38 @@ class Concrete06Spec(BaseModel): } ) - material: Literal['Concrete06'] = 'Concrete06' - strength_class: Literal['C12/15', 'C16/20', 'C20/25', 'C25/30', 'C30/37', 'C35/45', 'C40/50', 'C45/55', 'C50/60', 'C55/67', 'C60/75', 'C70/85', 'C80/95', 'C90/105', 'Custom'] = Field('Custom', description='Choose concrete strength category, to generate fast properties in the following fields.') - strength_type: Literal['Mean', 'Characteristic'] = 'Characteristic' - concrete_compressive_strength_fc: str = '-20 MPa' # TODO: unit-aware type + material: Literal["Concrete06"] = "Concrete06" + strength_class: Literal[ + "C12/15", + "C16/20", + "C20/25", + "C25/30", + "C30/37", + "C35/45", + "C40/50", + "C45/55", + "C50/60", + "C55/67", + "C60/75", + "C70/85", + "C80/95", + "C90/105", + "Custom", + ] = Field( + "Custom", + description="Choose concrete strength category, to generate fast properties in the following fields.", + ) + strength_type: Literal["Mean", "Characteristic"] = "Characteristic" + concrete_compressive_strength_fc: str = "-20 MPa" # TODO: unit-aware type strain_at_compressive_strength_e0: float = -2.0e-3 compressive_shape_factor_n: int = 1 post_peak_compressive_shape_factor_k: int = 1 parameter_a1_for_compressive_plastic_strain_definition: float = -2.500e-003 - tensile_strength_fcr: str = '2.2 MPa' # TODO: unit-aware type + tensile_strength_fcr: str = "2.2 MPa" # TODO: unit-aware type tensile_strain_at_peak_stress_ecr: float = 1.100e-004 exponent_of_the_tension_stiffering_curve_b: int = 1 parameter_a2_for_tensile_plastic_strain_definition: float = 7.700e-004 - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/catalog/generated/concretecm.py b/src/otko/core/catalog/generated/concretecm.py index a8a3593..224cecf 100644 --- a/src/otko/core/catalog/generated/concretecm.py +++ b/src/otko/core/catalog/generated/concretecm.py @@ -23,202 +23,202 @@ class CyclicDataRow(BaseModel): class ConcretecmSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'ConcreteCM', - 'x-book': 'Uniaxial_Concrete_Materials', - 'dependencies': [ + "x-gid-name": "ConcreteCM", + "x-book": "Uniaxial_Concrete_Materials", + "dependencies": [ { - 'field': 'strength_class', - 'gid_name': 'Strength_class', - 'rules': [ + "field": "strength_class", + "gid_name": "Strength_class", + "rules": [ { - 'trigger': 'C12/15', - 'actions': [ + "trigger": "C12/15", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C16/20', - 'actions': [ + "trigger": "C16/20", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C20/25', - 'actions': [ + "trigger": "C20/25", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C25/30', - 'actions': [ + "trigger": "C25/30", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C30/37', - 'actions': [ + "trigger": "C30/37", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C35/45', - 'actions': [ + "trigger": "C35/45", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C40/50', - 'actions': [ + "trigger": "C40/50", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C45/55', - 'actions': [ + "trigger": "C45/55", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C50/60', - 'actions': [ + "trigger": "C50/60", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C60/75', - 'actions': [ + "trigger": "C60/75", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C70/85', - 'actions': [ + "trigger": "C70/85", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C80/95', - 'actions': [ + "trigger": "C80/95", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'C90/105', - 'actions': [ + "trigger": "C90/105", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Custom', - 'actions': [ + "trigger": "Custom", + "actions": [ { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'consider_gap_closure', - 'gid_name': 'Consider_gap_closure', - 'rules': [ + "field": "consider_gap_closure", + "gid_name": "Consider_gap_closure", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Gap_parameter', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Gap_parameter", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'Gap_parameter', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Gap_parameter", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -228,19 +228,37 @@ class ConcretecmSpec(BaseModel): } ) - material: Literal['ConcreteCM'] = 'ConcreteCM' - strength_class: Literal['C12/15', 'C16/20', 'C20/25', 'C25/30', 'C30/37', 'C35/45', 'C40/50', 'C45/55', 'C50/60', 'C55/67', 'C60/75', 'C70/85', 'C80/95', 'C90/105', 'Custom'] = Field('Custom', description='Choose concrete strength category, to generate fast properties in the following fields.') - strength_type: Literal['Mean', 'Characteristic'] = 'Characteristic' - concrete_compressive_strength_fpcc: str = '-20 MPa' # TODO: unit-aware type + material: Literal["ConcreteCM"] = "ConcreteCM" + strength_class: Literal[ + "C12/15", + "C16/20", + "C20/25", + "C25/30", + "C30/37", + "C35/45", + "C40/50", + "C45/55", + "C50/60", + "C55/67", + "C60/75", + "C70/85", + "C80/95", + "C90/105", + "Custom", + ] = Field( + "Custom", + description="Choose concrete strength category, to generate fast properties in the following fields.", + ) + strength_type: Literal["Mean", "Characteristic"] = "Characteristic" + concrete_compressive_strength_fpcc: str = "-20 MPa" # TODO: unit-aware type strain_at_compressive_strength_epcc: float = -2.0e-3 - initial_tangent_modulus_ec: str = '30 GPa' # TODO: unit-aware type + initial_tangent_modulus_ec: str = "30 GPa" # TODO: unit-aware type shape_parameter_rc_in_tsai_s_equation_for_compression: int = 7 critical_strain_on_compression_envelope: float = 1.035 - tensile_strength_ft: str = '2.2 MPa' # TODO: unit-aware type + tensile_strength_ft: str = "2.2 MPa" # TODO: unit-aware type strain_at_tensile_strength_et: float = 1.100e-004 shape_parameter_rc_in_tsai_s_equation_for_tension: float = 1.2 critical_strain_on_tension_envelope: int = 10000 - consider_gap_closure: Literal['1', '0'] = '0' - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + consider_gap_closure: Literal["1", "0"] = "0" + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - diff --git a/src/otko/core/catalog/generated/conditions/__init__.py b/src/otko/core/catalog/generated/conditions/__init__.py index 3f4328b..fd52e19 100644 --- a/src/otko/core/catalog/generated/conditions/__init__.py +++ b/src/otko/core/catalog/generated/conditions/__init__.py @@ -15,42 +15,173 @@ from otko.core.catalog.generated.conditions.line_uniform_forces import LineUnifo from otko.core.catalog.generated.conditions.point_displacements import PointDisplacementsSpec from otko.core.catalog.generated.conditions.line_displacements import LineDisplacementsSpec from otko.core.catalog.generated.conditions.surface_displacements import SurfaceDisplacementsSpec -from otko.core.catalog.generated.conditions.point_ground_motion_from_record import PointGroundMotionFromRecordSpec -from otko.core.catalog.generated.conditions.point_sine_ground_motion import PointSineGroundMotionSpec -from otko.core.catalog.generated.conditions.point_equal_constraint_master_node import PointEqualConstraintMasterNodeSpec -from otko.core.catalog.generated.conditions.point_equal_constraint_slave_nodes import PointEqualConstraintSlaveNodesSpec -from otko.core.catalog.generated.conditions.line_equal_constraint_slave_nodes import LineEqualConstraintSlaveNodesSpec -from otko.core.catalog.generated.conditions.point_rigid_link_master_node import PointRigidLinkMasterNodeSpec -from otko.core.catalog.generated.conditions.point_rigid_link_slave_nodes import PointRigidLinkSlaveNodesSpec -from otko.core.catalog.generated.conditions.line_rigid_link_slave_nodes import LineRigidLinkSlaveNodesSpec -from otko.core.catalog.generated.conditions.point_rigid_diaphragm_master_node import PointRigidDiaphragmMasterNodeSpec -from otko.core.catalog.generated.conditions.point_rigid_diaphragm_slave_nodes import PointRigidDiaphragmSlaveNodesSpec -from otko.core.catalog.generated.conditions.line_rigid_diaphragm_slave_nodes import LineRigidDiaphragmSlaveNodesSpec +from otko.core.catalog.generated.conditions.point_ground_motion_from_record import ( + PointGroundMotionFromRecordSpec, +) +from otko.core.catalog.generated.conditions.point_sine_ground_motion import ( + PointSineGroundMotionSpec, +) +from otko.core.catalog.generated.conditions.point_equal_constraint_master_node import ( + PointEqualConstraintMasterNodeSpec, +) +from otko.core.catalog.generated.conditions.point_equal_constraint_slave_nodes import ( + PointEqualConstraintSlaveNodesSpec, +) +from otko.core.catalog.generated.conditions.line_equal_constraint_slave_nodes import ( + LineEqualConstraintSlaveNodesSpec, +) +from otko.core.catalog.generated.conditions.point_rigid_link_master_node import ( + PointRigidLinkMasterNodeSpec, +) +from otko.core.catalog.generated.conditions.point_rigid_link_slave_nodes import ( + PointRigidLinkSlaveNodesSpec, +) +from otko.core.catalog.generated.conditions.line_rigid_link_slave_nodes import ( + LineRigidLinkSlaveNodesSpec, +) +from otko.core.catalog.generated.conditions.point_rigid_diaphragm_master_node import ( + PointRigidDiaphragmMasterNodeSpec, +) +from otko.core.catalog.generated.conditions.point_rigid_diaphragm_slave_nodes import ( + PointRigidDiaphragmSlaveNodesSpec, +) +from otko.core.catalog.generated.conditions.line_rigid_diaphragm_slave_nodes import ( + LineRigidDiaphragmSlaveNodesSpec, +) from otko.core.catalog.generated.conditions.point_mass import PointMassSpec from otko.core.catalog.generated.conditions.line_mass import LineMassSpec from otko.core.catalog.generated.conditions.surface_mass import SurfaceMassSpec from otko.core.catalog.generated.conditions.volume_mass import VolumeMassSpec -from otko.core.catalog.generated.conditions.line_rayleigh_damping_elements import LineRayleighDampingElementsSpec -from otko.core.catalog.generated.conditions.surface_rayleigh_damping_elements import SurfaceRayleighDampingElementsSpec -from otko.core.catalog.generated.conditions.volume_rayleigh_damping_elements import VolumeRayleighDampingElementsSpec -from otko.core.catalog.generated.conditions.point_rayleigh_damping_nodes import PointRayleighDampingNodesSpec -from otko.core.catalog.generated.conditions.line_rayleigh_damping_nodes import LineRayleighDampingNodesSpec -from otko.core.catalog.generated.conditions.surface_rayleigh_damping_nodes import SurfaceRayleighDampingNodesSpec -from otko.core.catalog.generated.conditions.volume_rayleigh_damping_nodes import VolumeRayleighDampingNodesSpec +from otko.core.catalog.generated.conditions.line_rayleigh_damping_elements import ( + LineRayleighDampingElementsSpec, +) +from otko.core.catalog.generated.conditions.surface_rayleigh_damping_elements import ( + SurfaceRayleighDampingElementsSpec, +) +from otko.core.catalog.generated.conditions.volume_rayleigh_damping_elements import ( + VolumeRayleighDampingElementsSpec, +) +from otko.core.catalog.generated.conditions.point_rayleigh_damping_nodes import ( + PointRayleighDampingNodesSpec, +) +from otko.core.catalog.generated.conditions.line_rayleigh_damping_nodes import ( + LineRayleighDampingNodesSpec, +) +from otko.core.catalog.generated.conditions.surface_rayleigh_damping_nodes import ( + SurfaceRayleighDampingNodesSpec, +) +from otko.core.catalog.generated.conditions.volume_rayleigh_damping_nodes import ( + VolumeRayleighDampingNodesSpec, +) from otko.core.catalog.generated.conditions.point_zerolength import PointZerolengthSpec -from otko.core.catalog.generated.conditions.line_beam_contact_master_end_nodes import LineBeamContactMasterEndNodesSpec -from otko.core.catalog.generated.conditions.point_beam_contact_slave_nodes import PointBeamContactSlaveNodesSpec -from otko.core.catalog.generated.conditions.point_body_constraint_master_node import PointBodyConstraintMasterNodeSpec -from otko.core.catalog.generated.conditions.point_body_constraint_slave_nodes import PointBodyConstraintSlaveNodesSpec -from otko.core.catalog.generated.conditions.line_body_constraint_slave_nodes import LineBodyConstraintSlaveNodesSpec +from otko.core.catalog.generated.conditions.line_beam_contact_master_end_nodes import ( + LineBeamContactMasterEndNodesSpec, +) +from otko.core.catalog.generated.conditions.point_beam_contact_slave_nodes import ( + PointBeamContactSlaveNodesSpec, +) +from otko.core.catalog.generated.conditions.point_body_constraint_master_node import ( + PointBodyConstraintMasterNodeSpec, +) +from otko.core.catalog.generated.conditions.point_body_constraint_slave_nodes import ( + PointBodyConstraintSlaveNodesSpec, +) +from otko.core.catalog.generated.conditions.line_body_constraint_slave_nodes import ( + LineBodyConstraintSlaveNodesSpec, +) from otko.core.catalog.generated.conditions.zerolength import ZerolengthSpec Restraints = Union[PointRestraintsSpec, LineRestraintsSpec, SurfaceRestraintsSpec] -Loads = Union[PointForcesSpec, LineForcesSpec, SurfaceForcesSpec, LineUniformForcesSpec, PointDisplacementsSpec, LineDisplacementsSpec, SurfaceDisplacementsSpec, PointGroundMotionFromRecordSpec, PointSineGroundMotionSpec] -Constraints = Union[PointEqualConstraintMasterNodeSpec, PointEqualConstraintSlaveNodesSpec, LineEqualConstraintSlaveNodesSpec, PointRigidLinkMasterNodeSpec, PointRigidLinkSlaveNodesSpec, LineRigidLinkSlaveNodesSpec, PointRigidDiaphragmMasterNodeSpec, PointRigidDiaphragmSlaveNodesSpec, LineRigidDiaphragmSlaveNodesSpec] -MassDamping = Union[PointMassSpec, LineMassSpec, SurfaceMassSpec, VolumeMassSpec, LineRayleighDampingElementsSpec, SurfaceRayleighDampingElementsSpec, VolumeRayleighDampingElementsSpec, PointRayleighDampingNodesSpec, LineRayleighDampingNodesSpec, SurfaceRayleighDampingNodesSpec, VolumeRayleighDampingNodesSpec] -ZerolengthElements = Union[PointZerolengthSpec, LineBeamContactMasterEndNodesSpec, PointBeamContactSlaveNodesSpec] -OldConditions = Union[PointBodyConstraintMasterNodeSpec, PointBodyConstraintSlaveNodesSpec, LineBodyConstraintSlaveNodesSpec, ZerolengthSpec] +Loads = Union[ + PointForcesSpec, + LineForcesSpec, + SurfaceForcesSpec, + LineUniformForcesSpec, + PointDisplacementsSpec, + LineDisplacementsSpec, + SurfaceDisplacementsSpec, + PointGroundMotionFromRecordSpec, + PointSineGroundMotionSpec, +] +Constraints = Union[ + PointEqualConstraintMasterNodeSpec, + PointEqualConstraintSlaveNodesSpec, + LineEqualConstraintSlaveNodesSpec, + PointRigidLinkMasterNodeSpec, + PointRigidLinkSlaveNodesSpec, + LineRigidLinkSlaveNodesSpec, + PointRigidDiaphragmMasterNodeSpec, + PointRigidDiaphragmSlaveNodesSpec, + LineRigidDiaphragmSlaveNodesSpec, +] +MassDamping = Union[ + PointMassSpec, + LineMassSpec, + SurfaceMassSpec, + VolumeMassSpec, + LineRayleighDampingElementsSpec, + SurfaceRayleighDampingElementsSpec, + VolumeRayleighDampingElementsSpec, + PointRayleighDampingNodesSpec, + LineRayleighDampingNodesSpec, + SurfaceRayleighDampingNodesSpec, + VolumeRayleighDampingNodesSpec, +] +ZerolengthElements = Union[ + PointZerolengthSpec, LineBeamContactMasterEndNodesSpec, PointBeamContactSlaveNodesSpec +] +OldConditions = Union[ + PointBodyConstraintMasterNodeSpec, + PointBodyConstraintSlaveNodesSpec, + LineBodyConstraintSlaveNodesSpec, + ZerolengthSpec, +] -__all__ = ['PointRestraintsSpec', 'LineRestraintsSpec', 'SurfaceRestraintsSpec', 'PointForcesSpec', 'LineForcesSpec', 'SurfaceForcesSpec', 'LineUniformForcesSpec', 'PointDisplacementsSpec', 'LineDisplacementsSpec', 'SurfaceDisplacementsSpec', 'PointGroundMotionFromRecordSpec', 'PointSineGroundMotionSpec', 'PointEqualConstraintMasterNodeSpec', 'PointEqualConstraintSlaveNodesSpec', 'LineEqualConstraintSlaveNodesSpec', 'PointRigidLinkMasterNodeSpec', 'PointRigidLinkSlaveNodesSpec', 'LineRigidLinkSlaveNodesSpec', 'PointRigidDiaphragmMasterNodeSpec', 'PointRigidDiaphragmSlaveNodesSpec', 'LineRigidDiaphragmSlaveNodesSpec', 'PointMassSpec', 'LineMassSpec', 'SurfaceMassSpec', 'VolumeMassSpec', 'LineRayleighDampingElementsSpec', 'SurfaceRayleighDampingElementsSpec', 'VolumeRayleighDampingElementsSpec', 'PointRayleighDampingNodesSpec', 'LineRayleighDampingNodesSpec', 'SurfaceRayleighDampingNodesSpec', 'VolumeRayleighDampingNodesSpec', 'PointZerolengthSpec', 'LineBeamContactMasterEndNodesSpec', 'PointBeamContactSlaveNodesSpec', 'PointBodyConstraintMasterNodeSpec', 'PointBodyConstraintSlaveNodesSpec', 'LineBodyConstraintSlaveNodesSpec', 'ZerolengthSpec', 'Restraints', 'Loads', 'Constraints', 'MassDamping', 'ZerolengthElements', 'OldConditions'] +__all__ = [ + "PointRestraintsSpec", + "LineRestraintsSpec", + "SurfaceRestraintsSpec", + "PointForcesSpec", + "LineForcesSpec", + "SurfaceForcesSpec", + "LineUniformForcesSpec", + "PointDisplacementsSpec", + "LineDisplacementsSpec", + "SurfaceDisplacementsSpec", + "PointGroundMotionFromRecordSpec", + "PointSineGroundMotionSpec", + "PointEqualConstraintMasterNodeSpec", + "PointEqualConstraintSlaveNodesSpec", + "LineEqualConstraintSlaveNodesSpec", + "PointRigidLinkMasterNodeSpec", + "PointRigidLinkSlaveNodesSpec", + "LineRigidLinkSlaveNodesSpec", + "PointRigidDiaphragmMasterNodeSpec", + "PointRigidDiaphragmSlaveNodesSpec", + "LineRigidDiaphragmSlaveNodesSpec", + "PointMassSpec", + "LineMassSpec", + "SurfaceMassSpec", + "VolumeMassSpec", + "LineRayleighDampingElementsSpec", + "SurfaceRayleighDampingElementsSpec", + "VolumeRayleighDampingElementsSpec", + "PointRayleighDampingNodesSpec", + "LineRayleighDampingNodesSpec", + "SurfaceRayleighDampingNodesSpec", + "VolumeRayleighDampingNodesSpec", + "PointZerolengthSpec", + "LineBeamContactMasterEndNodesSpec", + "PointBeamContactSlaveNodesSpec", + "PointBodyConstraintMasterNodeSpec", + "PointBodyConstraintSlaveNodesSpec", + "LineBodyConstraintSlaveNodesSpec", + "ZerolengthSpec", + "Restraints", + "Loads", + "Constraints", + "MassDamping", + "ZerolengthElements", + "OldConditions", +] diff --git a/src/otko/core/catalog/generated/conditions/line_beam_contact_master_end_nodes.py b/src/otko/core/catalog/generated/conditions/line_beam_contact_master_end_nodes.py index e1c021d..3c063d7 100644 --- a/src/otko/core/catalog/generated/conditions/line_beam_contact_master_end_nodes.py +++ b/src/otko/core/catalog/generated/conditions/line_beam_contact_master_end_nodes.py @@ -15,10 +15,9 @@ from pydantic import BaseModel, ConfigDict, Field class LineBeamContactMasterEndNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Line_Beam_contact_master_end_nodes', - 'x-book': 'ZeroLength_Elements', + "x-gid-name": "Line_Beam_contact_master_end_nodes", + "x-book": "ZeroLength_Elements", } ) beam_contact_id: int = 1 - diff --git a/src/otko/core/catalog/generated/conditions/line_body_constraint_slave_nodes.py b/src/otko/core/catalog/generated/conditions/line_body_constraint_slave_nodes.py index 250dbec..16131a0 100644 --- a/src/otko/core/catalog/generated/conditions/line_body_constraint_slave_nodes.py +++ b/src/otko/core/catalog/generated/conditions/line_body_constraint_slave_nodes.py @@ -17,80 +17,80 @@ from pydantic import BaseModel, ConfigDict, Field class LineBodyConstraintSlaveNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Line_Body_constraint_slave_nodes', - 'x-book': 'Old_Conditions', - 'dependencies': [ + "x-gid-name": "Line_Body_constraint_slave_nodes", + "x-book": "Old_Conditions", + "dependencies": [ { - 'field': 'fast_constraints', - 'gid_name': 'Fast_constraints', - 'rules': [ + "field": "fast_constraints", + "gid_name": "Fast_constraints", + "rules": [ { - 'trigger': 'Pinned_Connection', - 'actions': [ + "trigger": "Pinned_Connection", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Fixed_Connection', - 'actions': [ + "trigger": "Fixed_Connection", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "X-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "1", }, ], }, @@ -100,12 +100,13 @@ class LineBodyConstraintSlaveNodesSpec(BaseModel): } ) - line_constraint_id_number: int = Field(1, description='Enter a positive integer number for the body constraint.') - x_translation: Literal['1', '0'] = '1' - y_translation: Literal['1', '0'] = '1' - z_translation: Literal['1', '0'] = '1' - x_rotation: Literal['1', '0'] = '1' - y_rotation: Literal['1', '0'] = '1' - z_rotation: Literal['1', '0'] = '1' - fast_constraints: Literal['Pinned_Connection', 'Fixed_Connection'] = 'Pinned_Connection' - + line_constraint_id_number: int = Field( + 1, description="Enter a positive integer number for the body constraint." + ) + x_translation: Literal["1", "0"] = "1" + y_translation: Literal["1", "0"] = "1" + z_translation: Literal["1", "0"] = "1" + x_rotation: Literal["1", "0"] = "1" + y_rotation: Literal["1", "0"] = "1" + z_rotation: Literal["1", "0"] = "1" + fast_constraints: Literal["Pinned_Connection", "Fixed_Connection"] = "Pinned_Connection" diff --git a/src/otko/core/catalog/generated/conditions/line_displacements.py b/src/otko/core/catalog/generated/conditions/line_displacements.py index d00e49a..140d8f2 100644 --- a/src/otko/core/catalog/generated/conditions/line_displacements.py +++ b/src/otko/core/catalog/generated/conditions/line_displacements.py @@ -15,15 +15,14 @@ from pydantic import BaseModel, ConfigDict, Field class LineDisplacementsSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Line_Displacements', - 'x-book': 'Loads', + "x-gid-name": "Line_Displacements", + "x-book": "Loads", } ) - x_displacement: str = '0.0m' # TODO: unit-aware type - y_displacement: str = '0.0m' # TODO: unit-aware type - z_displacement: str = '0.0m' # TODO: unit-aware type - x_rotation: str = '0.0 rad' # TODO: unit-aware type - y_rotation: str = '0.0 rad' # TODO: unit-aware type - z_rotation: str = '0.0 rad' # TODO: unit-aware type - + x_displacement: str = "0.0m" # TODO: unit-aware type + y_displacement: str = "0.0m" # TODO: unit-aware type + z_displacement: str = "0.0m" # TODO: unit-aware type + x_rotation: str = "0.0 rad" # TODO: unit-aware type + y_rotation: str = "0.0 rad" # TODO: unit-aware type + z_rotation: str = "0.0 rad" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/conditions/line_equal_constraint_slave_nodes.py b/src/otko/core/catalog/generated/conditions/line_equal_constraint_slave_nodes.py index cdfa971..43d050a 100644 --- a/src/otko/core/catalog/generated/conditions/line_equal_constraint_slave_nodes.py +++ b/src/otko/core/catalog/generated/conditions/line_equal_constraint_slave_nodes.py @@ -17,16 +17,17 @@ from pydantic import BaseModel, ConfigDict, Field class LineEqualConstraintSlaveNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Line_Equal_constraint_slave_nodes', - 'x-book': 'Constraints', + "x-gid-name": "Line_Equal_constraint_slave_nodes", + "x-book": "Constraints", } ) - equal_constraint_id: int = Field(1, description='Enter a positive integer number for the equal constraint.') - x_translation: Literal['1', '0'] = '1' - y_translation: Literal['1', '0'] = '1' - z_translation: Literal['1', '0'] = '1' - x_rotation: Literal['1', '0'] = '1' - y_rotation: Literal['1', '0'] = '1' - z_rotation: Literal['1', '0'] = '1' - + equal_constraint_id: int = Field( + 1, description="Enter a positive integer number for the equal constraint." + ) + x_translation: Literal["1", "0"] = "1" + y_translation: Literal["1", "0"] = "1" + z_translation: Literal["1", "0"] = "1" + x_rotation: Literal["1", "0"] = "1" + y_rotation: Literal["1", "0"] = "1" + z_rotation: Literal["1", "0"] = "1" diff --git a/src/otko/core/catalog/generated/conditions/line_forces.py b/src/otko/core/catalog/generated/conditions/line_forces.py index e03e48a..69a5422 100644 --- a/src/otko/core/catalog/generated/conditions/line_forces.py +++ b/src/otko/core/catalog/generated/conditions/line_forces.py @@ -15,15 +15,14 @@ from pydantic import BaseModel, ConfigDict, Field class LineForcesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Line_Forces', - 'x-book': 'Loads', + "x-gid-name": "Line_Forces", + "x-book": "Loads", } ) - x_force: str = '0.0 kN' # TODO: unit-aware type - y_force: str = '0.0 kN' # TODO: unit-aware type - z_force: str = '0.0 kN' # TODO: unit-aware type - x_moment: str = '0.0 kNm' # TODO: unit-aware type - y_moment: str = '0.0 kNm' # TODO: unit-aware type - z_moment: str = '0.0 kNm' # TODO: unit-aware type - + x_force: str = "0.0 kN" # TODO: unit-aware type + y_force: str = "0.0 kN" # TODO: unit-aware type + z_force: str = "0.0 kN" # TODO: unit-aware type + x_moment: str = "0.0 kNm" # TODO: unit-aware type + y_moment: str = "0.0 kNm" # TODO: unit-aware type + z_moment: str = "0.0 kNm" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/conditions/line_mass.py b/src/otko/core/catalog/generated/conditions/line_mass.py index 891c038..c4b57a2 100644 --- a/src/otko/core/catalog/generated/conditions/line_mass.py +++ b/src/otko/core/catalog/generated/conditions/line_mass.py @@ -17,16 +17,15 @@ from pydantic import BaseModel, ConfigDict, Field class LineMassSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Line_Mass', - 'x-book': 'Mass/Damping', + "x-gid-name": "Line_Mass", + "x-book": "Mass/Damping", } ) - mass_dof_values: Literal[':'] = ':' - x_direction: str = '0.0ton' # TODO: unit-aware type - y_direction: str = '0.0ton' # TODO: unit-aware type - z_direction: str = '0.0ton' # TODO: unit-aware type - x_rotation: str = '0.0ton' # TODO: unit-aware type - y_rotation: str = '0.0ton' # TODO: unit-aware type - z_rotation: str = '0.0ton' # TODO: unit-aware type - + mass_dof_values: Literal[":"] = ":" + x_direction: str = "0.0ton" # TODO: unit-aware type + y_direction: str = "0.0ton" # TODO: unit-aware type + z_direction: str = "0.0ton" # TODO: unit-aware type + x_rotation: str = "0.0ton" # TODO: unit-aware type + y_rotation: str = "0.0ton" # TODO: unit-aware type + z_rotation: str = "0.0ton" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/conditions/line_rayleigh_damping_elements.py b/src/otko/core/catalog/generated/conditions/line_rayleigh_damping_elements.py index 5f24808..dc8a0b1 100644 --- a/src/otko/core/catalog/generated/conditions/line_rayleigh_damping_elements.py +++ b/src/otko/core/catalog/generated/conditions/line_rayleigh_damping_elements.py @@ -15,8 +15,8 @@ from pydantic import BaseModel, ConfigDict, Field class LineRayleighDampingElementsSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Line_Rayleigh_Damping-Elements', - 'x-book': 'Mass/Damping', + "x-gid-name": "Line_Rayleigh_Damping-Elements", + "x-book": "Mass/Damping", } ) @@ -25,4 +25,3 @@ class LineRayleighDampingElementsSpec(BaseModel): betak: float = 0.0 betakinit: float = 0.0 betakcomm: float = 0.0 - diff --git a/src/otko/core/catalog/generated/conditions/line_rayleigh_damping_nodes.py b/src/otko/core/catalog/generated/conditions/line_rayleigh_damping_nodes.py index fbf062d..57679ed 100644 --- a/src/otko/core/catalog/generated/conditions/line_rayleigh_damping_nodes.py +++ b/src/otko/core/catalog/generated/conditions/line_rayleigh_damping_nodes.py @@ -15,8 +15,8 @@ from pydantic import BaseModel, ConfigDict, Field class LineRayleighDampingNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Line_Rayleigh_Damping-Nodes', - 'x-book': 'Mass/Damping', + "x-gid-name": "Line_Rayleigh_Damping-Nodes", + "x-book": "Mass/Damping", } ) @@ -25,4 +25,3 @@ class LineRayleighDampingNodesSpec(BaseModel): betak: float = 0.0 betakinit: float = 0.0 betakcomm: float = 0.0 - diff --git a/src/otko/core/catalog/generated/conditions/line_restraints.py b/src/otko/core/catalog/generated/conditions/line_restraints.py index 74455ee..15ef27e 100644 --- a/src/otko/core/catalog/generated/conditions/line_restraints.py +++ b/src/otko/core/catalog/generated/conditions/line_restraints.py @@ -17,290 +17,290 @@ from pydantic import BaseModel, ConfigDict, Field class LineRestraintsSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Line_Restraints', - 'x-book': 'Restraints', - 'dependencies': [ + "x-gid-name": "Line_Restraints", + "x-book": "Restraints", + "dependencies": [ { - 'field': 'fast_restraints', - 'gid_name': 'Fast_restraints', - 'rules': [ + "field": "fast_restraints", + "gid_name": "Fast_restraints", + "rules": [ { - 'trigger': 'Fix_XYZ', - 'actions': [ + "trigger": "Fix_XYZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "X-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "1", }, ], }, { - 'trigger': 'Fix_XY', - 'actions': [ + "trigger": "Fix_XY", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Fix_XZ', - 'actions': [ + "trigger": "Fix_XZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Fix_YZ', - 'actions': [ + "trigger": "Fix_YZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "X-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '0', + "action": "RESTORE", + "field": "X-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Pin_XYZ', - 'actions': [ + "trigger": "Pin_XYZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Pin_XY', - 'actions': [ + "trigger": "Pin_XY", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Pin_XZ', - 'actions': [ + "trigger": "Pin_XZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Pin_YZ', - 'actions': [ + "trigger": "Pin_YZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '0', + "action": "RESTORE", + "field": "X-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, @@ -310,12 +310,13 @@ class LineRestraintsSpec(BaseModel): } ) - x_translation: Literal['1', '0'] = '1' - y_translation: Literal['1', '0'] = '1' - z_translation: Literal['1', '0'] = '1' - x_rotation: Literal['1', '0'] = '1' - y_rotation: Literal['1', '0'] = '1' - z_rotation: Literal['1', '0'] = '1' - fluid_pressure: Literal['1', '0'] = '0' - fast_restraints: Literal['Fix_XYZ', 'Fix_XY', 'Fix_XZ', 'Fix_YZ', 'Pin_XYZ', 'Pin_XY', 'Pin_XZ', 'Pin_YZ'] = 'Fix_XYZ' - + x_translation: Literal["1", "0"] = "1" + y_translation: Literal["1", "0"] = "1" + z_translation: Literal["1", "0"] = "1" + x_rotation: Literal["1", "0"] = "1" + y_rotation: Literal["1", "0"] = "1" + z_rotation: Literal["1", "0"] = "1" + fluid_pressure: Literal["1", "0"] = "0" + fast_restraints: Literal[ + "Fix_XYZ", "Fix_XY", "Fix_XZ", "Fix_YZ", "Pin_XYZ", "Pin_XY", "Pin_XZ", "Pin_YZ" + ] = "Fix_XYZ" diff --git a/src/otko/core/catalog/generated/conditions/line_rigid_diaphragm_slave_nodes.py b/src/otko/core/catalog/generated/conditions/line_rigid_diaphragm_slave_nodes.py index 107a4fb..aa0b5b9 100644 --- a/src/otko/core/catalog/generated/conditions/line_rigid_diaphragm_slave_nodes.py +++ b/src/otko/core/catalog/generated/conditions/line_rigid_diaphragm_slave_nodes.py @@ -17,11 +17,10 @@ from pydantic import BaseModel, ConfigDict, Field class LineRigidDiaphragmSlaveNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Line_Rigid_diaphragm_slave_nodes', - 'x-book': 'Constraints', + "x-gid-name": "Line_Rigid_diaphragm_slave_nodes", + "x-book": "Constraints", } ) rigid_diaphragm_id: int = 1 - rigid_plane: Literal['XY', 'YZ', 'ZX'] = 'XY' - + rigid_plane: Literal["XY", "YZ", "ZX"] = "XY" diff --git a/src/otko/core/catalog/generated/conditions/line_rigid_link_slave_nodes.py b/src/otko/core/catalog/generated/conditions/line_rigid_link_slave_nodes.py index abfff93..d105126 100644 --- a/src/otko/core/catalog/generated/conditions/line_rigid_link_slave_nodes.py +++ b/src/otko/core/catalog/generated/conditions/line_rigid_link_slave_nodes.py @@ -17,11 +17,12 @@ from pydantic import BaseModel, ConfigDict, Field class LineRigidLinkSlaveNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Line_Rigid_link_slave_nodes', - 'x-book': 'Constraints', + "x-gid-name": "Line_Rigid_link_slave_nodes", + "x-book": "Constraints", } ) - rigid_link_id: int = Field(1, description='Give a positive integer number for body constraint group.') - rigid_link_type: Literal['Bar', 'Beam'] = 'Beam' - + rigid_link_id: int = Field( + 1, description="Give a positive integer number for body constraint group." + ) + rigid_link_type: Literal["Bar", "Beam"] = "Beam" diff --git a/src/otko/core/catalog/generated/conditions/line_uniform_forces.py b/src/otko/core/catalog/generated/conditions/line_uniform_forces.py index 0a0cbc2..3303b6b 100644 --- a/src/otko/core/catalog/generated/conditions/line_uniform_forces.py +++ b/src/otko/core/catalog/generated/conditions/line_uniform_forces.py @@ -15,12 +15,11 @@ from pydantic import BaseModel, ConfigDict, Field class LineUniformForcesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Line_Uniform_Forces', - 'x-book': 'Loads', + "x-gid-name": "Line_Uniform_Forces", + "x-book": "Loads", } ) - uniform_load_along_line_longitudinal_axis: str = '0.0 kN/m' # TODO: unit-aware type - uniform_load_acting_in_local_y_direction: str = '0.0 kN/m' # TODO: unit-aware type - uniform_load_acting_in_local_z_direction: str = '0.0 kN/m' # TODO: unit-aware type - + uniform_load_along_line_longitudinal_axis: str = "0.0 kN/m" # TODO: unit-aware type + uniform_load_acting_in_local_y_direction: str = "0.0 kN/m" # TODO: unit-aware type + uniform_load_acting_in_local_z_direction: str = "0.0 kN/m" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/conditions/point_beam_contact_slave_nodes.py b/src/otko/core/catalog/generated/conditions/point_beam_contact_slave_nodes.py index 5d96720..600c99e 100644 --- a/src/otko/core/catalog/generated/conditions/point_beam_contact_slave_nodes.py +++ b/src/otko/core/catalog/generated/conditions/point_beam_contact_slave_nodes.py @@ -17,15 +17,14 @@ from pydantic import BaseModel, ConfigDict, Field class PointBeamContactSlaveNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Beam_contact_slave_nodes', - 'x-book': 'ZeroLength_Elements', + "x-gid-name": "Point_Beam_contact_slave_nodes", + "x-book": "ZeroLength_Elements", } ) beam_contact_id: int = 1 - contact_material_mat_multidimensional_nd_materials_user_materials: str = 'Contact' - width_radius: str = '0.25m' # TODO: unit-aware type + contact_material_mat_multidimensional_nd_materials_user_materials: str = "Contact" + width_radius: str = "0.25m" # TODO: unit-aware type gap_tolerance: float = 1.0e-10 force_tolerance: float = 1.0e-10 - initial_assumption: Literal['Contact', 'No_Contact'] = 'Contact' - + initial_assumption: Literal["Contact", "No_Contact"] = "Contact" diff --git a/src/otko/core/catalog/generated/conditions/point_body_constraint_master_node.py b/src/otko/core/catalog/generated/conditions/point_body_constraint_master_node.py index b8403c9..8a52b87 100644 --- a/src/otko/core/catalog/generated/conditions/point_body_constraint_master_node.py +++ b/src/otko/core/catalog/generated/conditions/point_body_constraint_master_node.py @@ -15,10 +15,11 @@ from pydantic import BaseModel, ConfigDict, Field class PointBodyConstraintMasterNodeSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Body_constraint_master_node', - 'x-book': 'Old_Conditions', + "x-gid-name": "Point_Body_constraint_master_node", + "x-book": "Old_Conditions", } ) - body_constraint_id_number: int = Field(1, description='Give a positive integer number for body constraint group.') - + body_constraint_id_number: int = Field( + 1, description="Give a positive integer number for body constraint group." + ) diff --git a/src/otko/core/catalog/generated/conditions/point_body_constraint_slave_nodes.py b/src/otko/core/catalog/generated/conditions/point_body_constraint_slave_nodes.py index c51ad33..01c115d 100644 --- a/src/otko/core/catalog/generated/conditions/point_body_constraint_slave_nodes.py +++ b/src/otko/core/catalog/generated/conditions/point_body_constraint_slave_nodes.py @@ -17,80 +17,80 @@ from pydantic import BaseModel, ConfigDict, Field class PointBodyConstraintSlaveNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Body_constraint_slave_nodes', - 'x-book': 'Old_Conditions', - 'dependencies': [ + "x-gid-name": "Point_Body_constraint_slave_nodes", + "x-book": "Old_Conditions", + "dependencies": [ { - 'field': 'fast_constraints', - 'gid_name': 'Fast_constraints', - 'rules': [ + "field": "fast_constraints", + "gid_name": "Fast_constraints", + "rules": [ { - 'trigger': 'Pinned_Connection', - 'actions': [ + "trigger": "Pinned_Connection", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Fixed_Connection', - 'actions': [ + "trigger": "Fixed_Connection", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "X-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "1", }, ], }, @@ -100,12 +100,13 @@ class PointBodyConstraintSlaveNodesSpec(BaseModel): } ) - body_constraint_id_number: int = Field(1, description='Give a positive integer number for body constraint group.') - x_translation: Literal['1', '0'] = '1' - y_translation: Literal['1', '0'] = '1' - z_translation: Literal['1', '0'] = '1' - x_rotation: Literal['1', '0'] = '1' - y_rotation: Literal['1', '0'] = '1' - z_rotation: Literal['1', '0'] = '1' - fast_constraints: Literal['Pinned_Connection', 'Fixed_Connection'] = 'Pinned_Connection' - + body_constraint_id_number: int = Field( + 1, description="Give a positive integer number for body constraint group." + ) + x_translation: Literal["1", "0"] = "1" + y_translation: Literal["1", "0"] = "1" + z_translation: Literal["1", "0"] = "1" + x_rotation: Literal["1", "0"] = "1" + y_rotation: Literal["1", "0"] = "1" + z_rotation: Literal["1", "0"] = "1" + fast_constraints: Literal["Pinned_Connection", "Fixed_Connection"] = "Pinned_Connection" diff --git a/src/otko/core/catalog/generated/conditions/point_displacements.py b/src/otko/core/catalog/generated/conditions/point_displacements.py index 45073fd..e95eb4a 100644 --- a/src/otko/core/catalog/generated/conditions/point_displacements.py +++ b/src/otko/core/catalog/generated/conditions/point_displacements.py @@ -15,15 +15,14 @@ from pydantic import BaseModel, ConfigDict, Field class PointDisplacementsSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Displacements', - 'x-book': 'Loads', + "x-gid-name": "Point_Displacements", + "x-book": "Loads", } ) - x_displacement: str = '0.0m' # TODO: unit-aware type - y_displacement: str = '0.0m' # TODO: unit-aware type - z_displacement: str = '0.0m' # TODO: unit-aware type - x_rotation: str = '0.0 rad' # TODO: unit-aware type - y_rotation: str = '0.0 rad' # TODO: unit-aware type - z_rotation: str = '0.0 rad' # TODO: unit-aware type - + x_displacement: str = "0.0m" # TODO: unit-aware type + y_displacement: str = "0.0m" # TODO: unit-aware type + z_displacement: str = "0.0m" # TODO: unit-aware type + x_rotation: str = "0.0 rad" # TODO: unit-aware type + y_rotation: str = "0.0 rad" # TODO: unit-aware type + z_rotation: str = "0.0 rad" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/conditions/point_equal_constraint_master_node.py b/src/otko/core/catalog/generated/conditions/point_equal_constraint_master_node.py index b5166b0..5c8465e 100644 --- a/src/otko/core/catalog/generated/conditions/point_equal_constraint_master_node.py +++ b/src/otko/core/catalog/generated/conditions/point_equal_constraint_master_node.py @@ -15,10 +15,11 @@ from pydantic import BaseModel, ConfigDict, Field class PointEqualConstraintMasterNodeSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Equal_constraint_master_node', - 'x-book': 'Constraints', + "x-gid-name": "Point_Equal_constraint_master_node", + "x-book": "Constraints", } ) - equal_constraint_id: int = Field(1, description='Give a positive integer number for body constraint group.') - + equal_constraint_id: int = Field( + 1, description="Give a positive integer number for body constraint group." + ) diff --git a/src/otko/core/catalog/generated/conditions/point_equal_constraint_slave_nodes.py b/src/otko/core/catalog/generated/conditions/point_equal_constraint_slave_nodes.py index b5106af..969e08e 100644 --- a/src/otko/core/catalog/generated/conditions/point_equal_constraint_slave_nodes.py +++ b/src/otko/core/catalog/generated/conditions/point_equal_constraint_slave_nodes.py @@ -17,16 +17,17 @@ from pydantic import BaseModel, ConfigDict, Field class PointEqualConstraintSlaveNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Equal_constraint_slave_nodes', - 'x-book': 'Constraints', + "x-gid-name": "Point_Equal_constraint_slave_nodes", + "x-book": "Constraints", } ) - equal_constraint_id: int = Field(1, description='Give a positive integer number for body constraint group.') - x_translation: Literal['1', '0'] = '1' - y_translation: Literal['1', '0'] = '1' - z_translation: Literal['1', '0'] = '1' - x_rotation: Literal['1', '0'] = '1' - y_rotation: Literal['1', '0'] = '1' - z_rotation: Literal['1', '0'] = '1' - + equal_constraint_id: int = Field( + 1, description="Give a positive integer number for body constraint group." + ) + x_translation: Literal["1", "0"] = "1" + y_translation: Literal["1", "0"] = "1" + z_translation: Literal["1", "0"] = "1" + x_rotation: Literal["1", "0"] = "1" + y_rotation: Literal["1", "0"] = "1" + z_rotation: Literal["1", "0"] = "1" diff --git a/src/otko/core/catalog/generated/conditions/point_forces.py b/src/otko/core/catalog/generated/conditions/point_forces.py index 599b530..2bf40d5 100644 --- a/src/otko/core/catalog/generated/conditions/point_forces.py +++ b/src/otko/core/catalog/generated/conditions/point_forces.py @@ -15,15 +15,14 @@ from pydantic import BaseModel, ConfigDict, Field class PointForcesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Forces', - 'x-book': 'Loads', + "x-gid-name": "Point_Forces", + "x-book": "Loads", } ) - x_force: str = '0.0 kN' # TODO: unit-aware type - y_force: str = '0.0 kN' # TODO: unit-aware type - z_force: str = '0.0 kN' # TODO: unit-aware type - x_moment: str = '0.0 kNm' # TODO: unit-aware type - y_moment: str = '0.0 kNm' # TODO: unit-aware type - z_moment: str = '0.0 kNm' # TODO: unit-aware type - + x_force: str = "0.0 kN" # TODO: unit-aware type + y_force: str = "0.0 kN" # TODO: unit-aware type + z_force: str = "0.0 kN" # TODO: unit-aware type + x_moment: str = "0.0 kNm" # TODO: unit-aware type + y_moment: str = "0.0 kNm" # TODO: unit-aware type + z_moment: str = "0.0 kNm" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/conditions/point_ground_motion_from_record.py b/src/otko/core/catalog/generated/conditions/point_ground_motion_from_record.py index da6ddef..9898b6d 100644 --- a/src/otko/core/catalog/generated/conditions/point_ground_motion_from_record.py +++ b/src/otko/core/catalog/generated/conditions/point_ground_motion_from_record.py @@ -17,11 +17,10 @@ from pydantic import BaseModel, ConfigDict, Field class PointGroundMotionFromRecordSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Ground_Motion_from_Record', - 'x-book': 'Loads', + "x-gid-name": "Point_Ground_Motion_from_Record", + "x-book": "Loads", } ) - record_file: str = 'Ground_Motion_Record' - ground_motion_direction: Literal['Ux', 'Uy', 'Uz', 'Rx', 'Ry', 'Rz'] = 'Ux' - + record_file: str = "Ground_Motion_Record" + ground_motion_direction: Literal["Ux", "Uy", "Uz", "Rx", "Ry", "Rz"] = "Ux" diff --git a/src/otko/core/catalog/generated/conditions/point_mass.py b/src/otko/core/catalog/generated/conditions/point_mass.py index d4143f8..5afb059 100644 --- a/src/otko/core/catalog/generated/conditions/point_mass.py +++ b/src/otko/core/catalog/generated/conditions/point_mass.py @@ -17,16 +17,15 @@ from pydantic import BaseModel, ConfigDict, Field class PointMassSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Mass', - 'x-book': 'Mass/Damping', + "x-gid-name": "Point_Mass", + "x-book": "Mass/Damping", } ) - mass_dof_values: Literal[':'] = ':' - x_direction: str = '0.0ton' # TODO: unit-aware type - y_direction: str = '0.0ton' # TODO: unit-aware type - z_direction: str = '0.0ton' # TODO: unit-aware type - x_rotation: str = '0.0ton' # TODO: unit-aware type - y_rotation: str = '0.0ton' # TODO: unit-aware type - z_rotation: str = '0.0ton' # TODO: unit-aware type - + mass_dof_values: Literal[":"] = ":" + x_direction: str = "0.0ton" # TODO: unit-aware type + y_direction: str = "0.0ton" # TODO: unit-aware type + z_direction: str = "0.0ton" # TODO: unit-aware type + x_rotation: str = "0.0ton" # TODO: unit-aware type + y_rotation: str = "0.0ton" # TODO: unit-aware type + z_rotation: str = "0.0ton" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/conditions/point_rayleigh_damping_nodes.py b/src/otko/core/catalog/generated/conditions/point_rayleigh_damping_nodes.py index 24dd864..70155a1 100644 --- a/src/otko/core/catalog/generated/conditions/point_rayleigh_damping_nodes.py +++ b/src/otko/core/catalog/generated/conditions/point_rayleigh_damping_nodes.py @@ -15,8 +15,8 @@ from pydantic import BaseModel, ConfigDict, Field class PointRayleighDampingNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Rayleigh_Damping-Nodes', - 'x-book': 'Mass/Damping', + "x-gid-name": "Point_Rayleigh_Damping-Nodes", + "x-book": "Mass/Damping", } ) @@ -25,4 +25,3 @@ class PointRayleighDampingNodesSpec(BaseModel): betak: float = 0.0 betakinit: float = 0.0 betakcomm: float = 0.0 - diff --git a/src/otko/core/catalog/generated/conditions/point_restraints.py b/src/otko/core/catalog/generated/conditions/point_restraints.py index ea4fbd6..4a45da1 100644 --- a/src/otko/core/catalog/generated/conditions/point_restraints.py +++ b/src/otko/core/catalog/generated/conditions/point_restraints.py @@ -17,290 +17,290 @@ from pydantic import BaseModel, ConfigDict, Field class PointRestraintsSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Restraints', - 'x-book': 'Restraints', - 'dependencies': [ + "x-gid-name": "Point_Restraints", + "x-book": "Restraints", + "dependencies": [ { - 'field': 'fast_restraints', - 'gid_name': 'Fast_restraints', - 'rules': [ + "field": "fast_restraints", + "gid_name": "Fast_restraints", + "rules": [ { - 'trigger': 'Fix_XYZ', - 'actions': [ + "trigger": "Fix_XYZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "X-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "1", }, ], }, { - 'trigger': 'Fix_XY', - 'actions': [ + "trigger": "Fix_XY", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Fix_XZ', - 'actions': [ + "trigger": "Fix_XZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Fix_YZ', - 'actions': [ + "trigger": "Fix_YZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "X-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '0', + "action": "RESTORE", + "field": "X-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Pin_XYZ', - 'actions': [ + "trigger": "Pin_XYZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Pin_XY', - 'actions': [ + "trigger": "Pin_XY", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Pin_XZ', - 'actions': [ + "trigger": "Pin_XZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Pin_YZ', - 'actions': [ + "trigger": "Pin_YZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '0', + "action": "RESTORE", + "field": "X-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, @@ -310,12 +310,13 @@ class PointRestraintsSpec(BaseModel): } ) - x_translation: Literal['1', '0'] = '1' - y_translation: Literal['1', '0'] = '1' - z_translation: Literal['1', '0'] = '1' - x_rotation: Literal['1', '0'] = '1' - y_rotation: Literal['1', '0'] = '1' - z_rotation: Literal['1', '0'] = '1' - fluid_pressure: Literal['1', '0'] = '0' - fast_restraints: Literal['Fix_XYZ', 'Fix_XY', 'Fix_XZ', 'Fix_YZ', 'Pin_XYZ', 'Pin_XY', 'Pin_XZ', 'Pin_YZ'] = 'Fix_XYZ' - + x_translation: Literal["1", "0"] = "1" + y_translation: Literal["1", "0"] = "1" + z_translation: Literal["1", "0"] = "1" + x_rotation: Literal["1", "0"] = "1" + y_rotation: Literal["1", "0"] = "1" + z_rotation: Literal["1", "0"] = "1" + fluid_pressure: Literal["1", "0"] = "0" + fast_restraints: Literal[ + "Fix_XYZ", "Fix_XY", "Fix_XZ", "Fix_YZ", "Pin_XYZ", "Pin_XY", "Pin_XZ", "Pin_YZ" + ] = "Fix_XYZ" diff --git a/src/otko/core/catalog/generated/conditions/point_rigid_diaphragm_master_node.py b/src/otko/core/catalog/generated/conditions/point_rigid_diaphragm_master_node.py index 1996c5f..3bdb1f3 100644 --- a/src/otko/core/catalog/generated/conditions/point_rigid_diaphragm_master_node.py +++ b/src/otko/core/catalog/generated/conditions/point_rigid_diaphragm_master_node.py @@ -15,10 +15,9 @@ from pydantic import BaseModel, ConfigDict, Field class PointRigidDiaphragmMasterNodeSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Rigid_diaphragm_master_node', - 'x-book': 'Constraints', + "x-gid-name": "Point_Rigid_diaphragm_master_node", + "x-book": "Constraints", } ) rigid_diaphragm_id: int = 1 - diff --git a/src/otko/core/catalog/generated/conditions/point_rigid_diaphragm_slave_nodes.py b/src/otko/core/catalog/generated/conditions/point_rigid_diaphragm_slave_nodes.py index 115514d..bc69de3 100644 --- a/src/otko/core/catalog/generated/conditions/point_rigid_diaphragm_slave_nodes.py +++ b/src/otko/core/catalog/generated/conditions/point_rigid_diaphragm_slave_nodes.py @@ -17,11 +17,10 @@ from pydantic import BaseModel, ConfigDict, Field class PointRigidDiaphragmSlaveNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Rigid_diaphragm_slave_nodes', - 'x-book': 'Constraints', + "x-gid-name": "Point_Rigid_diaphragm_slave_nodes", + "x-book": "Constraints", } ) rigid_diaphragm_id: int = 1 - rigid_plane: Literal['XY', 'YZ', 'ZX'] = 'XY' - + rigid_plane: Literal["XY", "YZ", "ZX"] = "XY" diff --git a/src/otko/core/catalog/generated/conditions/point_rigid_link_master_node.py b/src/otko/core/catalog/generated/conditions/point_rigid_link_master_node.py index 8c0bad6..cb56d02 100644 --- a/src/otko/core/catalog/generated/conditions/point_rigid_link_master_node.py +++ b/src/otko/core/catalog/generated/conditions/point_rigid_link_master_node.py @@ -15,10 +15,9 @@ from pydantic import BaseModel, ConfigDict, Field class PointRigidLinkMasterNodeSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Rigid_link_master_node', - 'x-book': 'Constraints', + "x-gid-name": "Point_Rigid_link_master_node", + "x-book": "Constraints", } ) rigid_link_id: int = 1 - diff --git a/src/otko/core/catalog/generated/conditions/point_rigid_link_slave_nodes.py b/src/otko/core/catalog/generated/conditions/point_rigid_link_slave_nodes.py index fac5ffd..6d9cdc9 100644 --- a/src/otko/core/catalog/generated/conditions/point_rigid_link_slave_nodes.py +++ b/src/otko/core/catalog/generated/conditions/point_rigid_link_slave_nodes.py @@ -17,11 +17,12 @@ from pydantic import BaseModel, ConfigDict, Field class PointRigidLinkSlaveNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Rigid_link_slave_nodes', - 'x-book': 'Constraints', + "x-gid-name": "Point_Rigid_link_slave_nodes", + "x-book": "Constraints", } ) - rigid_link_id: int = Field(1, description='Give a positive integer number for body constraint group.') - rigid_link_type: Literal['Bar', 'Beam'] = 'Beam' - + rigid_link_id: int = Field( + 1, description="Give a positive integer number for body constraint group." + ) + rigid_link_type: Literal["Bar", "Beam"] = "Beam" diff --git a/src/otko/core/catalog/generated/conditions/point_sine_ground_motion.py b/src/otko/core/catalog/generated/conditions/point_sine_ground_motion.py index 7be402d..aa82b6f 100644 --- a/src/otko/core/catalog/generated/conditions/point_sine_ground_motion.py +++ b/src/otko/core/catalog/generated/conditions/point_sine_ground_motion.py @@ -17,40 +17,40 @@ from pydantic import BaseModel, ConfigDict, Field class PointSineGroundMotionSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_Sine_Ground_Motion', - 'x-book': 'Loads', - 'dependencies': [ + "x-gid-name": "Point_Sine_Ground_Motion", + "x-book": "Loads", + "dependencies": [ { - 'field': 'type', - 'gid_name': 'Type', - 'rules': [ + "field": "type", + "gid_name": "Type", + "rules": [ { - 'trigger': 'Acceleration', - 'actions': [ + "trigger": "Acceleration", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Acceleration_Amplitude', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Acceleration_Amplitude", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_Amplitude', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_Amplitude", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Displacement', - 'actions': [ + "trigger": "Displacement", + "actions": [ { - 'action': 'HIDE', - 'field': 'Acceleration_Amplitude', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Acceleration_Amplitude", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Displacement_Amplitude', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Displacement_Amplitude", + "target": "#CURRENT#", }, ], }, @@ -60,11 +60,10 @@ class PointSineGroundMotionSpec(BaseModel): } ) - type: Literal['Acceleration', 'Displacement'] = 'Displacement' - ground_motion_direction: Literal['Ux', 'Uy', 'Uz', 'Rx', 'Ry', 'Rz'] = 'Ux' - acceleration_amplitude: str = '1.0m/s^2' # TODO: unit-aware type - displacement_amplitude: str = '1.0m' # TODO: unit-aware type - period: str = '0.6sec' # TODO: unit-aware type - duration: str = '10sec' # TODO: unit-aware type + type: Literal["Acceleration", "Displacement"] = "Displacement" + ground_motion_direction: Literal["Ux", "Uy", "Uz", "Rx", "Ry", "Rz"] = "Ux" + acceleration_amplitude: str = "1.0m/s^2" # TODO: unit-aware type + displacement_amplitude: str = "1.0m" # TODO: unit-aware type + period: str = "0.6sec" # TODO: unit-aware type + duration: str = "10sec" # TODO: unit-aware type shift: float = 0.0 - diff --git a/src/otko/core/catalog/generated/conditions/point_zerolength.py b/src/otko/core/catalog/generated/conditions/point_zerolength.py index c0aa22e..813ace9 100644 --- a/src/otko/core/catalog/generated/conditions/point_zerolength.py +++ b/src/otko/core/catalog/generated/conditions/point_zerolength.py @@ -17,160 +17,160 @@ from pydantic import BaseModel, ConfigDict, Field class PointZerolengthSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Point_ZeroLength', - 'x-book': 'ZeroLength_Elements', - 'dependencies': [ + "x-gid-name": "Point_ZeroLength", + "x-book": "ZeroLength_Elements", + "dependencies": [ { - 'field': 'activate_ux', - 'gid_name': 'Activate_Ux', - 'rules': [ + "field": "activate_ux", + "gid_name": "Activate_Ux", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Ux_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Ux_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Ux_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Ux_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_uy', - 'gid_name': 'Activate_Uy', - 'rules': [ + "field": "activate_uy", + "gid_name": "Activate_Uy", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Uy_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Uy_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Uy_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Uy_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_uz', - 'gid_name': 'Activate_Uz', - 'rules': [ + "field": "activate_uz", + "gid_name": "Activate_Uz", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Uz_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Uz_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Uz_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Uz_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_rx', - 'gid_name': 'Activate_Rx', - 'rules': [ + "field": "activate_rx", + "gid_name": "Activate_Rx", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Rx_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Rx_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Rx_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rx_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_ry', - 'gid_name': 'Activate_Ry', - 'rules': [ + "field": "activate_ry", + "gid_name": "Activate_Ry", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Ry_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Ry_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Ry_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Ry_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_rz', - 'gid_name': 'Activate_Rz', - 'rules': [ + "field": "activate_rz", + "gid_name": "Activate_Rz", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Rz_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Rz_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Rz_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rz_material", + "target": "#CURRENT#", }, ], }, @@ -181,16 +181,15 @@ class PointZerolengthSpec(BaseModel): ) zerolength_id: int = 1 - activate_ux: Literal['0', '1'] = '1' - ux_material: str = 'Elastic' - activate_uy: Literal['0', '1'] = '0' - uy_material: str = 'Elastic' - activate_uz: Literal['0', '1'] = '0' - uz_material: str = 'Elastic' - activate_rx: Literal['0', '1'] = '0' - rx_material: str = 'Elastic' - activate_ry: Literal['0', '1'] = '0' - ry_material: str = 'Elastic' - activate_rz: Literal['0', '1'] = '0' - rz_material: str = 'Elastic' - + activate_ux: Literal["0", "1"] = "1" + ux_material: str = "Elastic" + activate_uy: Literal["0", "1"] = "0" + uy_material: str = "Elastic" + activate_uz: Literal["0", "1"] = "0" + uz_material: str = "Elastic" + activate_rx: Literal["0", "1"] = "0" + rx_material: str = "Elastic" + activate_ry: Literal["0", "1"] = "0" + ry_material: str = "Elastic" + activate_rz: Literal["0", "1"] = "0" + rz_material: str = "Elastic" diff --git a/src/otko/core/catalog/generated/conditions/surface_displacements.py b/src/otko/core/catalog/generated/conditions/surface_displacements.py index a2c2a1b..c9c7b25 100644 --- a/src/otko/core/catalog/generated/conditions/surface_displacements.py +++ b/src/otko/core/catalog/generated/conditions/surface_displacements.py @@ -15,15 +15,14 @@ from pydantic import BaseModel, ConfigDict, Field class SurfaceDisplacementsSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Surface_Displacements', - 'x-book': 'Loads', + "x-gid-name": "Surface_Displacements", + "x-book": "Loads", } ) - x_displacement: str = '0.0m' # TODO: unit-aware type - y_displacement: str = '0.0m' # TODO: unit-aware type - z_displacement: str = '0.0m' # TODO: unit-aware type - x_rotation: str = '0.0 rad' # TODO: unit-aware type - y_rotation: str = '0.0 rad' # TODO: unit-aware type - z_rotation: str = '0.0 rad' # TODO: unit-aware type - + x_displacement: str = "0.0m" # TODO: unit-aware type + y_displacement: str = "0.0m" # TODO: unit-aware type + z_displacement: str = "0.0m" # TODO: unit-aware type + x_rotation: str = "0.0 rad" # TODO: unit-aware type + y_rotation: str = "0.0 rad" # TODO: unit-aware type + z_rotation: str = "0.0 rad" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/conditions/surface_forces.py b/src/otko/core/catalog/generated/conditions/surface_forces.py index ac53c76..dbf8ea9 100644 --- a/src/otko/core/catalog/generated/conditions/surface_forces.py +++ b/src/otko/core/catalog/generated/conditions/surface_forces.py @@ -15,15 +15,14 @@ from pydantic import BaseModel, ConfigDict, Field class SurfaceForcesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Surface_Forces', - 'x-book': 'Loads', + "x-gid-name": "Surface_Forces", + "x-book": "Loads", } ) - x_force: str = '0.0 kN' # TODO: unit-aware type - y_force: str = '0.0 kN' # TODO: unit-aware type - z_force: str = '0.0 kN' # TODO: unit-aware type - x_moment: str = '0.0 kNm' # TODO: unit-aware type - y_moment: str = '0.0 kNm' # TODO: unit-aware type - z_moment: str = '0.0 kNm' # TODO: unit-aware type - + x_force: str = "0.0 kN" # TODO: unit-aware type + y_force: str = "0.0 kN" # TODO: unit-aware type + z_force: str = "0.0 kN" # TODO: unit-aware type + x_moment: str = "0.0 kNm" # TODO: unit-aware type + y_moment: str = "0.0 kNm" # TODO: unit-aware type + z_moment: str = "0.0 kNm" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/conditions/surface_mass.py b/src/otko/core/catalog/generated/conditions/surface_mass.py index b7c08ec..1001fb0 100644 --- a/src/otko/core/catalog/generated/conditions/surface_mass.py +++ b/src/otko/core/catalog/generated/conditions/surface_mass.py @@ -17,16 +17,15 @@ from pydantic import BaseModel, ConfigDict, Field class SurfaceMassSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Surface_Mass', - 'x-book': 'Mass/Damping', + "x-gid-name": "Surface_Mass", + "x-book": "Mass/Damping", } ) - mass_dof_values: Literal[':'] = ':' - x_direction: str = '0.0ton' # TODO: unit-aware type - y_direction: str = '0.0ton' # TODO: unit-aware type - z_direction: str = '0.0ton' # TODO: unit-aware type - x_rotation: str = '0.0ton' # TODO: unit-aware type - y_rotation: str = '0.0ton' # TODO: unit-aware type - z_rotation: str = '0.0ton' # TODO: unit-aware type - + mass_dof_values: Literal[":"] = ":" + x_direction: str = "0.0ton" # TODO: unit-aware type + y_direction: str = "0.0ton" # TODO: unit-aware type + z_direction: str = "0.0ton" # TODO: unit-aware type + x_rotation: str = "0.0ton" # TODO: unit-aware type + y_rotation: str = "0.0ton" # TODO: unit-aware type + z_rotation: str = "0.0ton" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/conditions/surface_rayleigh_damping_elements.py b/src/otko/core/catalog/generated/conditions/surface_rayleigh_damping_elements.py index e33f80f..d32d2aa 100644 --- a/src/otko/core/catalog/generated/conditions/surface_rayleigh_damping_elements.py +++ b/src/otko/core/catalog/generated/conditions/surface_rayleigh_damping_elements.py @@ -15,8 +15,8 @@ from pydantic import BaseModel, ConfigDict, Field class SurfaceRayleighDampingElementsSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Surface_Rayleigh_Damping-Elements', - 'x-book': 'Mass/Damping', + "x-gid-name": "Surface_Rayleigh_Damping-Elements", + "x-book": "Mass/Damping", } ) @@ -25,4 +25,3 @@ class SurfaceRayleighDampingElementsSpec(BaseModel): betak: float = 0.0 betakinit: float = 0.0 betakcomm: float = 0.0 - diff --git a/src/otko/core/catalog/generated/conditions/surface_rayleigh_damping_nodes.py b/src/otko/core/catalog/generated/conditions/surface_rayleigh_damping_nodes.py index d086691..f3ed8c9 100644 --- a/src/otko/core/catalog/generated/conditions/surface_rayleigh_damping_nodes.py +++ b/src/otko/core/catalog/generated/conditions/surface_rayleigh_damping_nodes.py @@ -15,8 +15,8 @@ from pydantic import BaseModel, ConfigDict, Field class SurfaceRayleighDampingNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Surface_Rayleigh_Damping-Nodes', - 'x-book': 'Mass/Damping', + "x-gid-name": "Surface_Rayleigh_Damping-Nodes", + "x-book": "Mass/Damping", } ) @@ -25,4 +25,3 @@ class SurfaceRayleighDampingNodesSpec(BaseModel): betak: float = 0.0 betakinit: float = 0.0 betakcomm: float = 0.0 - diff --git a/src/otko/core/catalog/generated/conditions/surface_restraints.py b/src/otko/core/catalog/generated/conditions/surface_restraints.py index 7c40c31..c88e9d1 100644 --- a/src/otko/core/catalog/generated/conditions/surface_restraints.py +++ b/src/otko/core/catalog/generated/conditions/surface_restraints.py @@ -17,290 +17,290 @@ from pydantic import BaseModel, ConfigDict, Field class SurfaceRestraintsSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Surface_Restraints', - 'x-book': 'Restraints', - 'dependencies': [ + "x-gid-name": "Surface_Restraints", + "x-book": "Restraints", + "dependencies": [ { - 'field': 'fast_restraints', - 'gid_name': 'Fast_restraints', - 'rules': [ + "field": "fast_restraints", + "gid_name": "Fast_restraints", + "rules": [ { - 'trigger': 'Fix_XYZ', - 'actions': [ + "trigger": "Fix_XYZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "X-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "1", }, ], }, { - 'trigger': 'Fix_XY', - 'actions': [ + "trigger": "Fix_XY", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Fix_XZ', - 'actions': [ + "trigger": "Fix_XZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Fix_YZ', - 'actions': [ + "trigger": "Fix_YZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '1', + "action": "RESTORE", + "field": "X-Rotation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '0', + "action": "RESTORE", + "field": "X-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Pin_XYZ', - 'actions': [ + "trigger": "Pin_XYZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Pin_XY', - 'actions': [ + "trigger": "Pin_XY", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Pin_XZ', - 'actions': [ + "trigger": "Pin_XZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '1', + "action": "RESTORE", + "field": "X-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, { - 'trigger': 'Pin_YZ', - 'actions': [ + "trigger": "Pin_YZ", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Y-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Y-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'Z-Translation', - 'target': '1', + "action": "RESTORE", + "field": "Z-Translation", + "target": "1", }, { - 'action': 'RESTORE', - 'field': 'X-Translation', - 'target': '0', + "action": "RESTORE", + "field": "X-Translation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'X-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "X-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Y-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Y-Rotation", + "target": "0", }, { - 'action': 'RESTORE', - 'field': 'Z-Rotation', - 'target': '0', + "action": "RESTORE", + "field": "Z-Rotation", + "target": "0", }, ], }, @@ -310,12 +310,13 @@ class SurfaceRestraintsSpec(BaseModel): } ) - x_translation: Literal['1', '0'] = '1' - y_translation: Literal['1', '0'] = '1' - z_translation: Literal['1', '0'] = '1' - x_rotation: Literal['1', '0'] = '1' - y_rotation: Literal['1', '0'] = '1' - z_rotation: Literal['1', '0'] = '1' - fluid_pressure: Literal['1', '0'] = '0' - fast_restraints: Literal['Fix_XYZ', 'Fix_XY', 'Fix_XZ', 'Fix_YZ', 'Pin_XYZ', 'Pin_XY', 'Pin_XZ', 'Pin_YZ'] = 'Fix_XYZ' - + x_translation: Literal["1", "0"] = "1" + y_translation: Literal["1", "0"] = "1" + z_translation: Literal["1", "0"] = "1" + x_rotation: Literal["1", "0"] = "1" + y_rotation: Literal["1", "0"] = "1" + z_rotation: Literal["1", "0"] = "1" + fluid_pressure: Literal["1", "0"] = "0" + fast_restraints: Literal[ + "Fix_XYZ", "Fix_XY", "Fix_XZ", "Fix_YZ", "Pin_XYZ", "Pin_XY", "Pin_XZ", "Pin_YZ" + ] = "Fix_XYZ" diff --git a/src/otko/core/catalog/generated/conditions/volume_mass.py b/src/otko/core/catalog/generated/conditions/volume_mass.py index 2281556..a527d58 100644 --- a/src/otko/core/catalog/generated/conditions/volume_mass.py +++ b/src/otko/core/catalog/generated/conditions/volume_mass.py @@ -17,16 +17,15 @@ from pydantic import BaseModel, ConfigDict, Field class VolumeMassSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Volume_Mass', - 'x-book': 'Mass/Damping', + "x-gid-name": "Volume_Mass", + "x-book": "Mass/Damping", } ) - mass_dof_values: Literal[':'] = ':' - x_direction: str = '0.0ton' # TODO: unit-aware type - y_direction: str = '0.0ton' # TODO: unit-aware type - z_direction: str = '0.0ton' # TODO: unit-aware type - x_rotation: str = '0.0ton' # TODO: unit-aware type - y_rotation: str = '0.0ton' # TODO: unit-aware type - z_rotation: str = '0.0ton' # TODO: unit-aware type - + mass_dof_values: Literal[":"] = ":" + x_direction: str = "0.0ton" # TODO: unit-aware type + y_direction: str = "0.0ton" # TODO: unit-aware type + z_direction: str = "0.0ton" # TODO: unit-aware type + x_rotation: str = "0.0ton" # TODO: unit-aware type + y_rotation: str = "0.0ton" # TODO: unit-aware type + z_rotation: str = "0.0ton" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/conditions/volume_rayleigh_damping_elements.py b/src/otko/core/catalog/generated/conditions/volume_rayleigh_damping_elements.py index 48a38d0..6f25b2f 100644 --- a/src/otko/core/catalog/generated/conditions/volume_rayleigh_damping_elements.py +++ b/src/otko/core/catalog/generated/conditions/volume_rayleigh_damping_elements.py @@ -15,8 +15,8 @@ from pydantic import BaseModel, ConfigDict, Field class VolumeRayleighDampingElementsSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Volume_Rayleigh_Damping-Elements', - 'x-book': 'Mass/Damping', + "x-gid-name": "Volume_Rayleigh_Damping-Elements", + "x-book": "Mass/Damping", } ) @@ -25,4 +25,3 @@ class VolumeRayleighDampingElementsSpec(BaseModel): betak: float = 0.0 betakinit: float = 0.0 betakcomm: float = 0.0 - diff --git a/src/otko/core/catalog/generated/conditions/volume_rayleigh_damping_nodes.py b/src/otko/core/catalog/generated/conditions/volume_rayleigh_damping_nodes.py index eb57cdd..c268826 100644 --- a/src/otko/core/catalog/generated/conditions/volume_rayleigh_damping_nodes.py +++ b/src/otko/core/catalog/generated/conditions/volume_rayleigh_damping_nodes.py @@ -15,8 +15,8 @@ from pydantic import BaseModel, ConfigDict, Field class VolumeRayleighDampingNodesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Volume_Rayleigh_Damping-Nodes', - 'x-book': 'Mass/Damping', + "x-gid-name": "Volume_Rayleigh_Damping-Nodes", + "x-book": "Mass/Damping", } ) @@ -25,4 +25,3 @@ class VolumeRayleighDampingNodesSpec(BaseModel): betak: float = 0.0 betakinit: float = 0.0 betakcomm: float = 0.0 - diff --git a/src/otko/core/catalog/generated/conditions/zerolength.py b/src/otko/core/catalog/generated/conditions/zerolength.py index 361c6c8..7b59d3b 100644 --- a/src/otko/core/catalog/generated/conditions/zerolength.py +++ b/src/otko/core/catalog/generated/conditions/zerolength.py @@ -17,160 +17,160 @@ from pydantic import BaseModel, ConfigDict, Field class ZerolengthSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'ZeroLength', - 'x-book': 'Old_Conditions', - 'dependencies': [ + "x-gid-name": "ZeroLength", + "x-book": "Old_Conditions", + "dependencies": [ { - 'field': 'activate_ux', - 'gid_name': 'Activate_Ux', - 'rules': [ + "field": "activate_ux", + "gid_name": "Activate_Ux", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Ux_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Ux_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Ux_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Ux_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_uy', - 'gid_name': 'Activate_Uy', - 'rules': [ + "field": "activate_uy", + "gid_name": "Activate_Uy", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Uy_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Uy_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Uy_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Uy_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_uz', - 'gid_name': 'Activate_Uz', - 'rules': [ + "field": "activate_uz", + "gid_name": "Activate_Uz", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Uz_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Uz_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Uz_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Uz_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_rx', - 'gid_name': 'Activate_Rx', - 'rules': [ + "field": "activate_rx", + "gid_name": "Activate_Rx", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Rx_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Rx_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Rx_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rx_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_ry', - 'gid_name': 'Activate_Ry', - 'rules': [ + "field": "activate_ry", + "gid_name": "Activate_Ry", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Ry_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Ry_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Ry_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Ry_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_rz', - 'gid_name': 'Activate_Rz', - 'rules': [ + "field": "activate_rz", + "gid_name": "Activate_Rz", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Rz_material', - 'target': '#CURRENT#', + "action": "SET", + "field": "Rz_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Rz_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rz_material", + "target": "#CURRENT#", }, ], }, @@ -181,16 +181,15 @@ class ZerolengthSpec(BaseModel): ) zerolength_id_number: int = 1 - activate_ux: Literal['0', '1'] = '1' - ux_material: str = 'Elastic' - activate_uy: Literal['0', '1'] = '0' - uy_material: str = 'Elastic' - activate_uz: Literal['0', '1'] = '0' - uz_material: str = 'Elastic' - activate_rx: Literal['0', '1'] = '0' - rx_material: str = 'Elastic' - activate_ry: Literal['0', '1'] = '0' - ry_material: str = 'Elastic' - activate_rz: Literal['0', '1'] = '0' - rz_material: str = 'Elastic' - + activate_ux: Literal["0", "1"] = "1" + ux_material: str = "Elastic" + activate_uy: Literal["0", "1"] = "0" + uy_material: str = "Elastic" + activate_uz: Literal["0", "1"] = "0" + uz_material: str = "Elastic" + activate_rx: Literal["0", "1"] = "0" + rx_material: str = "Elastic" + activate_ry: Literal["0", "1"] = "0" + ry_material: str = "Elastic" + activate_rz: Literal["0", "1"] = "0" + rz_material: str = "Elastic" diff --git a/src/otko/core/catalog/generated/contact.py b/src/otko/core/catalog/generated/contact.py index 040344a..6e685b9 100644 --- a/src/otko/core/catalog/generated/contact.py +++ b/src/otko/core/catalog/generated/contact.py @@ -17,14 +17,13 @@ from pydantic import BaseModel, ConfigDict, Field class ContactSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Contact', - 'x-book': 'Multidimensional_(nD)_Materials', + "x-gid-name": "Contact", + "x-book": "Multidimensional_(nD)_Materials", } ) - material: Literal['Contact'] = 'Contact' + material: Literal["Contact"] = "Contact" frictional_coefficient: float = 0.1 - stiffness_parameter: str = '1000.0kN/m' # TODO: unit-aware type + stiffness_parameter: str = "1000.0kN/m" # TODO: unit-aware type cohesive_intercept: float = 0.0 - tensile_strength: str = '0.0kN' # TODO: unit-aware type - + tensile_strength: str = "0.0kN" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/corotational_truss.py b/src/otko/core/catalog/generated/corotational_truss.py index 2638cb1..04bdb57 100644 --- a/src/otko/core/catalog/generated/corotational_truss.py +++ b/src/otko/core/catalog/generated/corotational_truss.py @@ -17,196 +17,196 @@ from pydantic import BaseModel, ConfigDict, Field class CorotationalTrussSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Corotational_Truss', - 'x-book': 'Truss_Elements', - 'dependencies': [ + "x-gid-name": "Corotational_Truss", + "x-book": "Truss_Elements", + "dependencies": [ { - 'field': 'cross_section', - 'gid_name': 'Cross_section', - 'rules': [ + "field": "cross_section", + "gid_name": "Cross_section", + "rules": [ { - 'trigger': 'Rectangular', - 'actions': [ + "trigger": "Rectangular", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_Bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_Bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Circular', - 'actions': [ + "trigger": "Circular", + "actions": [ { - 'action': 'HIDE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_Bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_Bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Tee', - 'actions': [ + "trigger": "Tee", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_Bw', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_Bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'General', - 'actions': [ + "trigger": "General", + "actions": [ { - 'action': 'HIDE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_Bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_Bw", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Area_A", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'set_modification_factors', - 'gid_name': 'Set_modification_factors', - 'rules': [ + "field": "set_modification_factors", + "gid_name": "Set_modification_factors", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'mod._A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._A", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'mod._A', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._A", + "target": "#CURRENT#", }, ], }, @@ -216,21 +216,22 @@ class CorotationalTrussSpec(BaseModel): } ) - element_type: Literal['CorotationalTruss'] = 'CorotationalTruss' - compatible_materials: Literal['_'] = '_' - material: str = Field('Elastic', description='Choose one existing material to use its parameters.') - mass_density: str = '0.0ton/m^3' # TODO: unit-aware type - consider_consistent_mass_matrix: Literal['1', '0'] = '0' - include_rayleigh_damping: Literal['1', '0'] = '0' - weight_density: str = '0.0 kN/m^3' # TODO: unit-aware type - cross_section: Literal['Rectangular', 'Tee', 'Circular', 'General'] = 'General' - height_h: str = '0.5m' # TODO: unit-aware type - width_b: str = '0.3m' # TODO: unit-aware type - diameter_d: str = '0.5m' # TODO: unit-aware type - width_bf: str = '0.4m' # TODO: unit-aware type - height_hf: str = '0.15m' # TODO: unit-aware type - width_bw: str = '0.25m' # TODO: unit-aware type - area_a: str = '1 m^2' # TODO: unit-aware type - set_modification_factors: Literal['0', '1'] = '0' + element_type: Literal["CorotationalTruss"] = "CorotationalTruss" + compatible_materials: Literal["_"] = "_" + material: str = Field( + "Elastic", description="Choose one existing material to use its parameters." + ) + mass_density: str = "0.0ton/m^3" # TODO: unit-aware type + consider_consistent_mass_matrix: Literal["1", "0"] = "0" + include_rayleigh_damping: Literal["1", "0"] = "0" + weight_density: str = "0.0 kN/m^3" # TODO: unit-aware type + cross_section: Literal["Rectangular", "Tee", "Circular", "General"] = "General" + height_h: str = "0.5m" # TODO: unit-aware type + width_b: str = "0.3m" # TODO: unit-aware type + diameter_d: str = "0.5m" # TODO: unit-aware type + width_bf: str = "0.4m" # TODO: unit-aware type + height_hf: str = "0.15m" # TODO: unit-aware type + width_bw: str = "0.25m" # TODO: unit-aware type + area_a: str = "1 m^2" # TODO: unit-aware type + set_modification_factors: Literal["0", "1"] = "0" mod_a: int = 1 - diff --git a/src/otko/core/catalog/generated/damage2p.py b/src/otko/core/catalog/generated/damage2p.py index 77e3c1a..58bf76b 100644 --- a/src/otko/core/catalog/generated/damage2p.py +++ b/src/otko/core/catalog/generated/damage2p.py @@ -17,21 +17,22 @@ from pydantic import BaseModel, ConfigDict, Field class Damage2pSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Damage2p', - 'x-book': 'Multidimensional_(nD)_Materials', + "x-gid-name": "Damage2p", + "x-book": "Multidimensional_(nD)_Materials", } ) - material: Literal['Damage2p'] = 'Damage2p' - concrete_compressive_strength: str = '-20MPa' # TODO: unit-aware type + material: Literal["Damage2p"] = "Damage2p" + concrete_compressive_strength: str = "-20MPa" # TODO: unit-aware type calculate_default_values_update_to_apply_cb_1_0: int = 1 - young_modulus: str = '21.243GPa' # TODO: unit-aware type - concrete_tensile_strength: str = '2.0MPa' # TODO: unit-aware type + young_modulus: str = "21.243GPa" # TODO: unit-aware type + concrete_tensile_strength: str = "2.0MPa" # TODO: unit-aware type poisson_coefficient: float = 0.15 - tension_fracture_energy_density: str = '3.465e-004GPa' # TODO: unit-aware type - comp_fracture_energy_density: str = '1.177e-001GPa' # TODO: unit-aware type + tension_fracture_energy_density: str = "3.465e-004GPa" # TODO: unit-aware type + comp_fracture_energy_density: str = "1.177e-001GPa" # TODO: unit-aware type parameter_of_plastic_volume_change: float = 0.20 - linear_hardening_parameter: str = '5.311GPa' # TODO: unit-aware type + linear_hardening_parameter: str = "5.311GPa" # TODO: unit-aware type isotropic_kinematic_hardening_ratio: float = 0.50 - computational_stiffness_matrix: Literal['Computational_tangent', 'Damaged_secant_stiffness'] = 'Computational_tangent' - + computational_stiffness_matrix: Literal["Computational_tangent", "Damaged_secant_stiffness"] = ( + "Computational_tangent" + ) diff --git a/src/otko/core/catalog/generated/displacement_based_beam_column.py b/src/otko/core/catalog/generated/displacement_based_beam_column.py index e23893a..1ccfce7 100644 --- a/src/otko/core/catalog/generated/displacement_based_beam_column.py +++ b/src/otko/core/catalog/generated/displacement_based_beam_column.py @@ -17,18 +17,19 @@ from pydantic import BaseModel, ConfigDict, Field class DisplacementBasedBeamColumnSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Displacement-Based_Beam-Column', - 'x-book': 'Beam-Column_Elements', + "x-gid-name": "Displacement-Based_Beam-Column", + "x-book": "Beam-Column_Elements", } ) - element_type: Literal['dispBeamColumn'] = 'dispBeamColumn' - compatible_sections: Literal['_'] = '_' - number_of_integration_points: int = Field(3, description='number of integration points along the element') - the_number_of_integration: Literal['_'] = '_' - to_increase_analysis_accuracy: Literal['_'] = '_' - section: str = 'Fiber' - geometric_transformation: Literal['Linear', 'P-Delta', 'Corotational'] = 'Linear' - mass_density: str = '0.0ton/m^3' # TODO: unit-aware type - weight_density: str = '25 kN/m^3' # TODO: unit-aware type - + element_type: Literal["dispBeamColumn"] = "dispBeamColumn" + compatible_sections: Literal["_"] = "_" + number_of_integration_points: int = Field( + 3, description="number of integration points along the element" + ) + the_number_of_integration: Literal["_"] = "_" + to_increase_analysis_accuracy: Literal["_"] = "_" + section: str = "Fiber" + geometric_transformation: Literal["Linear", "P-Delta", "Corotational"] = "Linear" + mass_density: str = "0.0ton/m^3" # TODO: unit-aware type + weight_density: str = "25 kN/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/elastic.py b/src/otko/core/catalog/generated/elastic.py index 79b6ce1..ad411c7 100644 --- a/src/otko/core/catalog/generated/elastic.py +++ b/src/otko/core/catalog/generated/elastic.py @@ -23,187 +23,187 @@ class CyclicDataRow(BaseModel): class ElasticSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Elastic', - 'x-book': 'Standard_Uniaxial_Materials', - 'dependencies': [ + "x-gid-name": "Elastic", + "x-book": "Standard_Uniaxial_Materials", + "dependencies": [ { - 'field': 'formulation', - 'gid_name': 'Formulation', - 'rules': [ + "field": "formulation", + "gid_name": "Formulation", + "rules": [ { - 'trigger': 'Stress-Strain', - 'actions': [ + "trigger": "Stress-Strain", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Material_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_type", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stiffness_K', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Elastic_modulus_E', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Elastic_modulus_E", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Force-Deformation', - 'actions': [ + "trigger": "Force-Deformation", + "actions": [ { - 'action': 'HIDE', - 'field': 'Material_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Material_type", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Elastic_modulus_E', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Elastic_modulus_E", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Stiffness_K', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Moment-Rotation', - 'actions': [ + "trigger": "Moment-Rotation", + "actions": [ { - 'action': 'HIDE', - 'field': 'Material_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Material_type", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Elastic_modulus_E', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Elastic_modulus_E", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stiffness_K', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'material_type', - 'gid_name': 'Material_type', - 'rules': [ + "field": "material_type", + "gid_name": "Material_type", + "rules": [ { - 'trigger': 'Concrete', - 'actions': [ + "trigger": "Concrete", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Steel', - 'actions': [ + "trigger": "Steel", + "actions": [ { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Steel_grade", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Custom', - 'actions': [ + "trigger": "Custom", + "actions": [ { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -213,16 +213,32 @@ class ElasticSpec(BaseModel): } ) - material: Literal['Elastic'] = Field('Elastic', description='Elastic uniaxial material object') - formulation: Literal['Stress-Strain', 'Force-Deformation', 'Moment-Rotation'] = 'Stress-Strain' - material_type: Literal['Concrete', 'Steel', 'Custom'] = 'Custom' - concrete_class: Literal['C12/15', 'C16/20', 'C20/25', 'C25/30', 'C30/37', 'C35/45', 'C40/50', 'C45/55', 'C50/60', 'C55/67', 'C60/75', 'C70/85', 'C80/95', 'C90/105'] = 'C20/25' - steel_grade: Literal['S235', 'S275', 'S355', 'S450', 'B500'] = 'S235' - elastic_modulus_e: str = Field('30 GPa', description='Young modulus of elasticity E, considering stress-strain diagram') # TODO: unit-aware type - stiffness_k: str = '4000 kN/m' # TODO: unit-aware type - moment_per_rotation_unit: str = '10000 kNm' # TODO: unit-aware type - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + material: Literal["Elastic"] = Field("Elastic", description="Elastic uniaxial material object") + formulation: Literal["Stress-Strain", "Force-Deformation", "Moment-Rotation"] = "Stress-Strain" + material_type: Literal["Concrete", "Steel", "Custom"] = "Custom" + concrete_class: Literal[ + "C12/15", + "C16/20", + "C20/25", + "C25/30", + "C30/37", + "C35/45", + "C40/50", + "C45/55", + "C50/60", + "C55/67", + "C60/75", + "C70/85", + "C80/95", + "C90/105", + ] = "C20/25" + steel_grade: Literal["S235", "S275", "S355", "S450", "B500"] = "S235" + elastic_modulus_e: str = Field( + "30 GPa", description="Young modulus of elasticity E, considering stress-strain diagram" + ) # TODO: unit-aware type + stiffness_k: str = "4000 kN/m" # TODO: unit-aware type + moment_per_rotation_unit: str = "10000 kNm" # TODO: unit-aware type + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/catalog/generated/elastic_beam_column.py b/src/otko/core/catalog/generated/elastic_beam_column.py index 4321f87..0e575f5 100644 --- a/src/otko/core/catalog/generated/elastic_beam_column.py +++ b/src/otko/core/catalog/generated/elastic_beam_column.py @@ -17,286 +17,286 @@ from pydantic import BaseModel, ConfigDict, Field class ElasticBeamColumnSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Elastic_Beam-Column', - 'x-book': 'Beam-Column_Elements', - 'dependencies': [ + "x-gid-name": "Elastic_Beam-Column", + "x-book": "Beam-Column_Elements", + "dependencies": [ { - 'field': 'cross_section', - 'gid_name': 'Cross_section', - 'rules': [ + "field": "cross_section", + "gid_name": "Cross_section", + "rules": [ { - 'trigger': 'Rectangular', - 'actions': [ + "trigger": "Rectangular", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-y_Iyy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-y_Iyy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-z_Izz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-z_Izz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Torsional_moment_of_inertia_Iyz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Torsional_moment_of_inertia_Iyz", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Circular', - 'actions': [ + "trigger": "Circular", + "actions": [ { - 'action': 'HIDE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-y_Iyy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-y_Iyy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-z_Izz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-z_Izz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Torsional_moment_of_inertia_Iyz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Torsional_moment_of_inertia_Iyz", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Tee', - 'actions': [ + "trigger": "Tee", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_bw', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-y_Iyy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-y_Iyy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-z_Izz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-z_Izz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Torsional_moment_of_inertia_Iyz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Torsional_moment_of_inertia_Iyz", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'General', - 'actions': [ + "trigger": "General", + "actions": [ { - 'action': 'HIDE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bw", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Area_A", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_of_inertia_about_local-y_Iyy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_of_inertia_about_local-y_Iyy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_of_inertia_about_local-z_Izz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_of_inertia_about_local-z_Izz", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Torsional_moment_of_inertia_Iyz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Torsional_moment_of_inertia_Iyz", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'set_modification_factors', - 'gid_name': 'Set_modification_factors', - 'rules': [ + "field": "set_modification_factors", + "gid_name": "Set_modification_factors", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'mod._A', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._A", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Iyy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Iyy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Izz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Izz", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Iyz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Iyz", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'mod._A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._A", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Iyy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Iyy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Izz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Izz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Iyz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Iyz", + "target": "#CURRENT#", }, ], }, @@ -306,25 +306,24 @@ class ElasticBeamColumnSpec(BaseModel): } ) - element_type: Literal['ElasticBeamColumn'] = 'ElasticBeamColumn' - compatible_materials: Literal['Elastic_Isotropic'] = 'Elastic_Isotropic' - material_mat_multidimensional_nd_materials_user_materials: str = 'Elastic_Isotropic' - cross_section: Literal['Rectangular', 'Tee', 'Circular', 'General'] = 'Rectangular' - height_h: str = '0.5m' # TODO: unit-aware type - width_b: str = '0.3m' # TODO: unit-aware type - diameter_d: str = '0.5m' # TODO: unit-aware type - width_bf: str = '0.4m' # TODO: unit-aware type - height_hf: str = '0.15m' # TODO: unit-aware type - width_bw: str = '0.25m' # TODO: unit-aware type - area_a: str = '1.0 m^2' # TODO: unit-aware type - moment_of_inertia_about_local_y_iyy: str = '0.5 m^4' # TODO: unit-aware type - moment_of_inertia_about_local_z_izz: str = '0.5 m^4' # TODO: unit-aware type - torsional_moment_of_inertia_iyz: str = '1.0 m^4' # TODO: unit-aware type - set_modification_factors: Literal['1', '0'] = '0' + element_type: Literal["ElasticBeamColumn"] = "ElasticBeamColumn" + compatible_materials: Literal["Elastic_Isotropic"] = "Elastic_Isotropic" + material_mat_multidimensional_nd_materials_user_materials: str = "Elastic_Isotropic" + cross_section: Literal["Rectangular", "Tee", "Circular", "General"] = "Rectangular" + height_h: str = "0.5m" # TODO: unit-aware type + width_b: str = "0.3m" # TODO: unit-aware type + diameter_d: str = "0.5m" # TODO: unit-aware type + width_bf: str = "0.4m" # TODO: unit-aware type + height_hf: str = "0.15m" # TODO: unit-aware type + width_bw: str = "0.25m" # TODO: unit-aware type + area_a: str = "1.0 m^2" # TODO: unit-aware type + moment_of_inertia_about_local_y_iyy: str = "0.5 m^4" # TODO: unit-aware type + moment_of_inertia_about_local_z_izz: str = "0.5 m^4" # TODO: unit-aware type + torsional_moment_of_inertia_iyz: str = "1.0 m^4" # TODO: unit-aware type + set_modification_factors: Literal["1", "0"] = "0" mod_a: int = 1 mod_iyy: int = 1 mod_izz: int = 1 mod_iyz: int = 1 - geometric_transformation: Literal['Linear', 'P-Delta', 'Corotational'] = 'Linear' - weight_density: str = '25 kN/m^3' # TODO: unit-aware type - + geometric_transformation: Literal["Linear", "P-Delta", "Corotational"] = "Linear" + weight_density: str = "25 kN/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/elastic_isotropic.py b/src/otko/core/catalog/generated/elastic_isotropic.py index 8443902..86cbeb5 100644 --- a/src/otko/core/catalog/generated/elastic_isotropic.py +++ b/src/otko/core/catalog/generated/elastic_isotropic.py @@ -17,40 +17,40 @@ from pydantic import BaseModel, ConfigDict, Field class ElasticIsotropicSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Elastic_Isotropic', - 'x-book': 'Multidimensional_(nD)_Materials', - 'dependencies': [ + "x-gid-name": "Elastic_Isotropic", + "x-book": "Multidimensional_(nD)_Materials", + "dependencies": [ { - 'field': 'material_type', - 'gid_name': 'Material_type', - 'rules': [ + "field": "material_type", + "gid_name": "Material_type", + "rules": [ { - 'trigger': 'Concrete', - 'actions': [ + "trigger": "Concrete", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Concrete_class", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Steel', - 'actions': [ + "trigger": "Steel", + "actions": [ { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Custom', - 'actions': [ + "trigger": "Custom", + "actions": [ { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, ], }, @@ -60,10 +60,24 @@ class ElasticIsotropicSpec(BaseModel): } ) - material: Literal['ElasticIsotropic'] = 'ElasticIsotropic' - material_type: Literal['Concrete', 'Steel', 'Custom'] = 'Custom' - concrete_class: Literal['C12/15', 'C16/20', 'C20/25', 'C25/30', 'C30/37', 'C35/45', 'C40/50', 'C45/55', 'C50/60', 'C55/67', 'C60/75', 'C70/85', 'C80/95', 'C90/105'] = 'C20/25' - elastic_modulus_e: str = '30GPa' # TODO: unit-aware type + material: Literal["ElasticIsotropic"] = "ElasticIsotropic" + material_type: Literal["Concrete", "Steel", "Custom"] = "Custom" + concrete_class: Literal[ + "C12/15", + "C16/20", + "C20/25", + "C25/30", + "C30/37", + "C35/45", + "C40/50", + "C45/55", + "C50/60", + "C55/67", + "C60/75", + "C70/85", + "C80/95", + "C90/105", + ] = "C20/25" + elastic_modulus_e: str = "30GPa" # TODO: unit-aware type poisson_s_ratio: float = 0.20 - mass_density: str = '0.0ton/m^3' # TODO: unit-aware type - + mass_density: str = "0.0ton/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/elastic_membrane_plate.py b/src/otko/core/catalog/generated/elastic_membrane_plate.py index a19ab57..af479db 100644 --- a/src/otko/core/catalog/generated/elastic_membrane_plate.py +++ b/src/otko/core/catalog/generated/elastic_membrane_plate.py @@ -17,14 +17,13 @@ from pydantic import BaseModel, ConfigDict, Field class ElasticMembranePlateSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Elastic_Membrane_Plate', - 'x-book': 'Section_Force-Deformation', + "x-gid-name": "Elastic_Membrane_Plate", + "x-book": "Section_Force-Deformation", } ) - section: Literal['ElasticMembranePlate'] = 'ElasticMembranePlate' - elastic_modulus_e: str = '28GPa' # TODO: unit-aware type + section: Literal["ElasticMembranePlate"] = "ElasticMembranePlate" + elastic_modulus_e: str = "28GPa" # TODO: unit-aware type poisson_s_ratio: float = 0.2 - section_depth_h: str = '0.25m' # TODO: unit-aware type - mass_density: str = '0.0ton/m^3' # TODO: unit-aware type - + section_depth_h: str = "0.25m" # TODO: unit-aware type + mass_density: str = "0.0ton/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/elastic_orthotropic.py b/src/otko/core/catalog/generated/elastic_orthotropic.py index 5a3f863..f1a225a 100644 --- a/src/otko/core/catalog/generated/elastic_orthotropic.py +++ b/src/otko/core/catalog/generated/elastic_orthotropic.py @@ -17,20 +17,19 @@ from pydantic import BaseModel, ConfigDict, Field class ElasticOrthotropicSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Elastic_Orthotropic', - 'x-book': 'Multidimensional_(nD)_Materials', + "x-gid-name": "Elastic_Orthotropic", + "x-book": "Multidimensional_(nD)_Materials", } ) - material: Literal['ElasticOrthotropic'] = 'ElasticOrthotropic' - elastic_modulus_ex: str = '30GPa' # TODO: unit-aware type - elastic_modulus_ey: str = '30GPa' # TODO: unit-aware type - elastic_modulus_ez: str = '30GPa' # TODO: unit-aware type + material: Literal["ElasticOrthotropic"] = "ElasticOrthotropic" + elastic_modulus_ex: str = "30GPa" # TODO: unit-aware type + elastic_modulus_ey: str = "30GPa" # TODO: unit-aware type + elastic_modulus_ez: str = "30GPa" # TODO: unit-aware type poisson_s_ratio_vxy: float = 0.20 poisson_s_ratio_vyz: float = 0.20 poisson_s_ratio_vzx: float = 0.20 - shear_modulus_gxy: str = '11GPa' # TODO: unit-aware type - shear_modulus_gyz: str = '11GPa' # TODO: unit-aware type - shear_modulus_gzx: str = '11GPa' # TODO: unit-aware type - mass_density: str = '0.0ton/m^3' # TODO: unit-aware type - + shear_modulus_gxy: str = "11GPa" # TODO: unit-aware type + shear_modulus_gyz: str = "11GPa" # TODO: unit-aware type + shear_modulus_gzx: str = "11GPa" # TODO: unit-aware type + mass_density: str = "0.0ton/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/elastic_perfectly_plastic.py b/src/otko/core/catalog/generated/elastic_perfectly_plastic.py index 4b36761..fee4bf3 100644 --- a/src/otko/core/catalog/generated/elastic_perfectly_plastic.py +++ b/src/otko/core/catalog/generated/elastic_perfectly_plastic.py @@ -23,332 +23,332 @@ class CyclicDataRow(BaseModel): class ElasticPerfectlyPlasticSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Elastic_Perfectly_Plastic', - 'x-book': 'Standard_Uniaxial_Materials', - 'dependencies': [ + "x-gid-name": "Elastic_Perfectly_Plastic", + "x-book": "Standard_Uniaxial_Materials", + "dependencies": [ { - 'field': 'formulation', - 'gid_name': 'Formulation', - 'rules': [ + "field": "formulation", + "gid_name": "Formulation", + "rules": [ { - 'trigger': 'Stress-Strain', - 'actions': [ + "trigger": "Stress-Strain", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Material_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_type", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stiffness_K', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Elastic_modulus_E', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Elastic_modulus_E", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Deformation_epsN', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Deformation_epsN", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Deformation_epsP', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Deformation_epsP", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Initial_strain_eps0', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Initial_strain_eps0", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_deformation_eps0', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_deformation_eps0", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_epsP', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_epsP", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_epsN', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_epsN", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_rotation_eps0', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_rotation_eps0", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Force-Deformation', - 'actions': [ + "trigger": "Force-Deformation", + "actions": [ { - 'action': 'HIDE', - 'field': 'Material_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Material_type", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Elastic_modulus_E', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Elastic_modulus_E", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Stiffness_K', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Deformation_epsN', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Deformation_epsN", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Deformation_epsP', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Deformation_epsP", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_epsN', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_epsN", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_epsP', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_epsP", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_strain_eps0', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_strain_eps0", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Initial_deformation_eps0', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Initial_deformation_eps0", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_epsP', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_epsP", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_epsN', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_epsN", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_rotation_eps0', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_rotation_eps0", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Moment-Rotation', - 'actions': [ + "trigger": "Moment-Rotation", + "actions": [ { - 'action': 'HIDE', - 'field': 'Material_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Material_type", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Elastic_modulus_E', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Elastic_modulus_E", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stiffness_K', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Deformation_epsN', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Deformation_epsN", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Deformation_epsP', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Deformation_epsP", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_epsN', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_epsN", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_epsP', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_epsP", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_strain_eps0', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_strain_eps0", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_deformation_eps0', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_deformation_eps0", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Rotation_epsP', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rotation_epsP", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Rotation_epsN', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rotation_epsN", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Initial_rotation_eps0', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Initial_rotation_eps0", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'material_type', - 'gid_name': 'Material_type', - 'rules': [ + "field": "material_type", + "gid_name": "Material_type", + "rules": [ { - 'trigger': 'Concrete', - 'actions': [ + "trigger": "Concrete", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Steel', - 'actions': [ + "trigger": "Steel", + "actions": [ { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Custom', - 'actions': [ + "trigger": "Custom", + "actions": [ { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -358,26 +358,50 @@ class ElasticPerfectlyPlasticSpec(BaseModel): } ) - material: Literal['ElasticPerfectlyPlastic'] = Field('ElasticPerfectlyPlastic', description='elastic perfectly-plastic uniaxial material object') - formulation: Literal['Stress-Strain', 'Force-Deformation', 'Moment-Rotation'] = 'Stress-Strain' - material_type: Literal['Concrete', 'Steel', 'Custom'] = 'Custom' - concrete_class: Literal['C12/15', 'C16/20', 'C20/25', 'C25/30', 'C30/37', 'C35/45', 'C40/50', 'C45/55', 'C50/60', 'C55/67', 'C60/75', 'C70/85', 'C80/95', 'C90/105'] = 'C20/25' - strength_type: Literal['Mean', 'Characteristic'] = 'Characteristic' - steel_grade: Literal['S235', 'S275', 'S355', 'S450', 'B500'] = 'B500' - elastic_modulus_e: str = Field('20GPa', description='young modulus of elasticity E') # TODO: unit-aware type - stiffness_k: str = '4000 kN/m' # TODO: unit-aware type - moment_per_rotation_unit: str = '10000 kNm' # TODO: unit-aware type - strain_epsp: float = Field(1.10e-004, description='strain or deformation at which material reaches plastic state in tension') - strain_epsn: float = Field(-2.0e-3, description='strain or deformation at which material reaches plastic state in compression') - deformation_epsp: str = '1m' # TODO: unit-aware type - deformation_epsn: str = '-1m' # TODO: unit-aware type - rotation_epsp: str = '0.5 rad' # TODO: unit-aware type - rotation_epsn: str = '-0.5 rad' # TODO: unit-aware type + material: Literal["ElasticPerfectlyPlastic"] = Field( + "ElasticPerfectlyPlastic", description="elastic perfectly-plastic uniaxial material object" + ) + formulation: Literal["Stress-Strain", "Force-Deformation", "Moment-Rotation"] = "Stress-Strain" + material_type: Literal["Concrete", "Steel", "Custom"] = "Custom" + concrete_class: Literal[ + "C12/15", + "C16/20", + "C20/25", + "C25/30", + "C30/37", + "C35/45", + "C40/50", + "C45/55", + "C50/60", + "C55/67", + "C60/75", + "C70/85", + "C80/95", + "C90/105", + ] = "C20/25" + strength_type: Literal["Mean", "Characteristic"] = "Characteristic" + steel_grade: Literal["S235", "S275", "S355", "S450", "B500"] = "B500" + elastic_modulus_e: str = Field( + "20GPa", description="young modulus of elasticity E" + ) # TODO: unit-aware type + stiffness_k: str = "4000 kN/m" # TODO: unit-aware type + moment_per_rotation_unit: str = "10000 kNm" # TODO: unit-aware type + strain_epsp: float = Field( + 1.10e-004, + description="strain or deformation at which material reaches plastic state in tension", + ) + strain_epsn: float = Field( + -2.0e-3, + description="strain or deformation at which material reaches plastic state in compression", + ) + deformation_epsp: str = "1m" # TODO: unit-aware type + deformation_epsn: str = "-1m" # TODO: unit-aware type + rotation_epsp: str = "0.5 rad" # TODO: unit-aware type + rotation_epsn: str = "-0.5 rad" # TODO: unit-aware type initial_strain_eps0: float = 0.0 - initial_deformation_eps0: str = '0.0m' # TODO: unit-aware type - initial_rotation_eps0: str = '0.0 rad' # TODO: unit-aware type - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + initial_deformation_eps0: str = "0.0m" # TODO: unit-aware type + initial_rotation_eps0: str = "0.0 rad" # TODO: unit-aware type + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/catalog/generated/elastic_perfectly_plastic_with_gap.py b/src/otko/core/catalog/generated/elastic_perfectly_plastic_with_gap.py index 27221b1..e460af0 100644 --- a/src/otko/core/catalog/generated/elastic_perfectly_plastic_with_gap.py +++ b/src/otko/core/catalog/generated/elastic_perfectly_plastic_with_gap.py @@ -23,307 +23,307 @@ class CyclicDataRow(BaseModel): class ElasticPerfectlyPlasticWithGapSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Elastic_Perfectly_Plastic_with_Gap', - 'x-book': 'Standard_Uniaxial_Materials', - 'dependencies': [ + "x-gid-name": "Elastic_Perfectly_Plastic_with_Gap", + "x-book": "Standard_Uniaxial_Materials", + "dependencies": [ { - 'field': 'formulation', - 'gid_name': 'Formulation', - 'rules': [ + "field": "formulation", + "gid_name": "Formulation", + "rules": [ { - 'trigger': 'Stress-Strain', - 'actions': [ + "trigger": "Stress-Strain", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Elastic_modulus_E', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Elastic_modulus_E", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Yield_Stress_Fy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Yield_Stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Material_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_type", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Strain_gap', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strain_gap", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Deformation_gap', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Deformation_gap", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stiffness_K', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_My', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_My", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_gap', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_gap", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Force-Deformation', - 'actions': [ + "trigger": "Force-Deformation", + "actions": [ { - 'action': 'HIDE', - 'field': 'Elastic_modulus_E', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Elastic_modulus_E", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_gap', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_gap", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Yield_Stress_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Yield_Stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Material_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Material_type", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Stiffness_K', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Force_Fy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Force_Fy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Deformation_gap', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Deformation_gap", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_My', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_My", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_gap', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_gap", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Moment-Rotation', - 'actions': [ + "trigger": "Moment-Rotation", + "actions": [ { - 'action': 'HIDE', - 'field': 'Elastic_modulus_E', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Elastic_modulus_E", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_gap', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_gap", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Yield_Stress_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Yield_Stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Material_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Material_type", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stiffness_K', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Deformation_gap', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Deformation_gap", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_My', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_My", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Rotation_gap', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rotation_gap", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'material_type', - 'gid_name': 'Material_type', - 'rules': [ + "field": "material_type", + "gid_name": "Material_type", + "rules": [ { - 'trigger': 'Concrete', - 'actions': [ + "trigger": "Concrete", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Steel', - 'actions': [ + "trigger": "Steel", + "actions": [ { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Yield_Stress_Fy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Yield_Stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Custom', - 'actions': [ + "trigger": "Custom", + "actions": [ { - 'action': 'HIDE', - 'field': 'Concrete_class', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Concrete_class", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Yield_Stress_Fy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Yield_Stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strength_type', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strength_type", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -333,23 +333,45 @@ class ElasticPerfectlyPlasticWithGapSpec(BaseModel): } ) - material: Literal['ElasticPerfectlyPlasticwithGap'] = Field('ElasticPerfectlyPlasticwithGap', description='elastic perfectly-plastic gap uniaxial material object') - formulation: Literal['Stress-Strain', 'Force-Deformation', 'Moment-Rotation'] = 'Stress-Strain' - material_type: Literal['Concrete', 'Steel', 'Custom'] = 'Custom' - concrete_class: Literal['C12/15', 'C16/20', 'C20/25', 'C25/30', 'C30/37', 'C35/45', 'C40/50', 'C45/55', 'C50/60', 'C55/67', 'C60/75', 'C70/85', 'C80/95', 'C90/105'] = 'C20/25' - strength_type: Literal['Mean', 'Characteristic'] = 'Characteristic' - steel_grade: Literal['S235', 'S275', 'S355', 'S450', 'B500'] = 'B500' - elastic_modulus_e: str = Field('20GPa', description='young modulus of elasticity E') # TODO: unit-aware type - stiffness_k: str = '4000 kN/m' # TODO: unit-aware type - moment_per_rotation_unit: str = '10000 kNm' # TODO: unit-aware type - yield_stress_fy: str = '-20MPa' # TODO: unit-aware type - force_fy: str = Field('1000 kN', description='Yield stress at which material reaches the plastic plate. Negative value for compression.') # TODO: unit-aware type - moment_my: str = '150 kNm' # TODO: unit-aware type + material: Literal["ElasticPerfectlyPlasticwithGap"] = Field( + "ElasticPerfectlyPlasticwithGap", + description="elastic perfectly-plastic gap uniaxial material object", + ) + formulation: Literal["Stress-Strain", "Force-Deformation", "Moment-Rotation"] = "Stress-Strain" + material_type: Literal["Concrete", "Steel", "Custom"] = "Custom" + concrete_class: Literal[ + "C12/15", + "C16/20", + "C20/25", + "C25/30", + "C30/37", + "C35/45", + "C40/50", + "C45/55", + "C50/60", + "C55/67", + "C60/75", + "C70/85", + "C80/95", + "C90/105", + ] = "C20/25" + strength_type: Literal["Mean", "Characteristic"] = "Characteristic" + steel_grade: Literal["S235", "S275", "S355", "S450", "B500"] = "B500" + elastic_modulus_e: str = Field( + "20GPa", description="young modulus of elasticity E" + ) # TODO: unit-aware type + stiffness_k: str = "4000 kN/m" # TODO: unit-aware type + moment_per_rotation_unit: str = "10000 kNm" # TODO: unit-aware type + yield_stress_fy: str = "-20MPa" # TODO: unit-aware type + force_fy: str = Field( + "1000 kN", + description="Yield stress at which material reaches the plastic plate. Negative value for compression.", + ) # TODO: unit-aware type + moment_my: str = "150 kNm" # TODO: unit-aware type strain_gap: float = 0.0 - deformation_gap: str = '0.0m' # TODO: unit-aware type - rotation_gap: str = '0.0 rad' # TODO: unit-aware type - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + deformation_gap: str = "0.0m" # TODO: unit-aware type + rotation_gap: str = "0.0 rad" # TODO: unit-aware type + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/catalog/generated/elastic_section.py b/src/otko/core/catalog/generated/elastic_section.py index 2d98fb1..dc17b4f 100644 --- a/src/otko/core/catalog/generated/elastic_section.py +++ b/src/otko/core/catalog/generated/elastic_section.py @@ -17,332 +17,332 @@ from pydantic import BaseModel, ConfigDict, Field class ElasticSectionSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Elastic_Section', - 'x-book': 'Section_Force-Deformation', - 'dependencies': [ + "x-gid-name": "Elastic_Section", + "x-book": "Section_Force-Deformation", + "dependencies": [ { - 'field': 'calculate_shear_modulus_update_to_apply_cb_0_1', - 'gid_name': 'Calculate_Shear_modulus_(update_to_apply)#CB#(0,1)', - 'rules': [ + "field": "calculate_shear_modulus_update_to_apply_cb_0_1", + "gid_name": "Calculate_Shear_modulus_(update_to_apply)#CB#(0,1)", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': "Poisson's_ratio", - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Poisson's_ratio", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': "Poisson's_ratio", - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Poisson's_ratio", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'cross_section', - 'gid_name': 'Cross_section', - 'rules': [ + "field": "cross_section", + "gid_name": "Cross_section", + "rules": [ { - 'trigger': 'Rectangular', - 'actions': [ + "trigger": "Rectangular", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-y_Iyy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-y_Iyy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-z_Izz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-z_Izz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Torsional_moment_of_inertia_Iyz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Torsional_moment_of_inertia_Iyz", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Circular', - 'actions': [ + "trigger": "Circular", + "actions": [ { - 'action': 'HIDE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-y_Iyy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-y_Iyy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-z_Izz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-z_Izz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Torsional_moment_of_inertia_Iyz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Torsional_moment_of_inertia_Iyz", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Tee', - 'actions': [ + "trigger": "Tee", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_bw', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-y_Iyy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-y_Iyy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-z_Izz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-z_Izz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Torsional_moment_of_inertia_Iyz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Torsional_moment_of_inertia_Iyz", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'General', - 'actions': [ + "trigger": "General", + "actions": [ { - 'action': 'HIDE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bw", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Area_A", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_of_inertia_about_local-y_Iyy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_of_inertia_about_local-y_Iyy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_of_inertia_about_local-z_Izz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_of_inertia_about_local-z_Izz", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Torsional_moment_of_inertia_Iyz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Torsional_moment_of_inertia_Iyz", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'set_modification_factors', - 'gid_name': 'Set_modification_factors', - 'rules': [ + "field": "set_modification_factors", + "gid_name": "Set_modification_factors", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'mod._A', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._A", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Iyy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Iyy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Izz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Izz", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Iyz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Iyz", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Sy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Sy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Sz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Sz", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'mod._A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._A", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Iyy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Iyy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Izz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Izz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Iyz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Iyz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Sy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Sy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Sz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Sz", + "target": "#CURRENT#", }, ], }, @@ -352,26 +352,25 @@ class ElasticSectionSpec(BaseModel): } ) - section: Literal['ElasticSection'] = 'ElasticSection' - elastic_modulus_e: str = '28 GPa' # TODO: unit-aware type + section: Literal["ElasticSection"] = "ElasticSection" + elastic_modulus_e: str = "28 GPa" # TODO: unit-aware type calculate_shear_modulus_update_to_apply_cb_0_1: int = 0 - shear_modulus_g: str = '11.66666667 GPa' # TODO: unit-aware type - cross_section: Literal['Rectangular', 'Tee', 'Circular', 'General'] = 'Rectangular' - height_h: str = '0.5m' # TODO: unit-aware type - width_b: str = '0.3m' # TODO: unit-aware type - diameter_d: str = '0.5m' # TODO: unit-aware type - width_bf: str = '0.4m' # TODO: unit-aware type - height_hf: str = '0.15m' # TODO: unit-aware type - width_bw: str = '0.25m' # TODO: unit-aware type - area_a: str = '1.0 m^2' # TODO: unit-aware type - moment_of_inertia_about_local_y_iyy: str = '0.5 m^4' # TODO: unit-aware type - moment_of_inertia_about_local_z_izz: str = '0.5 m^4' # TODO: unit-aware type - torsional_moment_of_inertia_iyz: str = '1.0 m^4' # TODO: unit-aware type - set_modification_factors: Literal['1', '0'] = '0' + shear_modulus_g: str = "11.66666667 GPa" # TODO: unit-aware type + cross_section: Literal["Rectangular", "Tee", "Circular", "General"] = "Rectangular" + height_h: str = "0.5m" # TODO: unit-aware type + width_b: str = "0.3m" # TODO: unit-aware type + diameter_d: str = "0.5m" # TODO: unit-aware type + width_bf: str = "0.4m" # TODO: unit-aware type + height_hf: str = "0.15m" # TODO: unit-aware type + width_bw: str = "0.25m" # TODO: unit-aware type + area_a: str = "1.0 m^2" # TODO: unit-aware type + moment_of_inertia_about_local_y_iyy: str = "0.5 m^4" # TODO: unit-aware type + moment_of_inertia_about_local_z_izz: str = "0.5 m^4" # TODO: unit-aware type + torsional_moment_of_inertia_iyz: str = "1.0 m^4" # TODO: unit-aware type + set_modification_factors: Literal["1", "0"] = "0" mod_a: int = 1 mod_sy: int = 1 mod_sz: int = 1 mod_iyy: int = 1 mod_izz: int = 1 mod_iyz: int = 1 - diff --git a/src/otko/core/catalog/generated/elastic_timoshenko_beam_column.py b/src/otko/core/catalog/generated/elastic_timoshenko_beam_column.py index 8e02988..ca8b6fc 100644 --- a/src/otko/core/catalog/generated/elastic_timoshenko_beam_column.py +++ b/src/otko/core/catalog/generated/elastic_timoshenko_beam_column.py @@ -17,306 +17,306 @@ from pydantic import BaseModel, ConfigDict, Field class ElasticTimoshenkoBeamColumnSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Elastic_Timoshenko_Beam-Column', - 'x-book': 'Beam-Column_Elements', - 'dependencies': [ + "x-gid-name": "Elastic_Timoshenko_Beam-Column", + "x-book": "Beam-Column_Elements", + "dependencies": [ { - 'field': 'cross_section', - 'gid_name': 'Cross_section', - 'rules': [ + "field": "cross_section", + "gid_name": "Cross_section", + "rules": [ { - 'trigger': 'Rectangular', - 'actions': [ + "trigger": "Rectangular", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-y_Iyy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-y_Iyy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-z_Izz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-z_Izz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Torsional_moment_of_inertia_Iyz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Torsional_moment_of_inertia_Iyz", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Circular', - 'actions': [ + "trigger": "Circular", + "actions": [ { - 'action': 'HIDE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-y_Iyy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-y_Iyy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-z_Izz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-z_Izz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Torsional_moment_of_inertia_Iyz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Torsional_moment_of_inertia_Iyz", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Tee', - 'actions': [ + "trigger": "Tee", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_bw', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-y_Iyy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-y_Iyy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_of_inertia_about_local-z_Izz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_of_inertia_about_local-z_Izz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Torsional_moment_of_inertia_Iyz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Torsional_moment_of_inertia_Iyz", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'General', - 'actions': [ + "trigger": "General", + "actions": [ { - 'action': 'HIDE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bw", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Area_A", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_of_inertia_about_local-y_Iyy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_of_inertia_about_local-y_Iyy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_of_inertia_about_local-z_Izz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_of_inertia_about_local-z_Izz", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Torsional_moment_of_inertia_Iyz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Torsional_moment_of_inertia_Iyz", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'set_modification_factors', - 'gid_name': 'Set_modification_factors', - 'rules': [ + "field": "set_modification_factors", + "gid_name": "Set_modification_factors", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'mod._A', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._A", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Sy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Sy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Sz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Sz", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Iyy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Iyy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Izz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Izz", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'mod._Iyz', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._Iyz", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'mod._A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._A", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Sy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Sy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Sz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Sz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Iyy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Iyy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Izz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Izz", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'mod._Iyz', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._Iyz", + "target": "#CURRENT#", }, ], }, @@ -326,27 +326,33 @@ class ElasticTimoshenkoBeamColumnSpec(BaseModel): } ) - element_type: Literal['ElasticTimoshenkoBeamColumn'] = Field('ElasticTimoshenkoBeamColumn', description='A Timoshenko beam is a frame member that accounts for shear deformations.') - compatible_materials: Literal['Elastic_Isotropic'] = 'Elastic_Isotropic' - material_mat_multidimensional_nd_materials_user_materials: str = 'Elastic_Isotropic' - cross_section: Literal['Rectangular', 'Tee', 'Circular', 'General'] = 'Rectangular' - height_h: str = '0.5m' # TODO: unit-aware type - width_b: str = '0.3m' # TODO: unit-aware type - diameter_d: str = '0.5m' # TODO: unit-aware type - width_bf: str = '0.4m' # TODO: unit-aware type - height_hf: str = '0.15m' # TODO: unit-aware type - width_bw: str = '0.25m' # TODO: unit-aware type - area_a: str = '1.0 m^2' # TODO: unit-aware type - moment_of_inertia_about_local_y_iyy: str = '0.5 m^4' # TODO: unit-aware type - moment_of_inertia_about_local_z_izz: str = '0.5 m^4' # TODO: unit-aware type - torsional_moment_of_inertia_iyz: str = '1.0 m^4' # TODO: unit-aware type - set_modification_factors: Literal['1', '0'] = '0' + element_type: Literal["ElasticTimoshenkoBeamColumn"] = Field( + "ElasticTimoshenkoBeamColumn", + description="A Timoshenko beam is a frame member that accounts for shear deformations.", + ) + compatible_materials: Literal["Elastic_Isotropic"] = "Elastic_Isotropic" + material_mat_multidimensional_nd_materials_user_materials: str = "Elastic_Isotropic" + cross_section: Literal["Rectangular", "Tee", "Circular", "General"] = "Rectangular" + height_h: str = "0.5m" # TODO: unit-aware type + width_b: str = "0.3m" # TODO: unit-aware type + diameter_d: str = "0.5m" # TODO: unit-aware type + width_bf: str = "0.4m" # TODO: unit-aware type + height_hf: str = "0.15m" # TODO: unit-aware type + width_bw: str = "0.25m" # TODO: unit-aware type + area_a: str = "1.0 m^2" # TODO: unit-aware type + moment_of_inertia_about_local_y_iyy: str = "0.5 m^4" # TODO: unit-aware type + moment_of_inertia_about_local_z_izz: str = "0.5 m^4" # TODO: unit-aware type + torsional_moment_of_inertia_iyz: str = "1.0 m^4" # TODO: unit-aware type + set_modification_factors: Literal["1", "0"] = "0" mod_a: int = 1 - mod_sy: int = Field(1, description='This value will be multiplied by the Area (A) for local-y Shear Area (Sy)') - mod_sz: int = Field(1, description='This value will be multiplied by the Area (A) for local-z Shear Area (Sz)') + mod_sy: int = Field( + 1, description="This value will be multiplied by the Area (A) for local-y Shear Area (Sy)" + ) + mod_sz: int = Field( + 1, description="This value will be multiplied by the Area (A) for local-z Shear Area (Sz)" + ) mod_iyy: int = 1 mod_izz: int = 1 mod_iyz: int = 1 - geometric_transformation: Literal['Linear', 'P-Delta', 'Corotational'] = 'Linear' - weight_density: str = '25 kN/m^3' # TODO: unit-aware type - + geometric_transformation: Literal["Linear", "P-Delta", "Corotational"] = "Linear" + weight_density: str = "25 kN/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/fiber.py b/src/otko/core/catalog/generated/fiber.py index a047e2d..5ee6e3f 100644 --- a/src/otko/core/catalog/generated/fiber.py +++ b/src/otko/core/catalog/generated/fiber.py @@ -17,1968 +17,1968 @@ from pydantic import BaseModel, ConfigDict, Field class FiberSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Fiber', - 'x-book': 'Section_Force-Deformation', - 'dependencies': [ + "x-gid-name": "Fiber", + "x-book": "Section_Force-Deformation", + "dependencies": [ { - 'field': 'cross_section', - 'gid_name': 'Cross_section', - 'rules': [ + "field": "cross_section", + "gid_name": "Cross_section", + "rules": [ { - 'trigger': 'Rectangular_Column', - 'actions': [ + "trigger": "Rectangular_Column", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Core_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Core_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Cover_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cover_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Reinforcing_bar_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_beam_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_beam_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_beam_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_beam_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_beam_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_beam_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_beam_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_beam_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Web_width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Web_width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_thickness_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_thickness_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_beam_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_beam_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_beam_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_beam_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_b', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_b", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_arc', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_arc", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_in_the_circumferential_direction', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_in_the_circumferential_direction", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_in_the_radial_direction', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_in_the_radial_direction", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_along_h_length', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_along_h_length", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_along_b_length', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_along_b_length", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Corner_bar_size', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Corner_bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Middle_bar_size', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Middle_bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Corner_bar_area', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Corner_bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Middle_bar_area', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Middle_bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_h_length', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_h_length", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_b_length', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_b_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_bw_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_bw_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_width_wt', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_width_wt", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_width_wb', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_width_wb", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_thickness_ts1', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_thickness_ts1", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_thickness_ts2', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_thickness_ts2", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hv', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hv", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'External_web_thickness_tw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "External_web_thickness_tw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Include_additional_part', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Include_additional_part", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Solid_Section', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Solid_Section", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_top_layer_of_top_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_top_layer_of_top_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_bottom_layer_of_top_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_bottom_layer_of_top_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_top_layer_of_bottom_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_top_layer_of_bottom_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_bottom_layer_of_bottom_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_bottom_layer_of_bottom_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_beam_height', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_beam_height", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_beam_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_beam_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_top_slab_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_top_slab_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_top_slab_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_top_slab_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_bottom_slab_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_bottom_slab_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_bottom_slab_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_bottom_slab_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_external_web_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_external_web_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_internal_web_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_internal_web_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_web_height', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_web_height", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Main_section_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Main_section_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_reinforcing_bar_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_reinforcing_bar_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Circular_Column', - 'actions': [ + "trigger": "Circular_Column", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Core_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Core_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Cover_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cover_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Reinforcing_bar_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_beam_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_beam_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_beam_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_beam_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_beam_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_beam_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_beam_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_beam_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Web_width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Web_width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_thickness_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_thickness_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_beam_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_beam_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_beam_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_beam_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bar_area', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_b', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_b", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_along_arc', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_along_arc", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bar_size', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_in_the_circumferential_direction', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_in_the_circumferential_direction", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_in_the_radial_direction', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_in_the_radial_direction", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_h_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_h_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_b_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_b_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Corner_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Corner_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Middle_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Middle_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Corner_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Corner_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Middle_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Middle_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_h_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_h_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_b_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_b_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_bw_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_bw_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_width_wt', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_width_wt", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_width_wb', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_width_wb", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_thickness_ts1', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_thickness_ts1", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_thickness_ts2', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_thickness_ts2", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hv', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hv", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'External_web_thickness_tw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "External_web_thickness_tw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Include_additional_part', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Include_additional_part", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Solid_Section', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Solid_Section", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_top_layer_of_top_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_top_layer_of_top_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_bottom_layer_of_top_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_bottom_layer_of_top_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_top_layer_of_bottom_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_top_layer_of_bottom_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_bottom_layer_of_bottom_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_bottom_layer_of_bottom_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_beam_height', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_beam_height", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_beam_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_beam_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_top_slab_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_top_slab_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_top_slab_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_top_slab_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_bottom_slab_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_bottom_slab_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_bottom_slab_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_bottom_slab_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_external_web_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_external_web_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_internal_web_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_internal_web_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_web_height', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_web_height", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Main_section_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Main_section_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_reinforcing_bar_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_reinforcing_bar_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Rectangular_Beam', - 'actions': [ + "trigger": "Rectangular_Beam", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Core_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Core_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Cover_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cover_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Reinforcing_bar_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_beam_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_beam_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_beam_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_beam_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_beam_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_beam_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_beam_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_beam_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Web_width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Web_width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_thickness_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_thickness_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_beam_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_beam_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_beam_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_beam_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Corner_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Corner_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Middle_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Middle_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Corner_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Corner_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Middle_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Middle_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_b_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_b_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_h_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_h_length", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_b', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_b", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_arc', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_arc", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Top_bars', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Top_bars", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bottom_bars', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bottom_bars", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Top_bar_size', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Top_bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bottom_bar_size', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bottom_bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Top_bar_area', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Top_bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bottom_bar_area', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bottom_bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_h_length', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_h_length", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_b_length', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_b_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_bw_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_bw_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_in_the_circumferential_direction', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_in_the_circumferential_direction", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_in_the_radial_direction', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_in_the_radial_direction", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_width_wt', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_width_wt", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_width_wb', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_width_wb", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_thickness_ts1', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_thickness_ts1", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_thickness_ts2', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_thickness_ts2", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hv', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hv", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'External_web_thickness_tw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "External_web_thickness_tw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Include_additional_part', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Include_additional_part", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Solid_Section', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Solid_Section", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_top_layer_of_top_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_top_layer_of_top_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_bottom_layer_of_top_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_bottom_layer_of_top_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_top_layer_of_bottom_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_top_layer_of_bottom_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_bottom_layer_of_bottom_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_bottom_layer_of_bottom_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_beam_height', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_beam_height", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_beam_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_beam_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_top_slab_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_top_slab_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_top_slab_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_top_slab_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_bottom_slab_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_bottom_slab_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_bottom_slab_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_bottom_slab_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_external_web_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_external_web_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_internal_web_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_internal_web_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_web_height', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_web_height", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Main_section_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Main_section_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_reinforcing_bar_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_reinforcing_bar_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Tee_Beam', - 'actions': [ + "trigger": "Tee_Beam", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Core_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Core_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Cover_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cover_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Reinforcing_bar_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bottom_beam_bar_area', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bottom_beam_bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bottom_beam_bar_size', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bottom_beam_bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bottom_beam_bars', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bottom_beam_bars", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Top_beam_bar_size', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Top_beam_bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Slab_bar_size', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Slab_bar_area', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Top_beam_bar_area', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Top_beam_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Web_width_bw', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Web_width_bw", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Slab_thickness_hf', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Slab_thickness_hf", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Top_beam_bars', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Top_beam_bars", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Slab_bars', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Slab_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Corner_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Corner_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Middle_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Middle_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Corner_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Corner_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Middle_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Middle_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_b_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_b_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_h_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_h_length", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_b', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_b", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_arc', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_arc", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_h_length', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_h_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_b_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_b_length", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_bw_length', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_bw_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_in_the_circumferential_direction', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_in_the_circumferential_direction", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_in_the_radial_direction', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_in_the_radial_direction", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_width_wt', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_width_wt", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_width_wb', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_width_wb", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_thickness_ts1', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_thickness_ts1", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_thickness_ts2', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_thickness_ts2", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hv', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hv", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'External_web_thickness_tw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "External_web_thickness_tw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Include_additional_part', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Include_additional_part", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Solid_Section', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Solid_Section", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_top_layer_of_top_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_top_layer_of_top_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_bottom_layer_of_top_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_bottom_layer_of_top_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_top_layer_of_bottom_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_top_layer_of_bottom_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_bottom_layer_of_bottom_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_bottom_layer_of_bottom_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_beam_height', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_beam_height", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_beam_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_beam_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_top_slab_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_top_slab_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_top_slab_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_top_slab_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_bottom_slab_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_bottom_slab_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_bottom_slab_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_bottom_slab_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_external_web_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_external_web_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_internal_web_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_internal_web_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_web_height', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_web_height", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Main_section_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Main_section_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_slab_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_slab_reinforcing_bar_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_slab_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_slab_reinforcing_bar_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Bridge_Deck', - 'actions': [ + "trigger": "Bridge_Deck", + "actions": [ { - 'action': 'HIDE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_b', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_b", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Web_width_bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Web_width_bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_thickness_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_thickness_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_beam_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_beam_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_beam_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_beam_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_bars', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_bars", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_h_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_h_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_b_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_b_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_arc', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_arc", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Corner_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Corner_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Middle_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Middle_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_beam_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_beam_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_beam_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_beam_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Corner_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Corner_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Middle_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Middle_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Top_beam_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Top_beam_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bottom_beam_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bottom_beam_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_h_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_h_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_b_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_b_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_bw_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_bw_length", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_in_the_circumferential_direction', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_in_the_circumferential_direction", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_in_the_radial_direction', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_in_the_radial_direction", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Core_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Core_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Cover_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cover_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Reinforcing_bar_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Top_slab_width_wt', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Top_slab_width_wt", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bottom_slab_width_wb', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bottom_slab_width_wb", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Top_slab_thickness_ts1', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Top_slab_thickness_ts1", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bottom_slab_thickness_ts2', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bottom_slab_thickness_ts2", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Height_hv', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_hv", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'External_web_thickness_tw', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "External_web_thickness_tw", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Include_additional_part', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Include_additional_part", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Solid_Section', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Solid_Section", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_on_top_layer_of_top_slab', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_on_top_layer_of_top_slab", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_on_bottom_layer_of_top_slab', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_on_bottom_layer_of_top_slab", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_on_top_layer_of_bottom_slab', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_on_top_layer_of_bottom_slab", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_on_bottom_layer_of_bottom_slab', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_on_bottom_layer_of_bottom_slab", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_along_beam_height', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_along_beam_height", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_along_beam_width', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_along_beam_width", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Top_slab_bar_size', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Top_slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bottom_slab_bar_size', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bottom_slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Top_slab_bar_area', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Top_slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bottom_slab_bar_area', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bottom_slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_top_slab_width', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_top_slab_width", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_top_slab_thickness', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_top_slab_thickness", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_bottom_slab_width', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_bottom_slab_width", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_bottom_slab_thickness', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_bottom_slab_thickness", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_external_web_thickness', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_external_web_thickness", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_internal_web_thickness', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_internal_web_thickness", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_web_height', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_web_height", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Main_section_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Main_section_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Top_slab_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Top_slab_reinforcing_bar_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bottom_slab_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bottom_slab_reinforcing_bar_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'include_additional_part', - 'gid_name': 'Include_additional_part', - 'rules': [ + "field": "include_additional_part", + "gid_name": "Include_additional_part", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Additional_slab_thickness_ts3', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Additional_slab_thickness_ts3", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Sidewalk_width_bsw', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Sidewalk_width_bsw", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Sidewalk_thickness', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Sidewalk_thickness", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Beam_width_b', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Beam_width_b", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Beam_height', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Beam_height", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_beam_height', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_beam_height", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_beam_width', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_beam_width", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_on_top_layer_of_additional_slab', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_on_top_layer_of_additional_slab", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_on_bottom_layer_of_additional_slab', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_on_bottom_layer_of_additional_slab", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_along_beam_height', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_along_beam_height", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Bars_along_beam_width', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Bars_along_beam_width", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Additional_slab_bar_size', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Additional_slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Beam_bar_size', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Beam_bar_size", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Additional_slab_bar_area', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Additional_slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Beam_bar_area', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Beam_bar_area", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Additional_part_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Additional_part_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Additional_slab_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Additional_slab_reinforcing_bar_material", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Beam_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Beam_reinforcing_bar_material", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'Additional_slab_thickness_ts3', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Additional_slab_thickness_ts3", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Sidewalk_width_bsw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Sidewalk_width_bsw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Sidewalk_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Sidewalk_thickness", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Beam_width_b', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Beam_width_b", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Beam_height', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Beam_height", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_beam_height', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_beam_height", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_beam_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_beam_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_top_layer_of_additional_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_top_layer_of_additional_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_on_bottom_layer_of_additional_slab', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_on_bottom_layer_of_additional_slab", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_beam_height', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_beam_height", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Bars_along_beam_width', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Bars_along_beam_width", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Additional_slab_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Additional_slab_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Beam_bar_size', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Beam_bar_size", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Additional_slab_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Additional_slab_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Beam_bar_area', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Beam_bar_area", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Additional_part_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Additional_part_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Additional_slab_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Additional_slab_reinforcing_bar_material", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Beam_reinforcing_bar_material', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Beam_reinforcing_bar_material", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'solid_section', - 'gid_name': 'Solid_Section', - 'rules': [ + "field": "solid_section", + "gid_name": "Solid_Section", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'SET', - 'field': 'Void_width_dv', - 'target': '0.0m', + "action": "SET", + "field": "Void_width_dv", + "target": "0.0m", }, { - 'action': 'SET', - 'field': 'Number_of_voids', - 'target': '0', + "action": "SET", + "field": "Number_of_voids", + "target": "0", }, { - 'action': 'HIDE', - 'field': 'External_web_thickness_tw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "External_web_thickness_tw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Fibers_along_internal_web_thickness', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Fibers_along_internal_web_thickness", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Void_width_dv', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Void_width_dv", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Number_of_voids', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Number_of_voids", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'External_web_thickness_tw', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "External_web_thickness_tw", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Fibers_along_internal_web_thickness', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Fibers_along_internal_web_thickness", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_torsional_stiffness', - 'gid_name': 'Activate_torsional_stiffness', - 'rules': [ + "field": "activate_torsional_stiffness", + "gid_name": "Activate_torsional_stiffness", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Torsional_stiffness_GJ', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Torsional_stiffness_GJ", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'Torsional_stiffness_GJ', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Torsional_stiffness_GJ", + "target": "#CURRENT#", }, ], }, @@ -1988,34 +1988,36 @@ class FiberSpec(BaseModel): } ) - section: Literal['Fiber'] = 'Fiber' - compatible_materials: Literal['_'] = '_' - cross_section: Literal['Rectangular_Column', 'Rectangular_Beam', 'Tee_Beam', 'Circular_Column', 'Bridge_Deck'] = 'Rectangular_Column' - height_h: str = '0.5 m' # TODO: unit-aware type - width_b: str = '0.3 m' # TODO: unit-aware type - width_bf: str = '0.8 m' # TODO: unit-aware type - diameter_d: str = '0.5 m' # TODO: unit-aware type - web_width_bw: str = '0.3 m' # TODO: unit-aware type - slab_thickness_hf: str = '0.15 m' # TODO: unit-aware type - top_slab_width_wt: str = '7.0 m' # TODO: unit-aware type - bottom_slab_width_wb: str = '4.0 m' # TODO: unit-aware type - top_slab_thickness_ts1: str = '0.5 m' # TODO: unit-aware type - bottom_slab_thickness_ts2: str = '0.5 m' # TODO: unit-aware type - height_hv: str = '0.7 m' # TODO: unit-aware type - external_web_thickness_tw: str = '0.4 m' # TODO: unit-aware type - include_additional_part: Literal['1', '0'] = '1' - additional_slab_thickness_ts3: str = '0.5 m' # TODO: unit-aware type - sidewalk_thickness_ts4: str = '0.2 m' # TODO: unit-aware type - sidewalk_width_bsw: str = '1.10 m' # TODO: unit-aware type - beam_width_b: str = '0.4 m' # TODO: unit-aware type - beam_height_h: str = '0.6 m' # TODO: unit-aware type - solid_section: Literal['1', '0'] = '0' - activate_torsional_stiffness: Literal['1', '0'] = '0' - torsional_stiffness_gj: str = '10000 kNm^2' # TODO: unit-aware type + section: Literal["Fiber"] = "Fiber" + compatible_materials: Literal["_"] = "_" + cross_section: Literal[ + "Rectangular_Column", "Rectangular_Beam", "Tee_Beam", "Circular_Column", "Bridge_Deck" + ] = "Rectangular_Column" + height_h: str = "0.5 m" # TODO: unit-aware type + width_b: str = "0.3 m" # TODO: unit-aware type + width_bf: str = "0.8 m" # TODO: unit-aware type + diameter_d: str = "0.5 m" # TODO: unit-aware type + web_width_bw: str = "0.3 m" # TODO: unit-aware type + slab_thickness_hf: str = "0.15 m" # TODO: unit-aware type + top_slab_width_wt: str = "7.0 m" # TODO: unit-aware type + bottom_slab_width_wb: str = "4.0 m" # TODO: unit-aware type + top_slab_thickness_ts1: str = "0.5 m" # TODO: unit-aware type + bottom_slab_thickness_ts2: str = "0.5 m" # TODO: unit-aware type + height_hv: str = "0.7 m" # TODO: unit-aware type + external_web_thickness_tw: str = "0.4 m" # TODO: unit-aware type + include_additional_part: Literal["1", "0"] = "1" + additional_slab_thickness_ts3: str = "0.5 m" # TODO: unit-aware type + sidewalk_thickness_ts4: str = "0.2 m" # TODO: unit-aware type + sidewalk_width_bsw: str = "1.10 m" # TODO: unit-aware type + beam_width_b: str = "0.4 m" # TODO: unit-aware type + beam_height_h: str = "0.6 m" # TODO: unit-aware type + solid_section: Literal["1", "0"] = "0" + activate_torsional_stiffness: Literal["1", "0"] = "0" + torsional_stiffness_gj: str = "10000 kNm^2" # TODO: unit-aware type number_of_voids: int = 2 - void_width_dv: str = '0.3 m' # TODO: unit-aware type - reinforcement: Literal['_'] = '_' - cover_depth_for_bars: str = '0.05 m' # TODO: unit-aware type + void_width_dv: str = "0.3 m" # TODO: unit-aware type + reinforcement: Literal["_"] = "_" + cover_depth_for_bars: str = "0.05 m" # TODO: unit-aware type top_bars: int = 2 top_beam_bars: int = 2 bottom_beam_bars: int = 3 @@ -2024,14 +2026,14 @@ class FiberSpec(BaseModel): bars_along_h_length: int = 3 bars_along_b_length: int = 3 bars_along_arc: int = 16 - corner_bar_size: str = '18 mm' # TODO: unit-aware type - middle_bar_size: str = '16 mm' # TODO: unit-aware type - bar_size: str = '8 mm' # TODO: unit-aware type - top_bar_size: str = '14 mm' # TODO: unit-aware type - top_beam_bar_size: str = '14 mm' # TODO: unit-aware type - bottom_bar_size: str = '18 mm' # TODO: unit-aware type - bottom_beam_bar_size: str = '18 mm' # TODO: unit-aware type - slab_bar_size: str = '8 mm' # TODO: unit-aware type + corner_bar_size: str = "18 mm" # TODO: unit-aware type + middle_bar_size: str = "16 mm" # TODO: unit-aware type + bar_size: str = "8 mm" # TODO: unit-aware type + top_bar_size: str = "14 mm" # TODO: unit-aware type + top_beam_bar_size: str = "14 mm" # TODO: unit-aware type + bottom_bar_size: str = "18 mm" # TODO: unit-aware type + bottom_beam_bar_size: str = "18 mm" # TODO: unit-aware type + slab_bar_size: str = "8 mm" # TODO: unit-aware type bars_on_top_layer_of_top_slab: int = 10 bars_on_bottom_layer_of_top_slab: int = 10 bars_on_top_layer_of_bottom_slab: int = 10 @@ -2040,24 +2042,24 @@ class FiberSpec(BaseModel): bars_on_bottom_layer_of_additional_slab: int = 10 bars_along_beam_height: int = 5 bars_along_beam_width: int = 5 - top_slab_bar_size: str = '20 mm' # TODO: unit-aware type - bottom_slab_bar_size: str = '20 mm' # TODO: unit-aware type - additional_slab_bar_size: str = '20 mm' # TODO: unit-aware type - beam_bar_size: str = '20 mm' # TODO: unit-aware type + top_slab_bar_size: str = "20 mm" # TODO: unit-aware type + bottom_slab_bar_size: str = "20 mm" # TODO: unit-aware type + additional_slab_bar_size: str = "20 mm" # TODO: unit-aware type + beam_bar_size: str = "20 mm" # TODO: unit-aware type calculate_bar_areas_update_to_apply_cb_0_1: int = 1 - corner_bar_area: str = '2.545e-04 m^2' # TODO: unit-aware type - middle_bar_area: str = '2.011e-04 m^2' # TODO: unit-aware type - bar_area: str = '5.027e-05 m^2' # TODO: unit-aware type - top_bar_area: str = '1.539e-04 m^2' # TODO: unit-aware type - top_beam_bar_area: str = '1.539e-04 m^2' # TODO: unit-aware type - bottom_bar_area: str = '2.545e-04 m^2' # TODO: unit-aware type - bottom_beam_bar_area: str = '2.545e-04 m^2' # TODO: unit-aware type - slab_bar_area: str = '5.027e-05 m^2' # TODO: unit-aware type - top_slab_bar_area: str = '3.142e-04 m^2' # TODO: unit-aware type - bottom_slab_bar_area: str = '3.142e-04 m^2' # TODO: unit-aware type - additional_slab_bar_area: str = '3.142e-04 m^2' # TODO: unit-aware type - beam_bar_area: str = '3.142e-04 m^2' # TODO: unit-aware type - fibers: Literal['_'] = '_' + corner_bar_area: str = "2.545e-04 m^2" # TODO: unit-aware type + middle_bar_area: str = "2.011e-04 m^2" # TODO: unit-aware type + bar_area: str = "5.027e-05 m^2" # TODO: unit-aware type + top_bar_area: str = "1.539e-04 m^2" # TODO: unit-aware type + top_beam_bar_area: str = "1.539e-04 m^2" # TODO: unit-aware type + bottom_bar_area: str = "2.545e-04 m^2" # TODO: unit-aware type + bottom_beam_bar_area: str = "2.545e-04 m^2" # TODO: unit-aware type + slab_bar_area: str = "5.027e-05 m^2" # TODO: unit-aware type + top_slab_bar_area: str = "3.142e-04 m^2" # TODO: unit-aware type + bottom_slab_bar_area: str = "3.142e-04 m^2" # TODO: unit-aware type + additional_slab_bar_area: str = "3.142e-04 m^2" # TODO: unit-aware type + beam_bar_area: str = "3.142e-04 m^2" # TODO: unit-aware type + fibers: Literal["_"] = "_" suggest_fiber_mesh_update_to_apply_cb_0_1: int = 0 fibers_along_h_length: int = 17 fibers_along_b_length: int = 10 @@ -2073,13 +2075,12 @@ class FiberSpec(BaseModel): fibers_along_web_height: int = 10 fibers_along_beam_height: int = 6 fibers_along_beam_width: int = 5 - core_material: str = 'Concrete01_(Zero_tensile_strength)' - cover_material: str = 'Concrete01_(Zero_tensile_strength)' - reinforcing_bar_material: str = 'Steel01' - main_section_material: str = 'Concrete01_(Zero_tensile_strength)' - top_slab_reinforcing_bar_material: str = 'Steel01' - bottom_slab_reinforcing_bar_material: str = 'Steel01' - additional_part_material: str = 'Concrete01_(Zero_tensile_strength)' - additional_slab_reinforcing_bar_material: str = 'Steel01' - beam_reinforcing_bar_material: str = 'Steel01' - + core_material: str = "Concrete01_(Zero_tensile_strength)" + cover_material: str = "Concrete01_(Zero_tensile_strength)" + reinforcing_bar_material: str = "Steel01" + main_section_material: str = "Concrete01_(Zero_tensile_strength)" + top_slab_reinforcing_bar_material: str = "Steel01" + bottom_slab_reinforcing_bar_material: str = "Steel01" + additional_part_material: str = "Concrete01_(Zero_tensile_strength)" + additional_slab_reinforcing_bar_material: str = "Steel01" + beam_reinforcing_bar_material: str = "Steel01" diff --git a/src/otko/core/catalog/generated/fiber_custom.py b/src/otko/core/catalog/generated/fiber_custom.py index bce9428..4850326 100644 --- a/src/otko/core/catalog/generated/fiber_custom.py +++ b/src/otko/core/catalog/generated/fiber_custom.py @@ -17,30 +17,30 @@ from pydantic import BaseModel, ConfigDict, Field class FiberCustomSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Fiber_Custom', - 'x-book': 'Section_Force-Deformation', - 'dependencies': [ + "x-gid-name": "Fiber_Custom", + "x-book": "Section_Force-Deformation", + "dependencies": [ { - 'field': 'activate_torsional_stiffness', - 'gid_name': 'Activate_torsional_stiffness', - 'rules': [ + "field": "activate_torsional_stiffness", + "gid_name": "Activate_torsional_stiffness", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Torsional_stiffness_GJ', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Torsional_stiffness_GJ", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'Torsional_stiffness_GJ', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Torsional_stiffness_GJ", + "target": "#CURRENT#", }, ], }, @@ -50,18 +50,17 @@ class FiberCustomSpec(BaseModel): } ) - section: Literal['FiberCustom'] = 'FiberCustom' - cross_section_area: str = '0.0 m^2' # TODO: unit-aware type - activate_torsional_stiffness: Literal['1', '0'] = '0' - torsional_stiffness_gj: str = '10000 kNm^2' # TODO: unit-aware type - region_1_material: str = 'Concrete01_(Zero_tensile_strength)' - region_2_material: str = 'Concrete01_(Zero_tensile_strength)' - region_3_material: str = 'Steel02' - region_4_material: str = 'Steel02' - region_5_material: str = 'Steel02' - region_6_material: str = 'Steel02' - region_7_material: str = 'Steel02' - region_8_material: str = 'Steel02' - region_9_material: str = 'Steel02' - region_10_material: str = 'Steel02' - + section: Literal["FiberCustom"] = "FiberCustom" + cross_section_area: str = "0.0 m^2" # TODO: unit-aware type + activate_torsional_stiffness: Literal["1", "0"] = "0" + torsional_stiffness_gj: str = "10000 kNm^2" # TODO: unit-aware type + region_1_material: str = "Concrete01_(Zero_tensile_strength)" + region_2_material: str = "Concrete01_(Zero_tensile_strength)" + region_3_material: str = "Steel02" + region_4_material: str = "Steel02" + region_5_material: str = "Steel02" + region_6_material: str = "Steel02" + region_7_material: str = "Steel02" + region_8_material: str = "Steel02" + region_9_material: str = "Steel02" + region_10_material: str = "Steel02" diff --git a/src/otko/core/catalog/generated/fiberint.py b/src/otko/core/catalog/generated/fiberint.py index 193d671..9f9a812 100644 --- a/src/otko/core/catalog/generated/fiberint.py +++ b/src/otko/core/catalog/generated/fiberint.py @@ -17,33 +17,32 @@ from pydantic import BaseModel, ConfigDict, Field class FiberintSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'FiberInt', - 'x-book': 'Section_Force-Deformation', + "x-gid-name": "FiberInt", + "x-book": "Section_Force-Deformation", } ) - section: Literal['FiberInt'] = 'FiberInt' - compatible_materials: Literal['_'] = '_' + section: Literal["FiberInt"] = "FiberInt" + compatible_materials: Literal["_"] = "_" left_subsection_y_direction_cb: str = "" - thickness: str = '0.5 m' # TODO: unit-aware type - width: str = '0.5 m' # TODO: unit-aware type - cover: str = '0.05 m' # TODO: unit-aware type + thickness: str = "0.5 m" # TODO: unit-aware type + width: str = "0.5 m" # TODO: unit-aware type + cover: str = "0.05 m" # TODO: unit-aware type strips: int = 2 - steel_area: str = '16.08 m^2' # TODO: unit-aware type - middle_subsection: Literal['_'] = '_' - thickness_1: str = '0.25 m' # TODO: unit-aware type - width_1: str = '2 m' # TODO: unit-aware type + steel_area: str = "16.08 m^2" # TODO: unit-aware type + middle_subsection: Literal["_"] = "_" + thickness_1: str = "0.25 m" # TODO: unit-aware type + width_1: str = "2 m" # TODO: unit-aware type strips_1: int = 4 - steel_area_1: str = '10.05 m^2' # TODO: unit-aware type + steel_area_1: str = "10.05 m^2" # TODO: unit-aware type right_subsection_y_direction_cb: str = "" - thickness_2: str = '0.5 m' # TODO: unit-aware type - width_2: str = '0.5 m' # TODO: unit-aware type - cover_1: str = '0.05 m' # TODO: unit-aware type + thickness_2: str = "0.5 m" # TODO: unit-aware type + width_2: str = "0.5 m" # TODO: unit-aware type + cover_1: str = "0.05 m" # TODO: unit-aware type strips_2: int = 2 - steel_area_2: str = '16.08 m^2' # TODO: unit-aware type - horizontal_reinforcement: Literal['_'] = '_' - steel_area_3: str = '10 m^2' # TODO: unit-aware type - core_material: str = 'Concrete01_(Zero_tensile_strength)' - cover_material: str = 'Concrete01_(Zero_tensile_strength)' - reinforcing_bar_material: str = 'Steel01' - + steel_area_2: str = "16.08 m^2" # TODO: unit-aware type + horizontal_reinforcement: Literal["_"] = "_" + steel_area_3: str = "10 m^2" # TODO: unit-aware type + core_material: str = "Concrete01_(Zero_tensile_strength)" + cover_material: str = "Concrete01_(Zero_tensile_strength)" + reinforcing_bar_material: str = "Steel01" diff --git a/src/otko/core/catalog/generated/flexure_shear_interaction_displacement_based_beam_column.py b/src/otko/core/catalog/generated/flexure_shear_interaction_displacement_based_beam_column.py index c0a6567..10de81b 100644 --- a/src/otko/core/catalog/generated/flexure_shear_interaction_displacement_based_beam_column.py +++ b/src/otko/core/catalog/generated/flexure_shear_interaction_displacement_based_beam_column.py @@ -17,19 +17,20 @@ from pydantic import BaseModel, ConfigDict, Field class FlexureShearInteractionDisplacementBasedBeamColumnSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Flexure-Shear_Interaction_Displacement-Based_Beam-Column', - 'x-book': 'Beam-Column_Elements', + "x-gid-name": "Flexure-Shear_Interaction_Displacement-Based_Beam-Column", + "x-book": "Beam-Column_Elements", } ) - element_type: Literal['dispBeamColumnInt'] = 'dispBeamColumnInt' - compatible_sections: Literal['_'] = '_' - number_of_integration_points: Literal['3'] = Field('3', description='number of integration points along the element') - the_number_of_integration: Literal['_'] = '_' - to_increase_analysis_accuracy: Literal['_'] = '_' - section: str = 'FiberInt' + element_type: Literal["dispBeamColumnInt"] = "dispBeamColumnInt" + compatible_sections: Literal["_"] = "_" + number_of_integration_points: Literal["3"] = Field( + "3", description="number of integration points along the element" + ) + the_number_of_integration: Literal["_"] = "_" + to_increase_analysis_accuracy: Literal["_"] = "_" + section: str = "FiberInt" fraction_of_the_height_from_bottom_to_the_rotation_center: float = 0.4 - geometric_transformation: Literal['LinearInt'] = 'LinearInt' - mass_density: str = '0.0ton/m^3' # TODO: unit-aware type - weight_density: str = '25 kN/m^3' # TODO: unit-aware type - + geometric_transformation: Literal["LinearInt"] = "LinearInt" + mass_density: str = "0.0ton/m^3" # TODO: unit-aware type + weight_density: str = "25 kN/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/force_based_beam_column.py b/src/otko/core/catalog/generated/force_based_beam_column.py index e26d416..183019a 100644 --- a/src/otko/core/catalog/generated/force_based_beam_column.py +++ b/src/otko/core/catalog/generated/force_based_beam_column.py @@ -17,40 +17,40 @@ from pydantic import BaseModel, ConfigDict, Field class ForceBasedBeamColumnSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Force-Based_Beam-Column', - 'x-book': 'Beam-Column_Elements', - 'dependencies': [ + "x-gid-name": "Force-Based_Beam-Column", + "x-book": "Beam-Column_Elements", + "dependencies": [ { - 'field': 'activate_iterative_scheme_for_satisfying_element_compatibility', - 'gid_name': 'Activate_iterative_scheme_for_satisfying_element_compatibility', - 'rules': [ + "field": "activate_iterative_scheme_for_satisfying_element_compatibility", + "gid_name": "Activate_iterative_scheme_for_satisfying_element_compatibility", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Maximum_Iterations', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Maximum_Iterations", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Tolerance', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Tolerance", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'Maximum_Iterations', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Maximum_Iterations", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Tolerance', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Tolerance", + "target": "#CURRENT#", }, ], }, @@ -60,17 +60,22 @@ class ForceBasedBeamColumnSpec(BaseModel): } ) - element_type: Literal['forceBeamColumn'] = 'forceBeamColumn' - compatible_sections: Literal['_'] = '_' - integration_type: Literal['Lobatto'] = 'Lobatto' - number_of_integration_points: int = Field(3, description='number of Gauss-Lobatto integration points along the element') - the_number_of_integration: Literal['_'] = '_' - to_increase_analysis_accuracy: Literal['_'] = '_' - section: str = 'Fiber' - geometric_transformation: Literal['Linear', 'P-Delta', 'Corotational'] = 'Linear' - activate_iterative_scheme_for_satisfying_element_compatibility: Literal['1', '0'] = '1' - maximum_iterations: int = Field(10, description='maximum number of iterations to undertake to satisfy element compatibility') - tolerance: float = Field(1.0e-6, description='tolerance for satisfaction of element compatibility') - mass_density: str = '0.0ton/m^3' # TODO: unit-aware type - weight_density: str = '25 kN/m^3' # TODO: unit-aware type - + element_type: Literal["forceBeamColumn"] = "forceBeamColumn" + compatible_sections: Literal["_"] = "_" + integration_type: Literal["Lobatto"] = "Lobatto" + number_of_integration_points: int = Field( + 3, description="number of Gauss-Lobatto integration points along the element" + ) + the_number_of_integration: Literal["_"] = "_" + to_increase_analysis_accuracy: Literal["_"] = "_" + section: str = "Fiber" + geometric_transformation: Literal["Linear", "P-Delta", "Corotational"] = "Linear" + activate_iterative_scheme_for_satisfying_element_compatibility: Literal["1", "0"] = "1" + maximum_iterations: int = Field( + 10, description="maximum number of iterations to undertake to satisfy element compatibility" + ) + tolerance: float = Field( + 1.0e-6, description="tolerance for satisfaction of element compatibility" + ) + mass_density: str = "0.0ton/m^3" # TODO: unit-aware type + weight_density: str = "25 kN/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/ground_motion_record.py b/src/otko/core/catalog/generated/ground_motion_record.py index e41c545..b21d04d 100644 --- a/src/otko/core/catalog/generated/ground_motion_record.py +++ b/src/otko/core/catalog/generated/ground_motion_record.py @@ -17,85 +17,85 @@ from pydantic import BaseModel, ConfigDict, Field class GroundMotionRecordSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Ground_Motion_Record', - 'x-book': 'Records', - 'dependencies': [ + "x-gid-name": "Ground_Motion_Record", + "x-book": "Records", + "dependencies": [ { - 'field': 'record_file_format', - 'gid_name': 'Record_file_format', - 'rules': [ + "field": "record_file_format", + "gid_name": "Record_file_format", + "rules": [ { - 'trigger': 'PEER_format', - 'actions': [ + "trigger": "PEER_format", + "actions": [ { - 'action': 'HIDE', - 'field': 'Lines_to_skip', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Lines_to_skip", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Time_step', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Time_step", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Time_column', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Time_column", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Value_column', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Value_column", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Single_value_per_line', - 'actions': [ + "trigger": "Single_value_per_line", + "actions": [ { - 'action': 'HIDE', - 'field': 'Time_column', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Time_column", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Value_column', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Value_column", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Lines_to_skip', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Lines_to_skip", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Time_step', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Time_step", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Time_and_value_per_line', - 'actions': [ + "trigger": "Time_and_value_per_line", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Lines_to_skip', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Lines_to_skip", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Time_step', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Time_step", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Time_column', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Time_column", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Value_column', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Value_column", + "target": "#CURRENT#", }, ], }, @@ -106,11 +106,12 @@ class GroundMotionRecordSpec(BaseModel): ) record_file: str = "" - record_type: Literal['Acceleration', 'Displacement', 'Velocity', 'Function'] = 'Acceleration' - record_file_format: Literal['PEER_format', 'Single_value_per_line', 'Time_and_value_per_line'] = 'Time_and_value_per_line' + record_type: Literal["Acceleration", "Displacement", "Velocity", "Function"] = "Acceleration" + record_file_format: Literal[ + "PEER_format", "Single_value_per_line", "Time_and_value_per_line" + ] = "Time_and_value_per_line" lines_to_skip: int = 1 time_step: float = 0.005 - time_column: Literal['1', '2', '3', '4'] = '1' - value_column: Literal['1', '2', '3', '4'] = '2' + time_column: Literal["1", "2", "3", "4"] = "1" + value_column: Literal["1", "2", "3", "4"] = "2" scale_factor: float = 9.81 - diff --git a/src/otko/core/catalog/generated/hyperbolic_gap.py b/src/otko/core/catalog/generated/hyperbolic_gap.py index bed462d..809b4b7 100644 --- a/src/otko/core/catalog/generated/hyperbolic_gap.py +++ b/src/otko/core/catalog/generated/hyperbolic_gap.py @@ -23,30 +23,30 @@ class CyclicDataRow(BaseModel): class HyperbolicGapSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Hyperbolic_Gap', - 'x-book': 'Standard_Uniaxial_Materials', - 'dependencies': [ + "x-gid-name": "Hyperbolic_Gap", + "x-book": "Standard_Uniaxial_Materials", + "dependencies": [ { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -56,12 +56,11 @@ class HyperbolicGapSpec(BaseModel): } ) - material: Literal['_'] = '_' - initial_stiffness: str = '20300 kN/m' # TODO: unit-aware type - unloading_reloading_stiffness: str = '20300 kN/m' # TODO: unit-aware type + material: Literal["_"] = "_" + initial_stiffness: str = "20300 kN/m" # TODO: unit-aware type + unloading_reloading_stiffness: str = "20300 kN/m" # TODO: unit-aware type failure_ratio: float = 0.7 - ultimate_passive_resistance: str = '-326 kN' # TODO: unit-aware type - initial_gap: str = '-2.54 cm' # TODO: unit-aware type - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + ultimate_passive_resistance: str = "-326 kN" # TODO: unit-aware type + initial_gap: str = "-2.54 cm" # TODO: unit-aware type + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - diff --git a/src/otko/core/catalog/generated/hysteretic.py b/src/otko/core/catalog/generated/hysteretic.py index 66b7f0e..1f6d3ad 100644 --- a/src/otko/core/catalog/generated/hysteretic.py +++ b/src/otko/core/catalog/generated/hysteretic.py @@ -23,591 +23,591 @@ class CyclicDataRow(BaseModel): class HystereticSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Hysteretic', - 'x-book': 'Uniaxial_Steel_Materials', - 'dependencies': [ + "x-gid-name": "Hysteretic", + "x-book": "Uniaxial_Steel_Materials", + "dependencies": [ { - 'field': 'formulation', - 'gid_name': 'Formulation', - 'rules': [ + "field": "formulation", + "gid_name": "Formulation", + "rules": [ { - 'trigger': 'Stress-Strain', - 'actions': [ + "trigger": "Stress-Strain", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Stress_s1p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Stress_s1p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Stress_s2p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Stress_s2p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Stress_s3p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Stress_s3p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Stress_s1n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Stress_s1n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Stress_s2n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Stress_s2n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Stress_s3n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Stress_s3n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Strain_e1p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strain_e1p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Strain_e2p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strain_e2p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Strain_e3p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strain_e3p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Strain_e1n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strain_e1n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Strain_e2n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strain_e2n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Strain_e3n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Strain_e3n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_s1p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_s1p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_s2p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_s2p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_s3p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_s3p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_s1n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_s1n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_s2n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_s2n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_s3n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_s3n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_e1p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_e1p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_e2p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_e2p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_e3p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_e3p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_e1n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_e1n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_e2n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_e2n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_e3n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_e3n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_s1p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_s1p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_s2p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_s2p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_s3p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_s3p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_s1n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_s1n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_s2n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_s2n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_s3n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_s3n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_e1p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_e1p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_e2p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_e2p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_e3p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_e3p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_e1n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_e1n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_e2n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_e2n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_e3n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_e3n", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Force-Deformation', - 'actions': [ + "trigger": "Force-Deformation", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Force_s1p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Force_s1p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Force_s2p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Force_s2p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Force_s3p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Force_s3p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Force_s1n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Force_s1n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Force_s2n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Force_s2n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Force_s3n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Force_s3n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Displacement_e1p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Displacement_e1p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Displacement_e2p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Displacement_e2p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Displacement_e3p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Displacement_e3p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Displacement_e1n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Displacement_e1n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Displacement_e2n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Displacement_e2n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Displacement_e3n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Displacement_e3n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stress_s1p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stress_s1p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stress_s2p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stress_s2p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stress_s3p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stress_s3p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stress_s1n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stress_s1n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stress_s2n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stress_s2n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stress_s3n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stress_s3n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_e1p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_e1p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_e2p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_e2p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_e3p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_e3p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_e1n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_e1n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_e2n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_e2n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_e3n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_e3n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_s1p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_s1p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_s2p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_s2p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_s3p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_s3p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_s1n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_s1n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_s2n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_s2n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_s3n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_s3n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_e1p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_e1p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_e2p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_e2p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_e3p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_e3p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_e1n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_e1n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_e2n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_e2n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Rotation_e3n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Rotation_e3n", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Moment-Rotation', - 'actions': [ + "trigger": "Moment-Rotation", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Moment_s1p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_s1p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_s2p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_s2p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_s3p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_s3p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_s1n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_s1n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_s2n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_s2n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_s3n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_s3n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Rotation_e1p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rotation_e1p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Rotation_e2p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rotation_e2p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Rotation_e3p', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rotation_e3p", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Rotation_e1n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rotation_e1n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Rotation_e2n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rotation_e2n", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Rotation_e3n', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Rotation_e3n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stress_s1p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stress_s1p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stress_s2p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stress_s2p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stress_s3p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stress_s3p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stress_s1n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stress_s1n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stress_s2n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stress_s2n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Stress_s3n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Stress_s3n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_e1p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_e1p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_e2p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_e2p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_e3p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_e3p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_e1n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_e1n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_e2n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_e2n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Strain_e3n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Strain_e3n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_s1p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_s1p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_s2p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_s2p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_s3p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_s3p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_s1n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_s1n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_s2n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_s2n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_s3n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_s3n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_e1p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_e1p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_e2p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_e2p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_e3p', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_e3p", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_e1n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_e1n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_e2n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_e2n", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Displacement_e3n', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Displacement_e3n", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -617,49 +617,48 @@ class HystereticSpec(BaseModel): } ) - material: Literal['_'] = '_' - formulation: Literal['Stress-Strain', 'Force-Deformation', 'Moment-Rotation'] = 'Stress-Strain' - stress_s1p: str = '400MPa' # TODO: unit-aware type + material: Literal["_"] = "_" + formulation: Literal["Stress-Strain", "Force-Deformation", "Moment-Rotation"] = "Stress-Strain" + stress_s1p: str = "400MPa" # TODO: unit-aware type strain_e1p: float = 2.0e-3 - force_s1p: str = '0.0kN' # TODO: unit-aware type - displacement_e1p: str = '0.0m' # TODO: unit-aware type - moment_s1p: str = '0.0kNm' # TODO: unit-aware type - rotation_e1p: str = '0.0 rad' # TODO: unit-aware type - stress_s2p: str = '500MPa' # TODO: unit-aware type + force_s1p: str = "0.0kN" # TODO: unit-aware type + displacement_e1p: str = "0.0m" # TODO: unit-aware type + moment_s1p: str = "0.0kNm" # TODO: unit-aware type + rotation_e1p: str = "0.0 rad" # TODO: unit-aware type + stress_s2p: str = "500MPa" # TODO: unit-aware type strain_e2p: float = 2.5e-3 - force_s2p: str = '0.0kN' # TODO: unit-aware type - displacement_e2p: str = '0.0m' # TODO: unit-aware type - moment_s2p: str = '0.0kNm' # TODO: unit-aware type - rotation_e2p: str = '0.0 rad' # TODO: unit-aware type - stress_s3p: str = '575MPa' # TODO: unit-aware type + force_s2p: str = "0.0kN" # TODO: unit-aware type + displacement_e2p: str = "0.0m" # TODO: unit-aware type + moment_s2p: str = "0.0kNm" # TODO: unit-aware type + rotation_e2p: str = "0.0 rad" # TODO: unit-aware type + stress_s3p: str = "575MPa" # TODO: unit-aware type strain_e3p: float = 7.5e-2 - force_s3p: str = '0.0kN' # TODO: unit-aware type - displacement_e3p: str = '0.0m' # TODO: unit-aware type - moment_s3p: str = '0.0kNm' # TODO: unit-aware type - rotation_e3p: str = '0.0 rad' # TODO: unit-aware type - stress_s1n: str = '-400MPa' # TODO: unit-aware type + force_s3p: str = "0.0kN" # TODO: unit-aware type + displacement_e3p: str = "0.0m" # TODO: unit-aware type + moment_s3p: str = "0.0kNm" # TODO: unit-aware type + rotation_e3p: str = "0.0 rad" # TODO: unit-aware type + stress_s1n: str = "-400MPa" # TODO: unit-aware type strain_e1n: float = -2.0e-3 - force_s1n: str = '0.0kN' # TODO: unit-aware type - displacement_e1n: str = '0.0m' # TODO: unit-aware type - moment_s1n: str = '0.0kNm' # TODO: unit-aware type - rotation_e1n: str = '0.0 rad' # TODO: unit-aware type - stress_s2n: str = '-500MPa' # TODO: unit-aware type + force_s1n: str = "0.0kN" # TODO: unit-aware type + displacement_e1n: str = "0.0m" # TODO: unit-aware type + moment_s1n: str = "0.0kNm" # TODO: unit-aware type + rotation_e1n: str = "0.0 rad" # TODO: unit-aware type + stress_s2n: str = "-500MPa" # TODO: unit-aware type strain_e2n: float = -2.5e-3 - force_s2n: str = '0.0kN' # TODO: unit-aware type - displacement_e2n: str = '0.0m' # TODO: unit-aware type - moment_s2n: str = '0.0kNm' # TODO: unit-aware type - rotation_e2n: str = '0.0 rad' # TODO: unit-aware type - stress_s3n: str = '-575MPa' # TODO: unit-aware type + force_s2n: str = "0.0kN" # TODO: unit-aware type + displacement_e2n: str = "0.0m" # TODO: unit-aware type + moment_s2n: str = "0.0kNm" # TODO: unit-aware type + rotation_e2n: str = "0.0 rad" # TODO: unit-aware type + stress_s3n: str = "-575MPa" # TODO: unit-aware type strain_e3n: float = -7.5e-2 - force_s3n: str = '0.0kN' # TODO: unit-aware type - displacement_e3n: str = '0.0m' # TODO: unit-aware type - moment_s3n: str = '0.0kNm' # TODO: unit-aware type - rotation_e3n: str = '0.0 rad' # TODO: unit-aware type + force_s3n: str = "0.0kN" # TODO: unit-aware type + displacement_e3n: str = "0.0m" # TODO: unit-aware type + moment_s3n: str = "0.0kNm" # TODO: unit-aware type + rotation_e3n: str = "0.0 rad" # TODO: unit-aware type pinching_factor_for_strain_deformation: int = 1 pinching_factor_for_stress_force: int = 1 damage_due_to_ductility: float = 0.0 damage_due_to_energy: float = 0.2 beta_power: float = 0.0 - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - diff --git a/src/otko/core/catalog/generated/initial_strain.py b/src/otko/core/catalog/generated/initial_strain.py index 1ea1283..e5cc1e9 100644 --- a/src/otko/core/catalog/generated/initial_strain.py +++ b/src/otko/core/catalog/generated/initial_strain.py @@ -17,12 +17,11 @@ from pydantic import BaseModel, ConfigDict, Field class InitialStrainSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Initial_Strain', - 'x-book': 'Combined_Materials', + "x-gid-name": "Initial_Strain", + "x-book": "Combined_Materials", } ) - material: Literal['_'] = '_' - material_for_defining_initial_strain: str = 'Elastic' + material: Literal["_"] = "_" + material_for_defining_initial_strain: str = "Elastic" initial_strain: float = 0.0004 - diff --git a/src/otko/core/catalog/generated/initial_stress.py b/src/otko/core/catalog/generated/initial_stress.py index cd31467..4938462 100644 --- a/src/otko/core/catalog/generated/initial_stress.py +++ b/src/otko/core/catalog/generated/initial_stress.py @@ -17,12 +17,11 @@ from pydantic import BaseModel, ConfigDict, Field class InitialStressSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Initial_Stress', - 'x-book': 'Combined_Materials', + "x-gid-name": "Initial_Stress", + "x-book": "Combined_Materials", } ) - material: Literal['_'] = '_' - material_for_defining_initial_stress: str = 'Elastic' - initial_stress: str = '-10 MPa' # TODO: unit-aware type - + material: Literal["_"] = "_" + material_for_defining_initial_stress: str = "Elastic" + initial_stress: str = "-10 MPa" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/j2plasticity.py b/src/otko/core/catalog/generated/j2plasticity.py index 9cea9ee..1dc77e9 100644 --- a/src/otko/core/catalog/generated/j2plasticity.py +++ b/src/otko/core/catalog/generated/j2plasticity.py @@ -17,16 +17,15 @@ from pydantic import BaseModel, ConfigDict, Field class J2plasticitySpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'J2Plasticity', - 'x-book': 'Multidimensional_(nD)_Materials', + "x-gid-name": "J2Plasticity", + "x-book": "Multidimensional_(nD)_Materials", } ) - material: Literal['J2Plasticity'] = 'J2Plasticity' - bulk_modulus: str = '300MPa' # TODO: unit-aware type - shear_modulus: str = '60MPa' # TODO: unit-aware type - initial_yield_stress: str = '0.0kPa' # TODO: unit-aware type - final_saturation_yield_stress: str = '10kPa' # TODO: unit-aware type + material: Literal["J2Plasticity"] = "J2Plasticity" + bulk_modulus: str = "300MPa" # TODO: unit-aware type + shear_modulus: str = "60MPa" # TODO: unit-aware type + initial_yield_stress: str = "0.0kPa" # TODO: unit-aware type + final_saturation_yield_stress: str = "10kPa" # TODO: unit-aware type exp_hardening_parameter_delta: float = 1.0 linear_hardening_parameter: float = 1.0 - diff --git a/src/otko/core/catalog/generated/layeredshell.py b/src/otko/core/catalog/generated/layeredshell.py index 515478e..3b9f2d2 100644 --- a/src/otko/core/catalog/generated/layeredshell.py +++ b/src/otko/core/catalog/generated/layeredshell.py @@ -17,41 +17,40 @@ from pydantic import BaseModel, ConfigDict, Field class LayeredshellSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'LayeredShell', - 'x-book': 'Section_Force-Deformation', + "x-gid-name": "LayeredShell", + "x-book": "Section_Force-Deformation", } ) - section: Literal['LayeredShell'] = 'LayeredShell' - wall_width: str = '0.25 m' # TODO: unit-aware type - reinforcement_cover: str = '5 cm' # TODO: unit-aware type + section: Literal["LayeredShell"] = "LayeredShell" + wall_width: str = "0.25 m" # TODO: unit-aware type + reinforcement_cover: str = "5 cm" # TODO: unit-aware type longitudinal_bars: int = 2 - longitudinal_bar_diameter: str = '16 mm' # TODO: unit-aware type - longitudinal_reinforcement_spacing: str = '100 mm' # TODO: unit-aware type + longitudinal_bar_diameter: str = "16 mm" # TODO: unit-aware type + longitudinal_reinforcement_spacing: str = "100 mm" # TODO: unit-aware type transverse_bars: int = 2 - transverse_bar_diameter: str = '8 mm' # TODO: unit-aware type - transverse_reinforcement_spacing: str = '100 mm' # TODO: unit-aware type + transverse_bar_diameter: str = "8 mm" # TODO: unit-aware type + transverse_reinforcement_spacing: str = "100 mm" # TODO: unit-aware type calculate_reinforcement_ratios_update_to_apply_cb_0_1: int = 1 longitudinal_reinforcement_ratio: float = 1.608e-02 transverse_reinforcement_ratio: float = 4.021e-03 cover_layers: int = 1 core_layers: int = 4 - cover_fc: str = Field('20 MPa', description='positive value') # TODO: unit-aware type - cover_ft: str = Field('2.2 MPa', description='positive value') # TODO: unit-aware type - cover_fu: str = Field('-17.0 MPa', description='negative value') # TODO: unit-aware type - cover_ec: float = Field(-2.0e-3, description='negative value') - cover_ecu: float = Field(-3.5e-3, description='negative value') - cover_etu: float = Field(1.0e-3, description='positive value') + cover_fc: str = Field("20 MPa", description="positive value") # TODO: unit-aware type + cover_ft: str = Field("2.2 MPa", description="positive value") # TODO: unit-aware type + cover_fu: str = Field("-17.0 MPa", description="negative value") # TODO: unit-aware type + cover_ec: float = Field(-2.0e-3, description="negative value") + cover_ecu: float = Field(-3.5e-3, description="negative value") + cover_etu: float = Field(1.0e-3, description="positive value") cover_shear_ret_factor: float = 0.1 - cover_g: str = '12.5 GPa' # TODO: unit-aware type - core_fc: str = Field('20 MPa', description='positive value') # TODO: unit-aware type - core_ft: str = Field('2.2 MPa', description='positive value') # TODO: unit-aware type - core_fu: str = Field('-17.0 MPa', description='negative value') # TODO: unit-aware type - core_ec: float = Field(-2.0e-3, description='negative value') - core_ecu: float = Field(-3.5e-3, description='negative value') - core_etu: float = Field(1.0e-3, description='positive value') + cover_g: str = "12.5 GPa" # TODO: unit-aware type + core_fc: str = Field("20 MPa", description="positive value") # TODO: unit-aware type + core_ft: str = Field("2.2 MPa", description="positive value") # TODO: unit-aware type + core_fu: str = Field("-17.0 MPa", description="negative value") # TODO: unit-aware type + core_ec: float = Field(-2.0e-3, description="negative value") + core_ecu: float = Field(-3.5e-3, description="negative value") + core_etu: float = Field(1.0e-3, description="positive value") core_shear_ret_factor: float = 0.1 - core_g: str = '12.5 GPa' # TODO: unit-aware type - longitudinal_steel_material: str = 'Steel02' - transverse_steel_material: str = 'Steel02' - + core_g: str = "12.5 GPa" # TODO: unit-aware type + longitudinal_steel_material: str = "Steel02" + transverse_steel_material: str = "Steel02" diff --git a/src/otko/core/catalog/generated/minmax.py b/src/otko/core/catalog/generated/minmax.py index 343b55d..a066d64 100644 --- a/src/otko/core/catalog/generated/minmax.py +++ b/src/otko/core/catalog/generated/minmax.py @@ -17,160 +17,160 @@ from pydantic import BaseModel, ConfigDict, Field class MinmaxSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'MinMax', - 'x-book': 'Combined_Materials', - 'dependencies': [ + "x-gid-name": "MinMax", + "x-book": "Combined_Materials", + "dependencies": [ { - 'field': 'formulation', - 'gid_name': 'Formulation', - 'rules': [ + "field": "formulation", + "gid_name": "Formulation", + "rules": [ { - 'trigger': 'Stress-Strain', - 'actions': [ + "trigger": "Stress-Strain", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Minimum_strain', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Minimum_strain", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Maximum_strain', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Maximum_strain", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Minimum_deformation', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Minimum_deformation", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Maximum_deformation', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Maximum_deformation", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Minimum_rotation', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Minimum_rotation", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Maximum_rotation', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Maximum_rotation", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Material_for_defining_stress-strain_behaviour', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_for_defining_stress-strain_behaviour", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Material_for_defining_force-deformation_behaviour', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Material_for_defining_force-deformation_behaviour", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Material_for_defining_moment-rotation_behaviour', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Material_for_defining_moment-rotation_behaviour", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Force-Deformation', - 'actions': [ + "trigger": "Force-Deformation", + "actions": [ { - 'action': 'HIDE', - 'field': 'Minimum_strain', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Minimum_strain", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Maximum_strain', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Maximum_strain", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Minimum_deformation', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Minimum_deformation", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Maximum_deformation', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Maximum_deformation", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Minimum_rotation', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Minimum_rotation", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Maximum_rotation', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Maximum_rotation", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Material_for_defining_stress-strain_behaviour', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Material_for_defining_stress-strain_behaviour", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Material_for_defining_force-deformation_behaviour', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_for_defining_force-deformation_behaviour", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Material_for_defining_moment-rotation_behaviour', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Material_for_defining_moment-rotation_behaviour", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Moment-Rotation', - 'actions': [ + "trigger": "Moment-Rotation", + "actions": [ { - 'action': 'HIDE', - 'field': 'Minimum_strain', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Minimum_strain", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Maximum_strain', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Maximum_strain", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Minimum_deformation', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Minimum_deformation", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Maximum_deformation', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Maximum_deformation", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Minimum_rotation', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Minimum_rotation", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Maximum_rotation', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Maximum_rotation", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Material_for_defining_stress-strain_behaviour', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Material_for_defining_stress-strain_behaviour", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Material_for_defining_force-deformation_behaviour', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Material_for_defining_force-deformation_behaviour", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Material_for_defining_moment-rotation_behaviour', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_for_defining_moment-rotation_behaviour", + "target": "#CURRENT#", }, ], }, @@ -180,15 +180,14 @@ class MinmaxSpec(BaseModel): } ) - material: Literal['_'] = '_' - formulation: Literal['Stress-Strain', 'Force-Deformation', 'Moment-Rotation'] = 'Stress-Strain' - material_for_defining_stress_strain_behaviour: str = 'Steel01' - material_for_defining_force_deformation_behaviour: str = 'Steel01' - material_for_defining_moment_rotation_behaviour: str = 'Steel01' + material: Literal["_"] = "_" + formulation: Literal["Stress-Strain", "Force-Deformation", "Moment-Rotation"] = "Stress-Strain" + material_for_defining_stress_strain_behaviour: str = "Steel01" + material_for_defining_force_deformation_behaviour: str = "Steel01" + material_for_defining_moment_rotation_behaviour: str = "Steel01" minimum_strain: float = -1.0e16 maximum_strain: float = 1.0e16 - minimum_deformation: str = '-1.0e16 m' # TODO: unit-aware type - maximum_deformation: str = '1.0e16 m' # TODO: unit-aware type - minimum_rotation: str = '-1.0e16 rad' # TODO: unit-aware type - maximum_rotation: str = '1.0e16 rad' # TODO: unit-aware type - + minimum_deformation: str = "-1.0e16 m" # TODO: unit-aware type + maximum_deformation: str = "1.0e16 m" # TODO: unit-aware type + minimum_rotation: str = "-1.0e16 rad" # TODO: unit-aware type + maximum_rotation: str = "1.0e16 rad" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/parallel.py b/src/otko/core/catalog/generated/parallel.py index b613566..813e7e1 100644 --- a/src/otko/core/catalog/generated/parallel.py +++ b/src/otko/core/catalog/generated/parallel.py @@ -17,134 +17,134 @@ from pydantic import BaseModel, ConfigDict, Field class ParallelSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Parallel', - 'x-book': 'Combined_Materials', - 'dependencies': [ + "x-gid-name": "Parallel", + "x-book": "Combined_Materials", + "dependencies": [ { - 'field': 'activate_1st_material', - 'gid_name': 'Activate_1st_material', - 'rules': [ + "field": "activate_1st_material", + "gid_name": "Activate_1st_material", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Material_1', - 'target': '#CURRENT#', + "action": "SET", + "field": "Material_1", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Material_1', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_1", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_2nd_material', - 'gid_name': 'Activate_2nd_material', - 'rules': [ + "field": "activate_2nd_material", + "gid_name": "Activate_2nd_material", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Material_2', - 'target': '#CURRENT#', + "action": "SET", + "field": "Material_2", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Material_2', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_2", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_3rd_material', - 'gid_name': 'Activate_3rd_material', - 'rules': [ + "field": "activate_3rd_material", + "gid_name": "Activate_3rd_material", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Material_3', - 'target': '#CURRENT#', + "action": "SET", + "field": "Material_3", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Material_3', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_3", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_4th_material', - 'gid_name': 'Activate_4th_Material', - 'rules': [ + "field": "activate_4th_material", + "gid_name": "Activate_4th_Material", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Material_4', - 'target': '#CURRENT#', + "action": "SET", + "field": "Material_4", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Material_4', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_4", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_5th_material', - 'gid_name': 'Activate_5th_material', - 'rules': [ + "field": "activate_5th_material", + "gid_name": "Activate_5th_material", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Material_5', - 'target': '#CURRENT#', + "action": "SET", + "field": "Material_5", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Material_5', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_5", + "target": "#CURRENT#", }, ], }, @@ -154,15 +154,14 @@ class ParallelSpec(BaseModel): } ) - material: Literal['Parallel'] = 'Parallel' - activate_1st_material: Literal['0', '1'] = '1' - material_1: str = 'Elastic' - activate_2nd_material: Literal['0', '1'] = '1' - material_2: str = 'Elastic' - activate_3rd_material: Literal['0', '1'] = '0' - material_3: str = 'Elastic' - activate_4th_material: Literal['0', '1'] = '0' - material_4: str = 'Elastic' - activate_5th_material: Literal['0', '1'] = '0' - material_5: str = 'Elastic' - + material: Literal["Parallel"] = "Parallel" + activate_1st_material: Literal["0", "1"] = "1" + material_1: str = "Elastic" + activate_2nd_material: Literal["0", "1"] = "1" + material_2: str = "Elastic" + activate_3rd_material: Literal["0", "1"] = "0" + material_3: str = "Elastic" + activate_4th_material: Literal["0", "1"] = "0" + material_4: str = "Elastic" + activate_5th_material: Literal["0", "1"] = "0" + material_5: str = "Elastic" diff --git a/src/otko/core/catalog/generated/plate_fiber.py b/src/otko/core/catalog/generated/plate_fiber.py index 45facaa..93c2073 100644 --- a/src/otko/core/catalog/generated/plate_fiber.py +++ b/src/otko/core/catalog/generated/plate_fiber.py @@ -17,13 +17,14 @@ from pydantic import BaseModel, ConfigDict, Field class PlateFiberSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Plate_Fiber', - 'x-book': 'Section_Force-Deformation', + "x-gid-name": "Plate_Fiber", + "x-book": "Section_Force-Deformation", } ) - section: Literal['PlateFiber'] = 'PlateFiber' - compatible_materials: Literal['nD_Materials'] = 'nD_Materials' - material_mat_multidimensional_nd_materials: str = Field('Elastic_Isotropic', description='All nD materials are listed here') - plate_thickness_h: str = '0.25m' # TODO: unit-aware type - + section: Literal["PlateFiber"] = "PlateFiber" + compatible_materials: Literal["nD_Materials"] = "nD_Materials" + material_mat_multidimensional_nd_materials: str = Field( + "Elastic_Isotropic", description="All nD materials are listed here" + ) + plate_thickness_h: str = "0.25m" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/pressuredependmultiyield.py b/src/otko/core/catalog/generated/pressuredependmultiyield.py index e62d0c3..e510fa8 100644 --- a/src/otko/core/catalog/generated/pressuredependmultiyield.py +++ b/src/otko/core/catalog/generated/pressuredependmultiyield.py @@ -17,56 +17,56 @@ from pydantic import BaseModel, ConfigDict, Field class PressuredependmultiyieldSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'PressureDependMultiYield', - 'x-book': 'Multidimensional_(nD)_Materials', - 'dependencies': [ + "x-gid-name": "PressureDependMultiYield", + "x-book": "Multidimensional_(nD)_Materials", + "dependencies": [ { - 'field': 'activate_plastic_response', - 'gid_name': 'Activate_plastic_response', - 'rules': [ + "field": "activate_plastic_response", + "gid_name": "Activate_plastic_response", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'Activate_plastic_response_at_interval', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Activate_plastic_response_at_interval", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Activate_plastic_response_at_interval', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Activate_plastic_response_at_interval", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'automatic_surface_generation', - 'gid_name': 'Automatic_surface_generation', - 'rules': [ + "field": "automatic_surface_generation", + "gid_name": "Automatic_surface_generation", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Define_yield_surfaces_based_on_shear_modulus_reduction_curve', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Define_yield_surfaces_based_on_shear_modulus_reduction_curve", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'HIDE', - 'field': 'Define_yield_surfaces_based_on_shear_modulus_reduction_curve', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Define_yield_surfaces_based_on_shear_modulus_reduction_curve", + "target": "#CURRENT#", }, ], }, @@ -76,30 +76,36 @@ class PressuredependmultiyieldSpec(BaseModel): } ) - material: Literal['PressureDependMultiYield'] = 'PressureDependMultiYield' - soil_type: Literal['Loose_Sand', 'Medium_Sand', 'Medium-dense_Sand', 'Dense_Sand', 'Custom'] = 'Custom' - saturated_soil_mass_density: str = '1.7ton/m^3' # TODO: unit-aware type - reference_shear_modulus_gr: str = '55MPa' # TODO: unit-aware type - reference_bulk_modulus: str = '150MPa' # TODO: unit-aware type + material: Literal["PressureDependMultiYield"] = "PressureDependMultiYield" + soil_type: Literal["Loose_Sand", "Medium_Sand", "Medium-dense_Sand", "Dense_Sand", "Custom"] = ( + "Custom" + ) + saturated_soil_mass_density: str = "1.7ton/m^3" # TODO: unit-aware type + reference_shear_modulus_gr: str = "55MPa" # TODO: unit-aware type + reference_bulk_modulus: str = "150MPa" # TODO: unit-aware type shear_strain_at_which_maximum_stress_is_reached: float = 0.1 - friction_angle: int = Field(29, description='Enter angle in degrees') - reference_mean_effective_confining_pressure_pr: str = '80kPa' # TODO: unit-aware type + friction_angle: int = Field(29, description="Enter angle in degrees") + reference_mean_effective_confining_pressure_pr: str = "80kPa" # TODO: unit-aware type positive_constant_d: float = 0.5 - phase_transformation_angle: int = Field(29, description='Enter angle in degrees') - contraction_rate_constant: float = Field(0.21, description='A non-negative constant defining the rate of shear-induced volume decrease (contraction) or pore pressure buildup. A larger value corresponds to faster contraction rate.') + phase_transformation_angle: int = Field(29, description="Enter angle in degrees") + contraction_rate_constant: float = Field( + 0.21, + description="A non-negative constant defining the rate of shear-induced volume decrease (contraction) or pore pressure buildup. A larger value corresponds to faster contraction rate.", + ) dilation_rate_constant_1: float = 0.0 dilation_rate_constant_2: float = 0.0 - liquefaction_parameter_1: str = '10kPa' # TODO: unit-aware type + liquefaction_parameter_1: str = "10kPa" # TODO: unit-aware type liquefaction_parameter_2: float = 0.02 - liquefaction_parameter_3: float = Field(1.0, description='liquefac3 takes a value between 0.0 and 3.0.') + liquefaction_parameter_3: float = Field( + 1.0, description="liquefac3 takes a value between 0.0 and 3.0." + ) initial_void_ratio: float = 0.85 critical_straight_line_parameter_cs1: float = 0.9 critical_straight_line_parameter_cs2: float = 0.02 critical_straight_line_parameter_cs3: float = 0.07 - atmospheric_pressure_for_normalization: str = '101kPa' # TODO: unit-aware type - numerical_constant: str = '0.3kPa' # TODO: unit-aware type - yield_surfaces: int = Field(20, description='Yield surfaces must be less than 40!') - activate_plastic_response: Literal['1', '0'] = '0' + atmospheric_pressure_for_normalization: str = "101kPa" # TODO: unit-aware type + numerical_constant: str = "0.3kPa" # TODO: unit-aware type + yield_surfaces: int = Field(20, description="Yield surfaces must be less than 40!") + activate_plastic_response: Literal["1", "0"] = "0" activate_plastic_response_at_interval: int = 2 - automatic_surface_generation: Literal['0', '1'] = '1' - + automatic_surface_generation: Literal["0", "1"] = "1" diff --git a/src/otko/core/catalog/generated/pressuredependmultiyield02.py b/src/otko/core/catalog/generated/pressuredependmultiyield02.py index 253454d..b60cd29 100644 --- a/src/otko/core/catalog/generated/pressuredependmultiyield02.py +++ b/src/otko/core/catalog/generated/pressuredependmultiyield02.py @@ -17,56 +17,56 @@ from pydantic import BaseModel, ConfigDict, Field class Pressuredependmultiyield02Spec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'PressureDependMultiYield02', - 'x-book': 'Multidimensional_(nD)_Materials', - 'dependencies': [ + "x-gid-name": "PressureDependMultiYield02", + "x-book": "Multidimensional_(nD)_Materials", + "dependencies": [ { - 'field': 'activate_plastic_response', - 'gid_name': 'Activate_plastic_response', - 'rules': [ + "field": "activate_plastic_response", + "gid_name": "Activate_plastic_response", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'Activate_plastic_response_at_interval', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Activate_plastic_response_at_interval", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Activate_plastic_response_at_interval', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Activate_plastic_response_at_interval", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'automatic_surface_generation', - 'gid_name': 'Automatic_surface_generation', - 'rules': [ + "field": "automatic_surface_generation", + "gid_name": "Automatic_surface_generation", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Define_yield_surfaces_based_on_shear_modulus_reduction_curve', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Define_yield_surfaces_based_on_shear_modulus_reduction_curve", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'HIDE', - 'field': 'Define_yield_surfaces_based_on_shear_modulus_reduction_curve', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Define_yield_surfaces_based_on_shear_modulus_reduction_curve", + "target": "#CURRENT#", }, ], }, @@ -76,32 +76,33 @@ class Pressuredependmultiyield02Spec(BaseModel): } ) - material: Literal['PressureDependMultiYield02'] = 'PressureDependMultiYield02' - soil_type: Literal['Dr=30%', 'Dr=40%', 'Dr=50%', 'Dr=60%', 'Dr=75%', 'Custom'] = 'Custom' - saturated_soil_mass_density: str = '1.7ton/m^3' # TODO: unit-aware type - reference_shear_modulus_gr: str = '55MPa' # TODO: unit-aware type - reference_bulk_modulus: str = '150MPa' # TODO: unit-aware type + material: Literal["PressureDependMultiYield02"] = "PressureDependMultiYield02" + soil_type: Literal["Dr=30%", "Dr=40%", "Dr=50%", "Dr=60%", "Dr=75%", "Custom"] = "Custom" + saturated_soil_mass_density: str = "1.7ton/m^3" # TODO: unit-aware type + reference_shear_modulus_gr: str = "55MPa" # TODO: unit-aware type + reference_bulk_modulus: str = "150MPa" # TODO: unit-aware type shear_strain_at_which_maximum_stress_is_reached: float = 0.1 - friction_angle: int = Field(29, description='Enter angle in degrees') - reference_mean_effective_confining_pressure_pr: str = '80kPa' # TODO: unit-aware type + friction_angle: int = Field(29, description="Enter angle in degrees") + reference_mean_effective_confining_pressure_pr: str = "80kPa" # TODO: unit-aware type positive_constant_d: float = 0.5 - phase_transformation_angle: int = Field(29, description='Enter angle in degrees') + phase_transformation_angle: int = Field(29, description="Enter angle in degrees") contraction_constant_1: float = 0.87 contraction_constant_2: int = 5 - contraction_constant_3: float = Field(0.18, description='A non-negative constant reflecting Ks effect') + contraction_constant_3: float = Field( + 0.18, description="A non-negative constant reflecting Ks effect" + ) dilation_rate_constant_1: float = 0.0 dilation_rate_constant_2: float = 0.0 dilation_rate_constant_3: float = 0.0 - liquefaction_parameter_1: str = '10kPa' # TODO: unit-aware type + liquefaction_parameter_1: str = "10kPa" # TODO: unit-aware type liquefaction_parameter_2: float = 0.02 initial_void_ratio: float = 0.85 critical_straight_line_parameter_cs1: float = 0.9 critical_straight_line_parameter_cs2: float = 0.02 critical_straight_line_parameter_cs3: float = 0.07 - atmospheric_pressure_for_normalization: str = '101kPa' # TODO: unit-aware type - numerical_constant: str = '0.3kPa' # TODO: unit-aware type - yield_surfaces: int = Field(20, description='Yield surfaces must be less than 40!') - activate_plastic_response: Literal['1', '0'] = '0' + atmospheric_pressure_for_normalization: str = "101kPa" # TODO: unit-aware type + numerical_constant: str = "0.3kPa" # TODO: unit-aware type + yield_surfaces: int = Field(20, description="Yield surfaces must be less than 40!") + activate_plastic_response: Literal["1", "0"] = "0" activate_plastic_response_at_interval: int = 2 - automatic_surface_generation: Literal['0', '1'] = '1' - + automatic_surface_generation: Literal["0", "1"] = "1" diff --git a/src/otko/core/catalog/generated/pressureindependmultiyield.py b/src/otko/core/catalog/generated/pressureindependmultiyield.py index 89a9a68..d239293 100644 --- a/src/otko/core/catalog/generated/pressureindependmultiyield.py +++ b/src/otko/core/catalog/generated/pressureindependmultiyield.py @@ -17,56 +17,56 @@ from pydantic import BaseModel, ConfigDict, Field class PressureindependmultiyieldSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'PressureIndependMultiYield', - 'x-book': 'Multidimensional_(nD)_Materials', - 'dependencies': [ + "x-gid-name": "PressureIndependMultiYield", + "x-book": "Multidimensional_(nD)_Materials", + "dependencies": [ { - 'field': 'activate_plastic_response', - 'gid_name': 'Activate_plastic_response', - 'rules': [ + "field": "activate_plastic_response", + "gid_name": "Activate_plastic_response", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'Activate_plastic_response_at_interval', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Activate_plastic_response_at_interval", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Activate_plastic_response_at_interval', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Activate_plastic_response_at_interval", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'automatic_surface_generation', - 'gid_name': 'Automatic_surface_generation', - 'rules': [ + "field": "automatic_surface_generation", + "gid_name": "Automatic_surface_generation", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Define_yield_surfaces_based_on_shear_modulus_reduction_curve', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Define_yield_surfaces_based_on_shear_modulus_reduction_curve", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'HIDE', - 'field': 'Define_yield_surfaces_based_on_shear_modulus_reduction_curve', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Define_yield_surfaces_based_on_shear_modulus_reduction_curve", + "target": "#CURRENT#", }, ], }, @@ -76,18 +76,17 @@ class PressureindependmultiyieldSpec(BaseModel): } ) - material: Literal['PressureIndependMultiYield'] = 'PressureIndependMultiYield' - soil_type: Literal['Soft_Clay', 'Medium_Clay', 'Stiff_Clay', 'Custom'] = 'Custom' - saturated_soil_mass_density: str = '1.5ton/m^3' # TODO: unit-aware type - reference_shear_modulus_gr: str = '60MPa' # TODO: unit-aware type - reference_bulk_modulus: str = '300MPa' # TODO: unit-aware type - apparent_cohesion: str = '37kPa' # TODO: unit-aware type + material: Literal["PressureIndependMultiYield"] = "PressureIndependMultiYield" + soil_type: Literal["Soft_Clay", "Medium_Clay", "Stiff_Clay", "Custom"] = "Custom" + saturated_soil_mass_density: str = "1.5ton/m^3" # TODO: unit-aware type + reference_shear_modulus_gr: str = "60MPa" # TODO: unit-aware type + reference_bulk_modulus: str = "300MPa" # TODO: unit-aware type + apparent_cohesion: str = "37kPa" # TODO: unit-aware type shear_strain_at_which_maximum_stress_is_reached: float = 0.1 - friction_angle: float = Field(0.0, description='Enter angle in degrees') - reference_mean_effective_confining_pressure_pr: str = '100kPa' # TODO: unit-aware type + friction_angle: float = Field(0.0, description="Enter angle in degrees") + reference_mean_effective_confining_pressure_pr: str = "100kPa" # TODO: unit-aware type positive_constant_d: float = 0.0 - yield_surfaces: int = Field(20, description='Yield surfaces must be less than 40!') - activate_plastic_response: Literal['1', '0'] = '0' + yield_surfaces: int = Field(20, description="Yield surfaces must be less than 40!") + activate_plastic_response: Literal["1", "0"] = "0" activate_plastic_response_at_interval: int = 2 - automatic_surface_generation: Literal['0', '1'] = '1' - + automatic_surface_generation: Literal["0", "1"] = "1" diff --git a/src/otko/core/catalog/generated/pysimple1.py b/src/otko/core/catalog/generated/pysimple1.py index d2c6230..73e8724 100644 --- a/src/otko/core/catalog/generated/pysimple1.py +++ b/src/otko/core/catalog/generated/pysimple1.py @@ -23,30 +23,30 @@ class CyclicDataRow(BaseModel): class Pysimple1Spec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'PySimple1', - 'x-book': 'Standard_Uniaxial_Materials', - 'dependencies': [ + "x-gid-name": "PySimple1", + "x-book": "Standard_Uniaxial_Materials", + "dependencies": [ { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -56,12 +56,21 @@ class Pysimple1Spec(BaseModel): } ) - material: Literal['_'] = '_' - p_y_backbone_curve: Literal['Soft_clay', 'Sand'] = Field('Soft_clay', description='Soft clay corresponds to Matlock (1970) relation. Sand corresponds to API (1993) relation') - ultimate_capacity_pult: str = '4000 kN' # TODO: unit-aware type - displacement_at_which_the_50_of_pult_is_mobilized_in_monotonic_loading: str = Field('0.02 m', description='Displacement at which the 50% of pult is mobilized in monotonic loading') # TODO: unit-aware type - coefficient_cd: float = Field(0.0, description='Sets the drag resistance within a fully-mobilized gap as Cd*pult') - viscous_damping_coefficient: str = Field('0.0 kNsec/m', description='Nonzero values are used to represent radiation damping effects') # TODO: unit-aware type - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + material: Literal["_"] = "_" + p_y_backbone_curve: Literal["Soft_clay", "Sand"] = Field( + "Soft_clay", + description="Soft clay corresponds to Matlock (1970) relation. Sand corresponds to API (1993) relation", + ) + ultimate_capacity_pult: str = "4000 kN" # TODO: unit-aware type + displacement_at_which_the_50_of_pult_is_mobilized_in_monotonic_loading: str = Field( + "0.02 m", + description="Displacement at which the 50% of pult is mobilized in monotonic loading", + ) # TODO: unit-aware type + coefficient_cd: float = Field( + 0.0, description="Sets the drag resistance within a fully-mobilized gap as Cd*pult" + ) + viscous_damping_coefficient: str = Field( + "0.0 kNsec/m", description="Nonzero values are used to represent radiation damping effects" + ) # TODO: unit-aware type + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - diff --git a/src/otko/core/catalog/generated/quad.py b/src/otko/core/catalog/generated/quad.py index 2b31ef5..c283988 100644 --- a/src/otko/core/catalog/generated/quad.py +++ b/src/otko/core/catalog/generated/quad.py @@ -17,20 +17,25 @@ from pydantic import BaseModel, ConfigDict, Field class QuadSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Quad', - 'x-book': 'Surface_Elements', + "x-gid-name": "Quad", + "x-book": "Surface_Elements", } ) - element_type: Literal['Quad'] = Field('Quad', description='Four node quadrilateral element object which uses a bilinear isoparametric formulation') - compatible_materials: Literal['nD_Materials'] = 'nD_Materials' - define_equaldof_between_nodes_of_the_same_vertical_location: Literal['0', '1'] = Field('0', description='equalDOF commands are automatically printed for element nodes which share the same vertical (Y) location') - material_mat_multidimensional_nd_materials_user_materials: str = 'Elastic_Isotropic' - plane_behavior: Literal['PlaneStrain', 'PlaneStress'] = 'PlaneStrain' - thickness: str = '1m' # TODO: unit-aware type - surface_pressure: str = '0.0kPa' # TODO: unit-aware type - applied_on_the_global_axes: Literal['_'] = '_' - x_direction: str = '0.0 kN/m^3' # TODO: unit-aware type - y_direction: str = '0.0 kN/m^3' # TODO: unit-aware type - mass_density: str = '0.0 ton/m^3' # TODO: unit-aware type - + element_type: Literal["Quad"] = Field( + "Quad", + description="Four node quadrilateral element object which uses a bilinear isoparametric formulation", + ) + compatible_materials: Literal["nD_Materials"] = "nD_Materials" + define_equaldof_between_nodes_of_the_same_vertical_location: Literal["0", "1"] = Field( + "0", + description="equalDOF commands are automatically printed for element nodes which share the same vertical (Y) location", + ) + material_mat_multidimensional_nd_materials_user_materials: str = "Elastic_Isotropic" + plane_behavior: Literal["PlaneStrain", "PlaneStress"] = "PlaneStrain" + thickness: str = "1m" # TODO: unit-aware type + surface_pressure: str = "0.0kPa" # TODO: unit-aware type + applied_on_the_global_axes: Literal["_"] = "_" + x_direction: str = "0.0 kN/m^3" # TODO: unit-aware type + y_direction: str = "0.0 kN/m^3" # TODO: unit-aware type + mass_density: str = "0.0 ton/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/quadup.py b/src/otko/core/catalog/generated/quadup.py index a1fb541..6393d90 100644 --- a/src/otko/core/catalog/generated/quadup.py +++ b/src/otko/core/catalog/generated/quadup.py @@ -17,30 +17,30 @@ from pydantic import BaseModel, ConfigDict, Field class QuadupSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'QuadUP', - 'x-book': 'Surface_Elements', - 'dependencies': [ + "x-gid-name": "QuadUP", + "x-book": "Surface_Elements", + "dependencies": [ { - 'field': 'update_permeabilities', - 'gid_name': 'Update_permeabilities', - 'rules': [ + "field": "update_permeabilities", + "gid_name": "Update_permeabilities", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'Update_permeabilities_at_interval', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Update_permeabilities_at_interval", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Update_permeabilities_at_interval', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Update_permeabilities_at_interval", + "target": "#CURRENT#", }, ], }, @@ -50,18 +50,28 @@ class QuadupSpec(BaseModel): } ) - element_type: Literal['QuadUP'] = Field('QuadUP', description='Four-node plane-strain element using bilinear isoparametric formulation') - compatible_materials: Literal['_'] = '_' - define_equal_translational_dof_between_nodes_of_the_same_vertical_location: Literal['0', '1'] = Field('0', description='equalDOF commands are automatically printed for element nodes which share the same vertical (Y) location') - material_mat_multidimensional_nd_materials_user_materials: str = 'PressureIndependMultiYield' - thickness: str = '1.0m' # TODO: unit-aware type - combined_undrained_bulk_modulus_bc: str = '2.2GPa' # TODO: unit-aware type - fluid_mass_density: str = '1.0ton/m^3' # TODO: unit-aware type - permeability_coefficient_in_horizontal_direction: str = '1.0e-3 m/sec' # TODO: unit-aware type - permeability_coefficient_in_vertical_direction: str = '1.0e-3 m/sec' # TODO: unit-aware type - update_permeabilities: Literal['1', '0'] = Field('0', description='Update permeabilities at the start of the selected interval (before analysis options)') + element_type: Literal["QuadUP"] = Field( + "QuadUP", + description="Four-node plane-strain element using bilinear isoparametric formulation", + ) + compatible_materials: Literal["_"] = "_" + define_equal_translational_dof_between_nodes_of_the_same_vertical_location: Literal[ + "0", "1" + ] = Field( + "0", + description="equalDOF commands are automatically printed for element nodes which share the same vertical (Y) location", + ) + material_mat_multidimensional_nd_materials_user_materials: str = "PressureIndependMultiYield" + thickness: str = "1.0m" # TODO: unit-aware type + combined_undrained_bulk_modulus_bc: str = "2.2GPa" # TODO: unit-aware type + fluid_mass_density: str = "1.0ton/m^3" # TODO: unit-aware type + permeability_coefficient_in_horizontal_direction: str = "1.0e-3 m/sec" # TODO: unit-aware type + permeability_coefficient_in_vertical_direction: str = "1.0e-3 m/sec" # TODO: unit-aware type + update_permeabilities: Literal["1", "0"] = Field( + "0", + description="Update permeabilities at the start of the selected interval (before analysis options)", + ) update_permeabilities_at_interval: int = 1 - uniform_normal_traction: str = '0.0kPa' # TODO: unit-aware type - x_acceleration: str = '0.0m/sec^2' # TODO: unit-aware type - y_acceleration: str = '0.0m/sec^2' # TODO: unit-aware type - + uniform_normal_traction: str = "0.0kPa" # TODO: unit-aware type + x_acceleration: str = "0.0m/sec^2" # TODO: unit-aware type + y_acceleration: str = "0.0m/sec^2" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/qzsimple1.py b/src/otko/core/catalog/generated/qzsimple1.py index 4641469..61fdbbc 100644 --- a/src/otko/core/catalog/generated/qzsimple1.py +++ b/src/otko/core/catalog/generated/qzsimple1.py @@ -23,30 +23,30 @@ class CyclicDataRow(BaseModel): class Qzsimple1Spec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'QzSimple1', - 'x-book': 'Standard_Uniaxial_Materials', - 'dependencies': [ + "x-gid-name": "QzSimple1", + "x-book": "Standard_Uniaxial_Materials", + "dependencies": [ { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -56,12 +56,22 @@ class Qzsimple1Spec(BaseModel): } ) - material: Literal['_'] = '_' - q_z_backbone_curve: Literal["Reese_and_O'Neill_relation", 'Vijayvergiya_relation'] = Field("Reese_and_O'Neill_relation", description="Reese and O'Neil relation is for drilled shafts in clay. Vijayvergiya relation is for piles in sand.") - ultimate_capacity_qult: str = '4000 kN' # TODO: unit-aware type - displacement_at_which_the_50_of_qult_is_mobilized_in_monotonic_loading: str = Field('0.02 m', description='Displacement at which the 50% of qult is mobilized in monotonic loading') # TODO: unit-aware type - suction: float = Field(0.0, description='Uplift resistance is equal to suction*qult. Valid values: between 0.0 - 0.1') - viscous_damping_coefficient: str = Field('0.0 kNsec/m', description='Nonzero values are used to represent radiation damping effects') # TODO: unit-aware type - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + material: Literal["_"] = "_" + q_z_backbone_curve: Literal["Reese_and_O'Neill_relation", "Vijayvergiya_relation"] = Field( + "Reese_and_O'Neill_relation", + description="Reese and O'Neil relation is for drilled shafts in clay. Vijayvergiya relation is for piles in sand.", + ) + ultimate_capacity_qult: str = "4000 kN" # TODO: unit-aware type + displacement_at_which_the_50_of_qult_is_mobilized_in_monotonic_loading: str = Field( + "0.02 m", + description="Displacement at which the 50% of qult is mobilized in monotonic loading", + ) # TODO: unit-aware type + suction: float = Field( + 0.0, + description="Uplift resistance is equal to suction*qult. Valid values: between 0.0 - 0.1", + ) + viscous_damping_coefficient: str = Field( + "0.0 kNsec/m", description="Nonzero values are used to represent radiation damping effects" + ) # TODO: unit-aware type + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - diff --git a/src/otko/core/catalog/generated/ramberg_osgood_steel.py b/src/otko/core/catalog/generated/ramberg_osgood_steel.py index 2b451c3..e029923 100644 --- a/src/otko/core/catalog/generated/ramberg_osgood_steel.py +++ b/src/otko/core/catalog/generated/ramberg_osgood_steel.py @@ -23,30 +23,30 @@ class CyclicDataRow(BaseModel): class RambergOsgoodSteelSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Ramberg-Osgood steel', - 'x-book': 'Uniaxial_Steel_Materials', - 'dependencies': [ + "x-gid-name": "Ramberg-Osgood steel", + "x-book": "Uniaxial_Steel_Materials", + "dependencies": [ { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -56,14 +56,13 @@ class RambergOsgoodSteelSpec(BaseModel): } ) - material: Literal['_'] = '_' - steel_grade: Literal['B500C', 'Custom'] = 'Custom' - yield_stress_fy: str = '500 MPa' # TODO: unit-aware type - initial_elastic_tangent_e0: str = '200 GPa' # TODO: unit-aware type + material: Literal["_"] = "_" + steel_grade: Literal["B500C", "Custom"] = "Custom" + yield_stress_fy: str = "500 MPa" # TODO: unit-aware type + initial_elastic_tangent_e0: str = "200 GPa" # TODO: unit-aware type yield_offset: float = 0.002 parameter_n_to_control_transition_from_elastic_to_plastic_branches: int = 5 - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/catalog/generated/reinforcing_steel.py b/src/otko/core/catalog/generated/reinforcing_steel.py index 0cfb1ec..cf6f384 100644 --- a/src/otko/core/catalog/generated/reinforcing_steel.py +++ b/src/otko/core/catalog/generated/reinforcing_steel.py @@ -23,30 +23,30 @@ class CyclicDataRow(BaseModel): class ReinforcingSteelSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Reinforcing_steel', - 'x-book': 'Uniaxial_Steel_Materials', - 'dependencies': [ + "x-gid-name": "Reinforcing_steel", + "x-book": "Uniaxial_Steel_Materials", + "dependencies": [ { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -56,16 +56,15 @@ class ReinforcingSteelSpec(BaseModel): } ) - material: Literal['_'] = '_' - steel_grade: Literal['B500C', 'Custom'] = 'Custom' - yield_stress_fy: str = '500 MPa' # TODO: unit-aware type - ultimate_stress_fsu: str = '575 MPa' # TODO: unit-aware type - initial_elastic_tangent_es: str = '200 GPa' # TODO: unit-aware type - tangent_at_initial_strain_hardening_esh: str = '20 GPa' # TODO: unit-aware type + material: Literal["_"] = "_" + steel_grade: Literal["B500C", "Custom"] = "Custom" + yield_stress_fy: str = "500 MPa" # TODO: unit-aware type + ultimate_stress_fsu: str = "575 MPa" # TODO: unit-aware type + initial_elastic_tangent_es: str = "200 GPa" # TODO: unit-aware type + tangent_at_initial_strain_hardening_esh: str = "20 GPa" # TODO: unit-aware type strain_corresponding_to_initial_strain_hardening_esh: float = 5e-3 strain_at_peak_stress_esu: float = 7.5e-2 - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/catalog/generated/section_aggregator.py b/src/otko/core/catalog/generated/section_aggregator.py index 9a6b95a..dc5c4d3 100644 --- a/src/otko/core/catalog/generated/section_aggregator.py +++ b/src/otko/core/catalog/generated/section_aggregator.py @@ -17,186 +17,186 @@ from pydantic import BaseModel, ConfigDict, Field class SectionAggregatorSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Section_Aggregator', - 'x-book': 'Combined_Materials', - 'dependencies': [ + "x-gid-name": "Section_Aggregator", + "x-book": "Combined_Materials", + "dependencies": [ { - 'field': 'select_section', - 'gid_name': 'Select_section', - 'rules': [ + "field": "select_section", + "gid_name": "Select_section", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Section_to_be_aggregated', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Section_to_be_aggregated", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'Section_to_be_aggregated', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Section_to_be_aggregated", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_p', - 'gid_name': 'Activate_P', - 'rules': [ + "field": "activate_p", + "gid_name": "Activate_P", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Axial_force-deformation', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Axial_force-deformation", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Axial_force-deformation', - 'target': '#CURRENT#', + "action": "SET", + "field": "Axial_force-deformation", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_mz', - 'gid_name': 'Activate_Mz', - 'rules': [ + "field": "activate_mz", + "gid_name": "Activate_Mz", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Moment-curvature_about_local_z-z', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment-curvature_about_local_z-z", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Moment-curvature_about_local_z-z', - 'target': '#CURRENT#', + "action": "SET", + "field": "Moment-curvature_about_local_z-z", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_vy', - 'gid_name': 'Activate_Vy', - 'rules': [ + "field": "activate_vy", + "gid_name": "Activate_Vy", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Shear_force-deformation_along_local_y-y', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Shear_force-deformation_along_local_y-y", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Shear_force-deformation_along_local_y-y', - 'target': '#CURRENT#', + "action": "SET", + "field": "Shear_force-deformation_along_local_y-y", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_my', - 'gid_name': 'Activate_My', - 'rules': [ + "field": "activate_my", + "gid_name": "Activate_My", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Moment-curvature_about_local_y-y', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment-curvature_about_local_y-y", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Moment-curvature_about_local_y-y', - 'target': '#CURRENT#', + "action": "SET", + "field": "Moment-curvature_about_local_y-y", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_vz', - 'gid_name': 'Activate_Vz', - 'rules': [ + "field": "activate_vz", + "gid_name": "Activate_Vz", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Shear_force-deformation_along_local_z-z', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Shear_force-deformation_along_local_z-z", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Shear_force-deformation_along_local_z-z', - 'target': '#CURRENT#', + "action": "SET", + "field": "Shear_force-deformation_along_local_z-z", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_t', - 'gid_name': 'Activate_T', - 'rules': [ + "field": "activate_t", + "gid_name": "Activate_T", + "rules": [ { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Torsion_force-deformation', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Torsion_force-deformation", + "target": "#CURRENT#", }, ], }, { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Torsion_force-deformation', - 'target': '#CURRENT#', + "action": "SET", + "field": "Torsion_force-deformation", + "target": "#CURRENT#", }, ], }, @@ -206,21 +206,20 @@ class SectionAggregatorSpec(BaseModel): } ) - section: Literal['SectionAggregator'] = 'SectionAggregator' - compatible_materials: Literal['_'] = '_' - compatible_sections: Literal['_'] = '_' - select_section: Literal['1', '0'] = '1' - section_to_be_aggregated: str = 'Fiber' - activate_p: Literal['1', '0'] = '0' - axial_force_deformation: str = 'Elastic' - activate_mz: Literal['1', '0'] = '0' - moment_curvature_about_local_z_z: str = 'Elastic' - activate_vy: Literal['1', '0'] = '0' - shear_force_deformation_along_local_y_y: str = 'Elastic' - activate_my: Literal['1', '0'] = '0' - moment_curvature_about_local_y_y: str = 'Elastic' - activate_vz: Literal['1', '0'] = '0' - shear_force_deformation_along_local_z_z: str = 'Elastic' - activate_t: Literal['1', '0'] = '0' - torsion_force_deformation: str = 'Elastic' - + section: Literal["SectionAggregator"] = "SectionAggregator" + compatible_materials: Literal["_"] = "_" + compatible_sections: Literal["_"] = "_" + select_section: Literal["1", "0"] = "1" + section_to_be_aggregated: str = "Fiber" + activate_p: Literal["1", "0"] = "0" + axial_force_deformation: str = "Elastic" + activate_mz: Literal["1", "0"] = "0" + moment_curvature_about_local_z_z: str = "Elastic" + activate_vy: Literal["1", "0"] = "0" + shear_force_deformation_along_local_y_y: str = "Elastic" + activate_my: Literal["1", "0"] = "0" + moment_curvature_about_local_y_y: str = "Elastic" + activate_vz: Literal["1", "0"] = "0" + shear_force_deformation_along_local_z_z: str = "Elastic" + activate_t: Literal["1", "0"] = "0" + torsion_force_deformation: str = "Elastic" diff --git a/src/otko/core/catalog/generated/series.py b/src/otko/core/catalog/generated/series.py index 6eb399c..47f00ee 100644 --- a/src/otko/core/catalog/generated/series.py +++ b/src/otko/core/catalog/generated/series.py @@ -17,134 +17,134 @@ from pydantic import BaseModel, ConfigDict, Field class SeriesSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Series', - 'x-book': 'Combined_Materials', - 'dependencies': [ + "x-gid-name": "Series", + "x-book": "Combined_Materials", + "dependencies": [ { - 'field': 'activate_1st_material', - 'gid_name': 'Activate_1st_material', - 'rules': [ + "field": "activate_1st_material", + "gid_name": "Activate_1st_material", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Material_1', - 'target': '#CURRENT#', + "action": "SET", + "field": "Material_1", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Material_1', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_1", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_2nd_material', - 'gid_name': 'Activate_2nd_material', - 'rules': [ + "field": "activate_2nd_material", + "gid_name": "Activate_2nd_material", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Material_2', - 'target': '#CURRENT#', + "action": "SET", + "field": "Material_2", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Material_2', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_2", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_3rd_material', - 'gid_name': 'Activate_3rd_material', - 'rules': [ + "field": "activate_3rd_material", + "gid_name": "Activate_3rd_material", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Material_3', - 'target': '#CURRENT#', + "action": "SET", + "field": "Material_3", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Material_3', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_3", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_4th_material', - 'gid_name': 'Activate_4th_Material', - 'rules': [ + "field": "activate_4th_material", + "gid_name": "Activate_4th_Material", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Material_4', - 'target': '#CURRENT#', + "action": "SET", + "field": "Material_4", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Material_4', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_4", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'activate_5th_material', - 'gid_name': 'Activate_5th_material', - 'rules': [ + "field": "activate_5th_material", + "gid_name": "Activate_5th_material", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'SET', - 'field': 'Material_5', - 'target': '#CURRENT#', + "action": "SET", + "field": "Material_5", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Material_5', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Material_5", + "target": "#CURRENT#", }, ], }, @@ -154,15 +154,14 @@ class SeriesSpec(BaseModel): } ) - material: Literal['Series'] = 'Series' - activate_1st_material: Literal['0', '1'] = '1' - material_1: str = 'Elastic' - activate_2nd_material: Literal['0', '1'] = '1' - material_2: str = 'Elastic' - activate_3rd_material: Literal['0', '1'] = '0' - material_3: str = 'Elastic' - activate_4th_material: Literal['0', '1'] = '0' - material_4: str = 'Elastic' - activate_5th_material: Literal['0', '1'] = '0' - material_5: str = 'Elastic' - + material: Literal["Series"] = "Series" + activate_1st_material: Literal["0", "1"] = "1" + material_1: str = "Elastic" + activate_2nd_material: Literal["0", "1"] = "1" + material_2: str = "Elastic" + activate_3rd_material: Literal["0", "1"] = "0" + material_3: str = "Elastic" + activate_4th_material: Literal["0", "1"] = "0" + material_4: str = "Elastic" + activate_5th_material: Literal["0", "1"] = "0" + material_5: str = "Elastic" diff --git a/src/otko/core/catalog/generated/shell.py b/src/otko/core/catalog/generated/shell.py index 1991283..e47d08a 100644 --- a/src/otko/core/catalog/generated/shell.py +++ b/src/otko/core/catalog/generated/shell.py @@ -17,13 +17,17 @@ from pydantic import BaseModel, ConfigDict, Field class ShellSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Shell', - 'x-book': 'Surface_Elements', + "x-gid-name": "Shell", + "x-book": "Surface_Elements", } ) - element_type: Literal['Shell'] = Field('Shell', description='A Shel element object using a bilinear isoparametric formulation with modified shear interpolation') - compatible_materials: Literal['PlateFiber/ElasticMembranePlate'] = 'PlateFiber/ElasticMembranePlate' - type: str = 'Elastic_Membrane_Plate' - weight_density: str = '25 kN/m^3' # TODO: unit-aware type - + element_type: Literal["Shell"] = Field( + "Shell", + description="A Shel element object using a bilinear isoparametric formulation with modified shear interpolation", + ) + compatible_materials: Literal["PlateFiber/ElasticMembranePlate"] = ( + "PlateFiber/ElasticMembranePlate" + ) + type: str = "Elastic_Membrane_Plate" + weight_density: str = "25 kN/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/shelldkgq.py b/src/otko/core/catalog/generated/shelldkgq.py index 5fee640..030a9ea 100644 --- a/src/otko/core/catalog/generated/shelldkgq.py +++ b/src/otko/core/catalog/generated/shelldkgq.py @@ -17,13 +17,15 @@ from pydantic import BaseModel, ConfigDict, Field class ShelldkgqSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'ShellDKGQ', - 'x-book': 'Surface_Elements', + "x-gid-name": "ShellDKGQ", + "x-book": "Surface_Elements", } ) - element_type: Literal['ShellDKGQ'] = Field('ShellDKGQ', description='A ShellDKGQ element object is a quadrilateral shell element based on the theory of generalized conforming element.') - compatible_materials: Literal['_'] = '_' - type: str = 'Elastic_Membrane_Plate' - weight_density: str = '25 kN/m^3' # TODO: unit-aware type - + element_type: Literal["ShellDKGQ"] = Field( + "ShellDKGQ", + description="A ShellDKGQ element object is a quadrilateral shell element based on the theory of generalized conforming element.", + ) + compatible_materials: Literal["_"] = "_" + type: str = "Elastic_Membrane_Plate" + weight_density: str = "25 kN/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/standard_brick_element.py b/src/otko/core/catalog/generated/standard_brick_element.py index 8e91dd5..927ada6 100644 --- a/src/otko/core/catalog/generated/standard_brick_element.py +++ b/src/otko/core/catalog/generated/standard_brick_element.py @@ -17,16 +17,18 @@ from pydantic import BaseModel, ConfigDict, Field class StandardBrickElementSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Standard_Brick_Element', - 'x-book': 'Solid_Elements', + "x-gid-name": "Standard_Brick_Element", + "x-book": "Solid_Elements", } ) - element_type: Literal['stdBrick'] = Field('stdBrick', description='An eight-node brick element using a trilinear isoparametric formulation') - compatible_materials: Literal['nD_Materials'] = 'nD_Materials' - material_mat_multidimensional_nd_materials_user_materials: str = 'Elastic_Isotropic' - applied_on_the_global_axes: Literal['_'] = '_' - x_direction: str = '0.0kN/m^3' # TODO: unit-aware type - y_direction: str = '0.0kN/m^3' # TODO: unit-aware type - z_direction: str = '0.0kN/m^3' # TODO: unit-aware type - + element_type: Literal["stdBrick"] = Field( + "stdBrick", + description="An eight-node brick element using a trilinear isoparametric formulation", + ) + compatible_materials: Literal["nD_Materials"] = "nD_Materials" + material_mat_multidimensional_nd_materials_user_materials: str = "Elastic_Isotropic" + applied_on_the_global_axes: Literal["_"] = "_" + x_direction: str = "0.0kN/m^3" # TODO: unit-aware type + y_direction: str = "0.0kN/m^3" # TODO: unit-aware type + z_direction: str = "0.0kN/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/steel01.py b/src/otko/core/catalog/generated/steel01.py index a928a79..0f3c320 100644 --- a/src/otko/core/catalog/generated/steel01.py +++ b/src/otko/core/catalog/generated/steel01.py @@ -23,156 +23,156 @@ class CyclicDataRow(BaseModel): class Steel01Spec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Steel01', - 'x-book': 'Uniaxial_Steel_Materials', - 'dependencies': [ + "x-gid-name": "Steel01", + "x-book": "Uniaxial_Steel_Materials", + "dependencies": [ { - 'field': 'formulation', - 'gid_name': 'Formulation', - 'rules': [ + "field": "formulation", + "gid_name": "Formulation", + "rules": [ { - 'trigger': 'Stress-Strain', - 'actions': [ + "trigger": "Stress-Strain", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Yield_Stress_Fy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Yield_Stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Initial_elastic_tangent_E0', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Initial_elastic_tangent_E0", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_stiffness_K', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_My', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_My", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Force-Deformation', - 'actions': [ + "trigger": "Force-Deformation", + "actions": [ { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Yield_Stress_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Yield_Stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_elastic_tangent_E0', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_elastic_tangent_E0", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Force_Fy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Force_Fy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Initial_stiffness_K', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Initial_stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_My', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_My", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Moment-Rotation', - 'actions': [ + "trigger": "Moment-Rotation", + "actions": [ { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Yield_Stress_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Yield_Stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_elastic_tangent_E0', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_elastic_tangent_E0", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_stiffness_K', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_My', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_My", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -182,23 +182,27 @@ class Steel01Spec(BaseModel): } ) - material: Literal['Steel01'] = Field('Steel01', description='Uniaxial bilinear steel material object with kinematic hardening and optional isotropic hardening described by a non-linear evolution equation.') - formulation: Literal['Stress-Strain', 'Force-Deformation', 'Moment-Rotation'] = 'Stress-Strain' - steel_grade: Literal['S235', 'S275', 'S355', 'S450', 'B500C', 'Custom'] = 'Custom' - yield_stress_fy: str = '500 MPa' # TODO: unit-aware type - force_fy: str = '500 kN' # TODO: unit-aware type - moment_my: str = '250 kNm' # TODO: unit-aware type - initial_elastic_tangent_e0: str = '200 GPa' # TODO: unit-aware type - initial_stiffness_k: str = '5000 kN/m' # TODO: unit-aware type - moment_per_rotation_unit: str = '10000 kNm' # TODO: unit-aware type - strain_hardening_ratio_b: float = Field(0.02, description='Ratio between post-yield tangent and initial elastic tangent.') - isotropic_hardening_parameters: Literal['_'] = '_' + material: Literal["Steel01"] = Field( + "Steel01", + description="Uniaxial bilinear steel material object with kinematic hardening and optional isotropic hardening described by a non-linear evolution equation.", + ) + formulation: Literal["Stress-Strain", "Force-Deformation", "Moment-Rotation"] = "Stress-Strain" + steel_grade: Literal["S235", "S275", "S355", "S450", "B500C", "Custom"] = "Custom" + yield_stress_fy: str = "500 MPa" # TODO: unit-aware type + force_fy: str = "500 kN" # TODO: unit-aware type + moment_my: str = "250 kNm" # TODO: unit-aware type + initial_elastic_tangent_e0: str = "200 GPa" # TODO: unit-aware type + initial_stiffness_k: str = "5000 kN/m" # TODO: unit-aware type + moment_per_rotation_unit: str = "10000 kNm" # TODO: unit-aware type + strain_hardening_ratio_b: float = Field( + 0.02, description="Ratio between post-yield tangent and initial elastic tangent." + ) + isotropic_hardening_parameters: Literal["_"] = "_" isotropic_hardening_parameter_a1: int = 0 isotropic_hardening_parameter_a2: int = 1 isotropic_hardening_parameter_a3: int = 0 isotropic_hardening_parameter_a4: int = 1 - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/catalog/generated/steel02.py b/src/otko/core/catalog/generated/steel02.py index 9fe1c55..19b2c51 100644 --- a/src/otko/core/catalog/generated/steel02.py +++ b/src/otko/core/catalog/generated/steel02.py @@ -23,201 +23,201 @@ class CyclicDataRow(BaseModel): class Steel02Spec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Steel02', - 'x-book': 'Uniaxial_Steel_Materials', - 'dependencies': [ + "x-gid-name": "Steel02", + "x-book": "Uniaxial_Steel_Materials", + "dependencies": [ { - 'field': 'formulation', - 'gid_name': 'Formulation', - 'rules': [ + "field": "formulation", + "gid_name": "Formulation", + "rules": [ { - 'trigger': 'Stress-Strain', - 'actions': [ + "trigger": "Stress-Strain", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Yield_Stress_Fy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Yield_Stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Initial_elastic_tangent_E0', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Initial_elastic_tangent_E0", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_stiffness_K', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_My', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_My", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Initial_stress', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Initial_stress", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_force', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_force", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_moment', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_moment", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Force-Deformation', - 'actions': [ + "trigger": "Force-Deformation", + "actions": [ { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Yield_Stress_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Yield_Stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_elastic_tangent_E0', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_elastic_tangent_E0", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Force_Fy', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Force_Fy", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Initial_stiffness_K', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Initial_stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_My', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_My", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_stress', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_stress", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Initial_force', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Initial_force", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_moment', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_moment", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Moment-Rotation', - 'actions': [ + "trigger": "Moment-Rotation", + "actions": [ { - 'action': 'HIDE', - 'field': 'Steel_grade', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Steel_grade", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Yield_Stress_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Yield_Stress_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_elastic_tangent_E0', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_elastic_tangent_E0", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Force_Fy', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Force_Fy", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_stiffness_K', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_stiffness_K", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_My', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_My", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Moment_per_rotation_unit', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Moment_per_rotation_unit", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_stress', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_stress", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Initial_force', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Initial_force", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Initial_moment', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Initial_moment", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -227,30 +227,34 @@ class Steel02Spec(BaseModel): } ) - material: Literal['Steel02'] = Field('Steel02', description='uniaxial Giuffre-Menegotto-Pinto steel material object with isotropic strain hardening') - formulation: Literal['Stress-Strain', 'Force-Deformation', 'Moment-Rotation'] = 'Stress-Strain' - steel_grade: Literal['S235', 'S275', 'S355', 'S450', 'B500C', 'Custom'] = 'Custom' - yield_stress_fy: str = '500 MPa' # TODO: unit-aware type - force_fy: str = '500 kN' # TODO: unit-aware type - moment_my: str = '250 kNm' # TODO: unit-aware type - initial_elastic_tangent_e0: str = '200 GPa' # TODO: unit-aware type - initial_stiffness_k: str = '5000 kN/m' # TODO: unit-aware type - moment_per_rotation_unit: str = '10000 kNm' # TODO: unit-aware type - strain_hardening_ratio_b: float = Field(0.02, description='Ratio between post-yield tangent and initial elastic tangent.') - parameters_to_control_the_transition: Literal['_'] = '_' + material: Literal["Steel02"] = Field( + "Steel02", + description="uniaxial Giuffre-Menegotto-Pinto steel material object with isotropic strain hardening", + ) + formulation: Literal["Stress-Strain", "Force-Deformation", "Moment-Rotation"] = "Stress-Strain" + steel_grade: Literal["S235", "S275", "S355", "S450", "B500C", "Custom"] = "Custom" + yield_stress_fy: str = "500 MPa" # TODO: unit-aware type + force_fy: str = "500 kN" # TODO: unit-aware type + moment_my: str = "250 kNm" # TODO: unit-aware type + initial_elastic_tangent_e0: str = "200 GPa" # TODO: unit-aware type + initial_stiffness_k: str = "5000 kN/m" # TODO: unit-aware type + moment_per_rotation_unit: str = "10000 kNm" # TODO: unit-aware type + strain_hardening_ratio_b: float = Field( + 0.02, description="Ratio between post-yield tangent and initial elastic tangent." + ) + parameters_to_control_the_transition: Literal["_"] = "_" parameter_r0: int = 20 parameter_cr1: float = 0.925 parameter_cr2: float = 0.15 - isotropic_hardening_parameters: Literal['_'] = '_' + isotropic_hardening_parameters: Literal["_"] = "_" isotropic_hardening_parameter_a1: int = 0 isotropic_hardening_parameter_a2: int = 1 isotropic_hardening_parameter_a3: int = 0 isotropic_hardening_parameter_a4: int = 1 - initial_stress: str = '0.0 MPa' # TODO: unit-aware type - initial_force: str = '0.0 kN' # TODO: unit-aware type - initial_moment: str = '0.0 kNm' # TODO: unit-aware type - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + initial_stress: str = "0.0 MPa" # TODO: unit-aware type + initial_force: str = "0.0 kN" # TODO: unit-aware type + initial_moment: str = "0.0 kNm" # TODO: unit-aware type + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/catalog/generated/tri31.py b/src/otko/core/catalog/generated/tri31.py index ada28d8..418d78b 100644 --- a/src/otko/core/catalog/generated/tri31.py +++ b/src/otko/core/catalog/generated/tri31.py @@ -17,19 +17,20 @@ from pydantic import BaseModel, ConfigDict, Field class Tri31Spec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Tri31', - 'x-book': 'Surface_Elements', + "x-gid-name": "Tri31", + "x-book": "Surface_Elements", } ) - element_type: Literal['Tri31'] = Field('Tri31', description='Constant strain triangular element') - compatible_materials: Literal['_'] = '_' - material_mat_multidimensional_nd_materials_user_materials: str = 'Elastic_Isotropic' - plane_behavior: Literal['PlaneStrain', 'PlaneStress'] = 'PlaneStrain' - thickness: str = '1.0 m' # TODO: unit-aware type - surface_pressure: str = '0.0 kPa' # TODO: unit-aware type - applied_on_the_global_axes: Literal['_'] = '_' - x_direction: str = '0.0 kN/m^3' # TODO: unit-aware type - y_direction: str = '0.0 kN/m^3' # TODO: unit-aware type - mass_density: str = '0.0ton/m^3' # TODO: unit-aware type - + element_type: Literal["Tri31"] = Field( + "Tri31", description="Constant strain triangular element" + ) + compatible_materials: Literal["_"] = "_" + material_mat_multidimensional_nd_materials_user_materials: str = "Elastic_Isotropic" + plane_behavior: Literal["PlaneStrain", "PlaneStress"] = "PlaneStrain" + thickness: str = "1.0 m" # TODO: unit-aware type + surface_pressure: str = "0.0 kPa" # TODO: unit-aware type + applied_on_the_global_axes: Literal["_"] = "_" + x_direction: str = "0.0 kN/m^3" # TODO: unit-aware type + y_direction: str = "0.0 kN/m^3" # TODO: unit-aware type + mass_density: str = "0.0ton/m^3" # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/truss.py b/src/otko/core/catalog/generated/truss.py index c789e73..a337c69 100644 --- a/src/otko/core/catalog/generated/truss.py +++ b/src/otko/core/catalog/generated/truss.py @@ -17,196 +17,196 @@ from pydantic import BaseModel, ConfigDict, Field class TrussSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Truss', - 'x-book': 'Truss_Elements', - 'dependencies': [ + "x-gid-name": "Truss", + "x-book": "Truss_Elements", + "dependencies": [ { - 'field': 'cross_section', - 'gid_name': 'Cross_section', - 'rules': [ + "field": "cross_section", + "gid_name": "Cross_section", + "rules": [ { - 'trigger': 'Rectangular', - 'actions': [ + "trigger": "Rectangular", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_Bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_Bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Circular', - 'actions': [ + "trigger": "Circular", + "actions": [ { - 'action': 'HIDE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_Bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_Bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Tee', - 'actions': [ + "trigger": "Tee", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Width_Bw', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Width_Bw", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Area_A", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'General', - 'actions': [ + "trigger": "General", + "actions": [ { - 'action': 'HIDE', - 'field': 'Height_h', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_h", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_B', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_B", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Diameter_d', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Diameter_d", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_bf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_bf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Height_hf', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Height_hf", + "target": "#CURRENT#", }, { - 'action': 'HIDE', - 'field': 'Width_Bw', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Width_Bw", + "target": "#CURRENT#", }, { - 'action': 'RESTORE', - 'field': 'Area_A', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Area_A", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'set_modification_factors', - 'gid_name': 'Set_modification_factors', - 'rules': [ + "field": "set_modification_factors", + "gid_name": "Set_modification_factors", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'mod._A', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "mod._A", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'mod._A', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "mod._A", + "target": "#CURRENT#", }, ], }, @@ -216,21 +216,22 @@ class TrussSpec(BaseModel): } ) - element_type: Literal['Truss'] = 'Truss' - compatible_materials: Literal['_'] = '_' - material: str = Field('Elastic', description='Choose one existing material to use its parameters.') - mass_density: str = '0.0ton/m^3' # TODO: unit-aware type - consider_consistent_mass_matrix: Literal['1', '0'] = '0' - include_rayleigh_damping: Literal['1', '0'] = '0' - weight_density: str = '0.0 kN/m^3' # TODO: unit-aware type - cross_section: Literal['Rectangular', 'Tee', 'Circular', 'General'] = 'General' - height_h: str = '0.5m' # TODO: unit-aware type - width_b: str = '0.3m' # TODO: unit-aware type - diameter_d: str = '0.5m' # TODO: unit-aware type - width_bf: str = '0.4m' # TODO: unit-aware type - height_hf: str = '0.15m' # TODO: unit-aware type - width_bw: str = '0.25m' # TODO: unit-aware type - area_a: str = '1.0 m^2' # TODO: unit-aware type - set_modification_factors: Literal['0', '1'] = '0' + element_type: Literal["Truss"] = "Truss" + compatible_materials: Literal["_"] = "_" + material: str = Field( + "Elastic", description="Choose one existing material to use its parameters." + ) + mass_density: str = "0.0ton/m^3" # TODO: unit-aware type + consider_consistent_mass_matrix: Literal["1", "0"] = "0" + include_rayleigh_damping: Literal["1", "0"] = "0" + weight_density: str = "0.0 kN/m^3" # TODO: unit-aware type + cross_section: Literal["Rectangular", "Tee", "Circular", "General"] = "General" + height_h: str = "0.5m" # TODO: unit-aware type + width_b: str = "0.3m" # TODO: unit-aware type + diameter_d: str = "0.5m" # TODO: unit-aware type + width_bf: str = "0.4m" # TODO: unit-aware type + height_hf: str = "0.15m" # TODO: unit-aware type + width_bw: str = "0.25m" # TODO: unit-aware type + area_a: str = "1.0 m^2" # TODO: unit-aware type + set_modification_factors: Literal["0", "1"] = "0" mod_a: int = 1 - diff --git a/src/otko/core/catalog/generated/tzsimple1.py b/src/otko/core/catalog/generated/tzsimple1.py index 819052a..39cb493 100644 --- a/src/otko/core/catalog/generated/tzsimple1.py +++ b/src/otko/core/catalog/generated/tzsimple1.py @@ -23,30 +23,30 @@ class CyclicDataRow(BaseModel): class Tzsimple1Spec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'TzSimple1', - 'x-book': 'Standard_Uniaxial_Materials', - 'dependencies': [ + "x-gid-name": "TzSimple1", + "x-book": "Standard_Uniaxial_Materials", + "dependencies": [ { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -56,11 +56,17 @@ class Tzsimple1Spec(BaseModel): } ) - material: Literal['_'] = '_' - t_z_backbone_curve: Literal["Reese_and_O'Neill_relation", 'Mosher_relation'] = "Reese_and_O'Neill_relation" - ultimate_capacity_tult: str = '4000 kN' # TODO: unit-aware type - displacement_at_which_the_50_of_tult_is_mobilized_in_monotonic_loading: str = Field('0.02 m', description='Displacement at which the 50% of tult is mobilized in monotonic loading') # TODO: unit-aware type - viscous_damping_coefficient: str = Field('0.0 kNsec/m', description='Nonzero values are used to represent radiation damping effects') # TODO: unit-aware type - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + material: Literal["_"] = "_" + t_z_backbone_curve: Literal["Reese_and_O'Neill_relation", "Mosher_relation"] = ( + "Reese_and_O'Neill_relation" + ) + ultimate_capacity_tult: str = "4000 kN" # TODO: unit-aware type + displacement_at_which_the_50_of_tult_is_mobilized_in_monotonic_loading: str = Field( + "0.02 m", + description="Displacement at which the 50% of tult is mobilized in monotonic loading", + ) # TODO: unit-aware type + viscous_damping_coefficient: str = Field( + "0.0 kNsec/m", description="Nonzero values are used to represent radiation damping effects" + ) # TODO: unit-aware type + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - diff --git a/src/otko/core/catalog/generated/user_material.py b/src/otko/core/catalog/generated/user_material.py index 5f43e7d..70adc8c 100644 --- a/src/otko/core/catalog/generated/user_material.py +++ b/src/otko/core/catalog/generated/user_material.py @@ -17,12 +17,11 @@ from pydantic import BaseModel, ConfigDict, Field class UserMaterialSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'User_Material', - 'x-book': 'User_Materials', + "x-gid-name": "User_Material", + "x-book": "User_Materials", } ) - material: Literal['UserMaterial'] = 'UserMaterial' - section: Literal['UserMaterial'] = 'UserMaterial' - width: str = Field('0.1m', description='Used to calculate self weight') # TODO: unit-aware type - + material: Literal["UserMaterial"] = "UserMaterial" + section: Literal["UserMaterial"] = "UserMaterial" + width: str = Field("0.1m", description="Used to calculate self weight") # TODO: unit-aware type diff --git a/src/otko/core/catalog/generated/viscous.py b/src/otko/core/catalog/generated/viscous.py index e659a9a..fd9c380 100644 --- a/src/otko/core/catalog/generated/viscous.py +++ b/src/otko/core/catalog/generated/viscous.py @@ -23,30 +23,30 @@ class CyclicDataRow(BaseModel): class ViscousSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Viscous', - 'x-book': 'Standard_Uniaxial_Materials', - 'dependencies': [ + "x-gid-name": "Viscous", + "x-book": "Standard_Uniaxial_Materials", + "dependencies": [ { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -56,11 +56,10 @@ class ViscousSpec(BaseModel): } ) - material: Literal['Viscous'] = 'Viscous' - damping_coefficient_c: str = '0.0 kNsec/m' # TODO: unit-aware type + material: Literal["Viscous"] = "Viscous" + damping_coefficient_c: str = "0.0 kNsec/m" # TODO: unit-aware type power_factor_alpha: int = 1 - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/catalog/generated/viscous_damper.py b/src/otko/core/catalog/generated/viscous_damper.py index c2c021d..1f53eac 100644 --- a/src/otko/core/catalog/generated/viscous_damper.py +++ b/src/otko/core/catalog/generated/viscous_damper.py @@ -23,56 +23,56 @@ class CyclicDataRow(BaseModel): class ViscousDamperSpec(BaseModel): model_config = ConfigDict( json_schema_extra={ - 'x-gid-name': 'Viscous_Damper', - 'x-book': 'Standard_Uniaxial_Materials', - 'dependencies': [ + "x-gid-name": "Viscous_Damper", + "x-book": "Standard_Uniaxial_Materials", + "dependencies": [ { - 'field': 'activate_gap_length', - 'gid_name': 'Activate_gap_length', - 'rules': [ + "field": "activate_gap_length", + "gid_name": "Activate_gap_length", + "rules": [ { - 'trigger': '0', - 'actions': [ + "trigger": "0", + "actions": [ { - 'action': 'HIDE', - 'field': 'Gap_length', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Gap_length", + "target": "#CURRENT#", }, ], }, { - 'trigger': '1', - 'actions': [ + "trigger": "1", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Gap_length', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Gap_length", + "target": "#CURRENT#", }, ], }, ], }, { - 'field': 'analysis_type', - 'gid_name': 'Analysis_type', - 'rules': [ + "field": "analysis_type", + "gid_name": "Analysis_type", + "rules": [ { - 'trigger': 'Monotonic', - 'actions': [ + "trigger": "Monotonic", + "actions": [ { - 'action': 'HIDE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "HIDE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, { - 'trigger': 'Cyclic', - 'actions': [ + "trigger": "Cyclic", + "actions": [ { - 'action': 'RESTORE', - 'field': 'Cyclic_data', - 'target': '#CURRENT#', + "action": "RESTORE", + "field": "Cyclic_data", + "target": "#CURRENT#", }, ], }, @@ -82,14 +82,13 @@ class ViscousDamperSpec(BaseModel): } ) - material: Literal['ViscousDamper'] = 'ViscousDamper' - elastic_stiffness: str = '300000 kN/m' # TODO: unit-aware type - damping_coefficient_cd: str = '280000 kNsec/m' # TODO: unit-aware type + material: Literal["ViscousDamper"] = "ViscousDamper" + elastic_stiffness: str = "300000 kN/m" # TODO: unit-aware type + damping_coefficient_cd: str = "280000 kNsec/m" # TODO: unit-aware type velocity_exponent_alpha: float = 0.30 - activate_gap_length: Literal['1', '0'] = '0' - gap_length: str = '0.05 m' # TODO: unit-aware type - analysis_type: Literal['Monotonic', 'Cyclic'] = 'Monotonic' + activate_gap_length: Literal["1", "0"] = "0" + gap_length: str = "0.05 m" # TODO: unit-aware type + analysis_type: Literal["Monotonic", "Cyclic"] = "Monotonic" cyclic_data: list[CyclicDataRow] = Field(default_factory=list) - field: Literal['_'] = '_' - set_as_variable: Literal['0', '1'] = '0' - + field: Literal["_"] = "_" + set_as_variable: Literal["0", "1"] = "0" diff --git a/src/otko/core/defaults.py b/src/otko/core/defaults.py index 9742d21..0980d6b 100644 --- a/src/otko/core/defaults.py +++ b/src/otko/core/defaults.py @@ -159,12 +159,8 @@ def ensure_default_timeseries_and_pattern( if existing is not None: return existing pattern_name = name or DEFAULT_PATTERN_NAME - ts = make_default_time_series( - project.next_time_series_id(), kind=ts_kind, name=pattern_name - ) + ts = make_default_time_series(project.next_time_series_id(), kind=ts_kind, name=pattern_name) project.time_series.append(ts) - pattern = make_default_pattern( - project.next_pattern_id(), ts.id, name=pattern_name - ) + pattern = make_default_pattern(project.next_pattern_id(), ts.id, name=pattern_name) project.load_patterns.append(pattern) return pattern diff --git a/src/otko/core/geometry/elements.py b/src/otko/core/geometry/elements.py index f78588f..35a2026 100644 --- a/src/otko/core/geometry/elements.py +++ b/src/otko/core/geometry/elements.py @@ -10,7 +10,7 @@ Conventions follow OpenSeesPy ``element ...`` commands. from __future__ import annotations -from typing import Annotated, Literal, Union +from typing import Annotated, Literal from pydantic import Field, PositiveFloat, PositiveInt @@ -91,7 +91,9 @@ class ZeroLengthElement(Entity): type: Literal["ZeroLength"] = "ZeroLength" nodes: tuple[PositiveInt, PositiveInt] material_ids: tuple[PositiveInt, ...] = Field(..., min_length=1) - dofs: tuple[int, ...] = Field(..., min_length=1, description="DOF directions, 1-indexed (1..6).") + dofs: tuple[int, ...] = Field( + ..., min_length=1, description="DOF directions, 1-indexed (1..6)." + ) do_rayleigh: bool = Field( default=False, description=( @@ -121,7 +123,9 @@ class ZeroLengthSectionElement(Entity): type: Literal["ZeroLengthSection"] = "ZeroLengthSection" nodes: tuple[PositiveInt, PositiveInt] - section_id: PositiveInt = Field(..., description="Section attached to the two coincident nodes.") + section_id: PositiveInt = Field( + ..., description="Section attached to the two coincident nodes." + ) class BeamWithHingesElement(Entity): @@ -179,7 +183,8 @@ class QuadElement(Entity): description="Surface pressure applied over the element (force / area).", ) rho: float = Field( - default=0.0, ge=0.0, + default=0.0, + ge=0.0, description="Mass density override (kip·s²/in⁴). Leave 0 to use material rho.", ) b1: float = Field( @@ -193,16 +198,14 @@ class QuadElement(Entity): Element = Annotated[ - Union[ - TrussElement, - CorotTrussElement, - ElasticBeamColumn, - ForceBeamColumn, - DispBeamColumn, - ZeroLengthElement, - ZeroLengthSectionElement, - BeamWithHingesElement, - QuadElement, - ], + TrussElement + | CorotTrussElement + | ElasticBeamColumn + | ForceBeamColumn + | DispBeamColumn + | ZeroLengthElement + | ZeroLengthSectionElement + | BeamWithHingesElement + | QuadElement, Field(discriminator="type"), ] diff --git a/src/otko/core/geometry/grid.py b/src/otko/core/geometry/grid.py index d726f68..f7d61ea 100644 --- a/src/otko/core/geometry/grid.py +++ b/src/otko/core/geometry/grid.py @@ -87,15 +87,17 @@ class GridSystem(BaseModel): if isinstance(v, dict): records.append(v) else: - records.append({ - "id": f"{prefix}{i + 1}", - "ordinate": float(v), - }) + records.append( + { + "id": f"{prefix}{i + 1}", + "ordinate": float(v), + } + ) data[grid_key] = records return data @model_validator(mode="after") - def _sort_and_dedupe(self) -> "GridSystem": + def _sort_and_dedupe(self) -> GridSystem: for name in ("x_grid_lines", "y_grid_lines", "z_grid_lines"): lines: list[GridLine] = list(getattr(self, name)) lines.sort(key=lambda ln: ln.ordinate) @@ -121,21 +123,21 @@ class GridSystem(BaseModel): def bounds(self) -> tuple[tuple[float, float], tuple[float, float], tuple[float, float]]: """Return ((xmin, xmax), (ymin, ymax), (zmin, zmax)) spanning the grid.""" + def span(vs: list[float]) -> tuple[float, float]: if not vs: return (0.0, 0.0) return (vs[0], vs[-1]) + return span(self.x_lines), span(self.y_lines), span(self.z_lines) def make_grid_lines( - axis: Literal["X", "Y", "Z"], ordinates: list[float], + axis: Literal["X", "Y", "Z"], + ordinates: list[float], ) -> list[GridLine]: """Helper: build default-metadata GridLine records from flat ordinates.""" - return [ - GridLine(id=f"{axis}{i + 1}", ordinate=float(v)) - for i, v in enumerate(ordinates) - ] + return [GridLine(id=f"{axis}{i + 1}", ordinate=float(v)) for i, v in enumerate(ordinates)] class CoordinateSystem(BaseModel): @@ -160,9 +162,9 @@ class CoordinateSystem(BaseModel): cz, sz = math.cos(rz), math.sin(rz) # Rz · Ry · Rx (applied right-to-left: Rx first, then Ry, then Rz). return [ - [cy * cz, sx * sy * cz - cx * sz, cx * sy * cz + sx * sz], - [cy * sz, sx * sy * sz + cx * cz, cx * sy * sz - sx * cz], - [-sy, sx * cy, cx * cy], + [cy * cz, sx * sy * cz - cx * sz, cx * sy * cz + sx * sz], + [cy * sz, sx * sy * sz + cx * cz, cx * sy * sz - sx * cz], + [-sy, sx * cy, cx * cy], ] def local_to_world(self, p_local: tuple[float, float, float]) -> tuple[float, float, float]: diff --git a/src/otko/core/loads/__init__.py b/src/otko/core/loads/__init__.py index 77429fc..68a1557 100644 --- a/src/otko/core/loads/__init__.py +++ b/src/otko/core/loads/__init__.py @@ -12,9 +12,9 @@ pattern is deleted, its loads go with it. from __future__ import annotations -from typing import Annotated, Literal, Union +from typing import Annotated, Literal -from pydantic import BaseModel, ConfigDict, Field, PositiveInt +from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator from otko.core._base import Entity @@ -77,15 +77,19 @@ class ResponseSpectrum(Entity): type: Literal["ResponseSpectrum"] = "ResponseSpectrum" periods: list[float] = Field( - ..., min_length=2, + ..., + min_length=2, description="Periods (s), strictly increasing.", ) accelerations: list[float] = Field( - ..., min_length=2, + ..., + min_length=2, description="Spectral pseudo-accelerations (length must match `periods`).", ) damping_ratio: float = Field( - default=0.05, ge=0.0, le=1.0, + default=0.05, + ge=0.0, + le=1.0, description="Modal damping ratio the spectrum was built for.", ) @@ -106,7 +110,7 @@ class ResponseSpectrum(Entity): TimeSeries = Annotated[ - Union[LinearTimeSeries, ConstantTimeSeries, PathTimeSeries], + LinearTimeSeries | ConstantTimeSeries | PathTimeSeries, Field(discriminator="type"), ] @@ -185,13 +189,67 @@ class ImposedSupportMotionPattern(Entity): direction: int = Field(..., ge=1, le=6, description="DOF direction (1..6).") disp_series_id: PositiveInt node_ids: list[int] = Field( - ..., min_length=1, + ..., + min_length=1, description="Support nodes driven by the motion (each restrained in `direction`).", ) factor: float = 1.0 +# ──────────────────────────── Load combinations ──────────────────────────── +class LoadCombinationItem(BaseModel): + """One term of a case-result load combination: ``factor × results(case_id)``.""" + + model_config = ConfigDict(extra="forbid", validate_assignment=True) + + case_id: PositiveInt = Field(..., description="AnalysisCase whose results are scaled.") + factor: float = Field(default=1.0, description="Scale applied to that case's results.") + + +class LoadCombination(Entity): + """Named combination of *completed* analysis-case results (post-processing). + + A ``Linear`` combination is the weighted sum + ``Σ factor_i × results(case_i)`` over nodal displacements, reactions, + and element forces. Valid for linear-elastic static superposition + (e.g. ``1.2×Dead + 1.6×Live``) — combining nonlinear, transient, or + modal results is rejected at evaluation time, not here, so projects + stay loadable while the UI guides the user. + + An ``Envelope`` combination reports the per-DOF, per-step maximum and + minimum across the factored terms instead of their sum. + """ + + type: Literal["LoadCombination"] = "LoadCombination" + kind: Literal["Linear", "Envelope"] = Field( + default="Linear", description="Linear = weighted sum; Envelope = per-DOF max/min." + ) + items: list[LoadCombinationItem] = Field( + ..., min_length=1, description="Terms of the combination (≥1; ≥2 recommended)." + ) + + @model_validator(mode="after") + def _check_items(self) -> LoadCombination: + """Reject duplicate case ids and non-finite factors.""" + import math + + seen: set[int] = set() + dup: set[int] = set() + for item in self.items: + if item.case_id in seen: + dup.add(item.case_id) + seen.add(item.case_id) + if not math.isfinite(item.factor): + raise ValueError( + f"LoadCombination {self.id}: factor for case {item.case_id} " + f"must be finite, got {item.factor!r}." + ) + if dup: + raise ValueError(f"LoadCombination {self.id}: duplicate case ids: {sorted(dup)}.") + return self + + LoadPattern = Annotated[ - Union[PlainLoadPattern, UniformExcitationPattern, ImposedSupportMotionPattern], + PlainLoadPattern | UniformExcitationPattern | ImposedSupportMotionPattern, Field(discriminator="type"), ] diff --git a/src/otko/core/materials/__init__.py b/src/otko/core/materials/__init__.py index a12d75e..daa84f6 100644 --- a/src/otko/core/materials/__init__.py +++ b/src/otko/core/materials/__init__.py @@ -11,7 +11,7 @@ https://openseespydoc.readthedocs.io/en/latest/src/uniaxialMaterial.html from __future__ import annotations -from typing import Annotated, Literal, Union +from typing import Annotated, Literal from pydantic import Field, PositiveFloat, model_validator @@ -58,7 +58,9 @@ class Steel02(Entity): Fy: PositiveFloat E0: PositiveFloat b: float = Field(..., ge=0.0, le=1.0) - R0: float = Field(default=18.0, description="Bauschinger curvature parameter (typically 10–20).") + R0: float = Field( + default=18.0, description="Bauschinger curvature parameter (typically 10–20)." + ) cR1: float = Field(default=0.925) cR2: float = Field(default=0.15) @@ -83,7 +85,10 @@ class Concrete02(Entity): fpcu: float = Field(..., le=0.0) epsU: float = Field(..., lt=0.0) lambda_: float = Field( - ..., alias="lambda", ge=0.0, le=1.0, + ..., + alias="lambda", + ge=0.0, + le=1.0, description="Ratio between unloading slope at epscu and initial slope.", ) ft: PositiveFloat = Field(..., description="Tensile strength.") @@ -108,20 +113,24 @@ class Concrete04(Entity): epscu: float = Field(..., lt=0.0, description="Ultimate compressive strain (negative).") Ec: PositiveFloat = Field(..., description="Initial tangent modulus.") fct: float | None = Field( - default=None, gt=0.0, + default=None, + gt=0.0, description="Maximum tensile strength. Omit for no-tension model.", ) et: float | None = Field( - default=None, gt=0.0, + default=None, + gt=0.0, description="Ultimate tensile strain. Required when fct is given.", ) beta: float | None = Field( - default=None, ge=0.0, le=1.0, + default=None, + ge=0.0, + le=1.0, description="Cyclic degradation factor on unloading stiffness. Requires fct and et.", ) @model_validator(mode="after") - def _tensile_params_consistent(self) -> "Concrete04": + def _tensile_params_consistent(self) -> Concrete04: if self.fct is not None and self.et is None: raise ValueError("et is required when fct is given.") if self.et is not None and self.fct is None: @@ -138,7 +147,8 @@ class ElasticPP(Entity): E: PositiveFloat epsy_pos: PositiveFloat = Field(..., description="Yield strain in tension.") epsy_neg: float | None = Field( - default=None, description="Yield strain in compression (negative); defaults to -epsy_pos.", + default=None, + description="Yield strain in compression (negative); defaults to -epsy_pos.", ) eps0: float = Field(default=0.0, description="Initial strain.") @@ -176,7 +186,8 @@ class HystereticMaterial(Entity): d1: float = Field(default=0.0, ge=0.0, description="Ductility damage, linear portion.") d2: float = Field(default=0.0, ge=0.0, description="Ductility damage, cumulative portion.") beta: float = Field( - default=0.0, ge=0.0, + default=0.0, + ge=0.0, description="Unloading-stiffness degradation (0 = no degradation).", ) @@ -206,7 +217,8 @@ class HystereticSM(Entity): type: Literal["HystereticSM"] = "HystereticSM" pos_env: list[tuple[float, float]] = Field( - ..., min_length=1, + ..., + min_length=1, description="Positive envelope (force, deformation) pairs, force first.", ) neg_env: list[tuple[float, float]] = Field( @@ -217,18 +229,16 @@ class HystereticSM(Entity): # ──────────────────────────── Discriminated union ──────────────────────────── Material = Annotated[ - Union[ - ElasticIsotropic, - ElasticUniaxial, - Steel01, - Steel02, - Concrete01, - Concrete02, - Concrete04, - ElasticPP, - HystereticMaterial, - HystereticSM, - ], + ElasticIsotropic + | ElasticUniaxial + | Steel01 + | Steel02 + | Concrete01 + | Concrete02 + | Concrete04 + | ElasticPP + | HystereticMaterial + | HystereticSM, Field(discriminator="type"), ] """Tagged union of every material kind. Pydantic uses ``type`` to dispatch on JSON load.""" diff --git a/src/otko/core/project.py b/src/otko/core/project.py index 5ed9046..f2d030a 100644 --- a/src/otko/core/project.py +++ b/src/otko/core/project.py @@ -30,7 +30,7 @@ from otko.core.geometry import ( Node, default_global_system, ) -from otko.core.loads import LoadPattern, ResponseSpectrum, TimeSeries +from otko.core.loads import LoadCombination, LoadPattern, ResponseSpectrum, TimeSeries from otko.core.materials import Material from otko.core.sections import Section from otko.core.units import UnitSystem @@ -110,6 +110,7 @@ _INDEX_KINDS: tuple[str, ...] = ( "load_patterns", "spectra", "analyses", + "combinations", ) @@ -155,15 +156,17 @@ class Project(BaseModel): """ if isinstance(data, dict) and "grid_system" in data and "coord_systems" not in data: legacy = data.pop("grid_system") - data["coord_systems"] = [{ - "name": "Global", - "coord": {}, - "grid": legacy, - }] + data["coord_systems"] = [ + { + "name": "Global", + "coord": {}, + "grid": legacy, + } + ] return data @model_validator(mode="after") - def _ensure_global_system(self) -> "Project": + def _ensure_global_system(self) -> Project: """Guarantee that a 'Global' entry exists as the first coord system.""" has_global = any(cs.name == "Global" for cs in self.coord_systems) if not has_global: @@ -171,7 +174,7 @@ class Project(BaseModel): return self @model_validator(mode="after") - def _ensure_indexed(self) -> "Project": + def _ensure_indexed(self) -> Project: """Wrap every collection in an :class:`_IndexedList` and drop caches. Runs after each validation pass (construction, ``model_validate``, @@ -203,10 +206,12 @@ class Project(BaseModel): return # No Global system yet — create one with this grid. from otko.core.geometry import CoordinateGridSystem + self.coord_systems.insert( 0, CoordinateGridSystem(name="Global", grid=new_grid), ) + sections: list[Section] = Field(default_factory=list) elements: list[Element] = Field(default_factory=list) mp_constraints: list[EqualDOFConstraint] = Field(default_factory=list) @@ -214,10 +219,11 @@ class Project(BaseModel): load_patterns: list[LoadPattern] = Field(default_factory=list) spectra: list[ResponseSpectrum] = Field(default_factory=list) analyses: list[AnalysisCase] = Field(default_factory=list) + combinations: list[LoadCombination] = Field(default_factory=list) # ─────────────────── invariants ─────────────────── @model_validator(mode="after") - def _check_ndm_ndf(self) -> "Project": + def _check_ndm_ndf(self) -> Project: valid = {(2, 2), (2, 3), (3, 3), (3, 6)} if (self.ndm, self.ndf) not in valid: raise ValueError( @@ -227,7 +233,7 @@ class Project(BaseModel): return self @model_validator(mode="after") - def _check_unique_ids(self) -> "Project": + def _check_unique_ids(self) -> Project: for label, items in ( ("node", self.nodes), ("material", self.materials), @@ -236,6 +242,7 @@ class Project(BaseModel): ("time series", self.time_series), ("load pattern", self.load_patterns), ("analysis", self.analyses), + ("combination", self.combinations), ): ids = [it.id for it in items] if len(ids) != len(set(ids)): @@ -270,6 +277,10 @@ class Project(BaseModel): def next_analysis_id(self) -> int: return self._next_id(self.analyses) + def next_combination_id(self) -> int: + """Return the next free load-combination id.""" + return self._next_id(self.combinations) + # ─────────────────── lookups ─────────────────── def node(self, node_id: PositiveInt) -> Node: return cast(Node, self._lookup("nodes", self.nodes, node_id, "node")) @@ -283,6 +294,20 @@ class Project(BaseModel): def element(self, element_id: PositiveInt) -> Element: return cast(Element, self._lookup("elements", self.elements, element_id, "element")) + def combination(self, combination_id: PositiveInt) -> LoadCombination: + """Return the load combination with ``combination_id``. + + Args: + combination_id: Combination tag to look up. + + Raises: + KeyError: if no combination carries that id. + """ + return cast( + LoadCombination, + self._lookup("combinations", self.combinations, combination_id, "combination"), + ) + def _lookup(self, kind: str, items: list[Any], target_id: int, label: str) -> Any: """Return the entity with ``target_id`` from ``items`` in O(1) time. @@ -395,5 +420,14 @@ class Project(BaseModel): f"uses invalid DOF {dof} for ndf={self.ndf}." ) + analysis_ids = {c.id for c in self.analyses} + for combo in self.combinations: + for item in combo.items: + if item.case_id not in analysis_ids: + problems.append( + f"Combination {combo.id} refers to missing analysis case " + f"{item.case_id}." + ) + if problems: raise ValueError("Reference validation failed:\n - " + "\n - ".join(problems)) diff --git a/src/otko/core/sections/__init__.py b/src/otko/core/sections/__init__.py index 5e8ce0e..530e0ca 100644 --- a/src/otko/core/sections/__init__.py +++ b/src/otko/core/sections/__init__.py @@ -16,7 +16,7 @@ exactly so the runner can emit them without further translation. from __future__ import annotations -from typing import Annotated, Literal, Union +from typing import Annotated, Literal from pydantic import BaseModel, ConfigDict, Field, PositiveFloat, PositiveInt @@ -61,7 +61,7 @@ class RectShape(BaseModel): SectionShape = Annotated[ - Union[PipeShape, AngleShape, RectShape], + PipeShape | AngleShape | RectShape, Field(discriminator="kind"), ] @@ -75,8 +75,12 @@ class ElasticSection(Entity): A: PositiveFloat Iz: PositiveFloat = Field(..., description="Moment of inertia about local z-axis.") Iy: PositiveFloat | None = Field(default=None, description="Required for 3D frames.") - G: PositiveFloat | None = Field(default=None, description="Shear modulus; required for 3D frames.") - J: PositiveFloat | None = Field(default=None, description="Torsional constant; required for 3D frames.") + G: PositiveFloat | None = Field( + default=None, description="Shear modulus; required for 3D frames." + ) + J: PositiveFloat | None = Field( + default=None, description="Torsional constant; required for 3D frames." + ) shape: SectionShape | None = Field( default=None, description=( @@ -161,12 +165,12 @@ class StraightLayer(BaseModel): Patch = Annotated[ - Union[RectangularPatch, CircularPatch], + RectangularPatch | CircularPatch, Field(discriminator="kind"), ] Layer = Annotated[ - Union[StraightLayer], + StraightLayer, Field(discriminator="kind"), ] @@ -198,7 +202,8 @@ class AggregatorDOF(BaseModel): material_id: PositiveInt dof: Literal["P", "Mz", "My", "Vy", "Vz", "T"] = Field( - ..., description="Section DOF code (OpenSees section-deformation names).", + ..., + description="Section DOF code (OpenSees section-deformation names).", ) @@ -220,6 +225,6 @@ class SectionAggregator(Entity): Section = Annotated[ - Union[ElasticSection, FiberSection, SectionAggregator], + ElasticSection | FiberSection | SectionAggregator, Field(discriminator="type"), ] diff --git a/src/otko/core/units.py b/src/otko/core/units.py index 426ccd2..63b40cc 100644 --- a/src/otko/core/units.py +++ b/src/otko/core/units.py @@ -42,30 +42,46 @@ class UnitLabels: label axes without hard-coding any particular set of units. """ - length: str # "m", "mm", "in", "ft" - force: str # "N", "kip" - moment: str # "N·m", "kip·in" - stress: str # "Pa", "MPa", "ksi", "ksf" - curvature: str # "1/m", "1/in", … - rotation: str # "rad" (always, no unit variants in practice) + length: str # "m", "mm", "in", "ft" + force: str # "N", "kip" + moment: str # "N·m", "kip·in" + stress: str # "Pa", "MPa", "ksi", "ksf" + curvature: str # "1/m", "1/in", … + rotation: str # "rad" (always, no unit variants in practice) _LABELS: dict[UnitSystem, UnitLabels] = { UnitSystem.SI_M_N: UnitLabels( - length="m", force="N", moment="N·m", - stress="Pa", curvature="1/m", rotation="rad", + length="m", + force="N", + moment="N·m", + stress="Pa", + curvature="1/m", + rotation="rad", ), UnitSystem.SI_MM_N: UnitLabels( - length="mm", force="N", moment="N·mm", - stress="MPa", curvature="1/mm", rotation="rad", + length="mm", + force="N", + moment="N·mm", + stress="MPa", + curvature="1/mm", + rotation="rad", ), UnitSystem.US_FT_KIP: UnitLabels( - length="ft", force="kip", moment="kip·ft", - stress="ksf", curvature="1/ft", rotation="rad", + length="ft", + force="kip", + moment="kip·ft", + stress="ksf", + curvature="1/ft", + rotation="rad", ), UnitSystem.US_IN_KIP: UnitLabels( - length="in", force="kip", moment="kip·in", - stress="ksi", curvature="1/in", rotation="rad", + length="in", + force="kip", + moment="kip·in", + stress="ksi", + curvature="1/in", + rotation="rad", ), } diff --git a/src/otko/services/__init__.py b/src/otko/services/__init__.py index b14f725..0c14bb3 100644 --- a/src/otko/services/__init__.py +++ b/src/otko/services/__init__.py @@ -6,6 +6,12 @@ worker lives in ``qt_workers`` and is the only module here that imports PySide6. """ +from otko.services.combinations import ( + EnvelopeResults, + combine_static_results, + envelope_static_results, + evaluate_combination, +) from otko.services.export import export_opspy, export_tcl from otko.services.material_tester import ( CyclicSegment, @@ -28,12 +34,16 @@ from otko.services.results import ( __all__ = [ "PROJECT_FILE_SUFFIX", "CyclicSegment", + "EnvelopeResults", "LoadProtocol", "MaterialTestResult", "ModalResults", "OpenSeesRunner", "StaticResults", "TransientResults", + "combine_static_results", + "envelope_static_results", + "evaluate_combination", "export_opspy", "export_tcl", "load_project", diff --git a/src/otko/services/_emit.py b/src/otko/services/_emit.py index 61e7530..ea6507a 100644 --- a/src/otko/services/_emit.py +++ b/src/otko/services/_emit.py @@ -409,7 +409,10 @@ class OpenSeesEmitter: if el.consistent_mass: ebc_args.append("-cMass") ops.element( - "elasticBeamColumn", el.id, *el.nodes, *ebc_args, + "elasticBeamColumn", + el.id, + *el.nodes, + *ebc_args, ) case ForceBeamColumn(): tag = self._element_geom_transf_tag[el.id] @@ -430,9 +433,13 @@ class OpenSeesEmitter: ops.element("dispBeamColumn", el.id, *el.nodes, tag, el.id) case ZeroLengthElement(): zl_args: list[Any] = [ - "zeroLength", el.id, *el.nodes, - "-mat", *el.material_ids, - "-dir", *el.dofs, + "zeroLength", + el.id, + *el.nodes, + "-mat", + *el.material_ids, + "-dir", + *el.dofs, ] # ``-doRayleigh 1`` only when requested (default off, matching # OpenSees' zeroLength default) — so an isolator can opt its @@ -647,15 +654,27 @@ class OpenSeesEmitter: ) vel = np.gradient(np.asarray(ts.values, dtype=float), ts.dt) ops.timeSeries( - "Path", vel_tag, "-dt", ts.dt, "-values", *vel.tolist(), - "-factor", ts.factor, + "Path", + vel_tag, + "-dt", + ts.dt, + "-values", + *vel.tolist(), + "-factor", + ts.factor, ) for nid in pat.node_ids: ops.remove("sp", nid, pat.direction) ops.pattern("MultipleSupport", pat.id) ops.groundMotion( - pat.id, "Plain", "-disp", pat.disp_series_id, "-vel", vel_tag, - "-fact", pat.factor, + pat.id, + "Plain", + "-disp", + pat.disp_series_id, + "-vel", + vel_tag, + "-fact", + pat.factor, ) for nid in pat.node_ids: ops.imposedMotion(nid, pat.direction, pat.id) diff --git a/src/otko/services/animation_export.py b/src/otko/services/animation_export.py index c23f5ea..fa80790 100644 --- a/src/otko/services/animation_export.py +++ b/src/otko/services/animation_export.py @@ -12,8 +12,9 @@ the main thread. from __future__ import annotations import math +from collections.abc import Callable from pathlib import Path -from typing import Any, Callable +from typing import Any import numpy as np @@ -55,8 +56,7 @@ def export_mode_shape_video( else: # MP4 / WebM use FFmpeg backend. macro_block_size=1 disables # the auto-resize-for-h264 pass (still works on modern players). - iio.imwrite(output_path, frames, fps=fps, codec="libx264", - macro_block_size=1) + iio.imwrite(output_path, frames, fps=fps, codec="libx264", macro_block_size=1) def export_time_history_video( @@ -94,5 +94,4 @@ def export_time_history_video( if suffix == ".gif": iio.imwrite(output_path, frames, duration=1.0 / fps, loop=0) else: - iio.imwrite(output_path, frames, fps=fps, codec="libx264", - macro_block_size=1) + iio.imwrite(output_path, frames, fps=fps, codec="libx264", macro_block_size=1) diff --git a/src/otko/services/combinations.py b/src/otko/services/combinations.py new file mode 100644 index 0000000..20a0d3a --- /dev/null +++ b/src/otko/services/combinations.py @@ -0,0 +1,235 @@ +"""Case-result load combinations — post-processing superposition. + +A :class:`LoadCombination` names a weighted set of *completed* analysis +results (e.g. ``1.2×Dead + 1.6×Live``). Evaluation is pure NumPy over +:class:`StaticResults` — no solver re-run — and is only valid for +linear-elastic static superposition. Combining any other result kind +(transient, modal, pushover, response-spectrum) raises :class:`TypeError`. + +Two combination kinds: + +- ``Linear`` → :func:`combine_static_results`: weighted sum, returned as + :class:`StaticResults` (``case_id``/``case_name`` taken from the combo). +- ``Envelope`` → :func:`envelope_static_results`: per-DOF, per-step + maximum/minimum across the factored terms, returned as + :class:`EnvelopeResults`. +""" + +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from dataclasses import dataclass, field +from typing import Any + +import numpy as np + +from otko.core.loads import LoadCombination +from otko.services.results import StaticResults + + +@dataclass +class EnvelopeResults: + """Per-DOF, per-step max/min across a combination's factored terms.""" + + case_id: int + case_name: str + n_steps: int + node_disp_max: dict[int, np.ndarray] = field(default_factory=dict) + """node_id → array of shape (n_steps, ndf), per-DOF maxima.""" + node_disp_min: dict[int, np.ndarray] = field(default_factory=dict) + """node_id → array of shape (n_steps, ndf), per-DOF minima.""" + node_reaction_max: dict[int, np.ndarray] = field(default_factory=dict) + """node_id → array of shape (n_steps, ndf), per-DOF maxima.""" + node_reaction_min: dict[int, np.ndarray] = field(default_factory=dict) + """node_id → array of shape (n_steps, ndf), per-DOF minima.""" + element_forces_max: dict[int, np.ndarray] = field(default_factory=dict) + """element_id → array of shape (n_steps, n_components), maxima.""" + element_forces_min: dict[int, np.ndarray] = field(default_factory=dict) + """element_id → array of shape (n_steps, n_components), minima.""" + + +def _check_compatible(results: Sequence[StaticResults]) -> int: + """Validate equal steps, nodes, elements and shapes; return ``n_steps``. + + Raises: + ValueError: on any incompatibility, with the offending detail. + """ + if not results: + raise ValueError("Need at least one result to combine.") + first = results[0] + n_steps = first.n_steps + node_ids = set(first.node_disp) + elem_ids = set(first.element_forces) + reaction_ids = set(first.node_reaction) + for res in results[1:]: + if res.n_steps != n_steps: + raise ValueError( + f"Step-count mismatch: case {first.case_id} has {n_steps} steps, " + f"case {res.case_id} has {res.n_steps}." + ) + if set(res.node_disp) != node_ids: + raise ValueError(f"Node set mismatch: case {first.case_id} vs case {res.case_id}.") + if set(res.node_reaction) != reaction_ids: + raise ValueError(f"Reaction set mismatch: case {first.case_id} vs case {res.case_id}.") + if set(res.element_forces) != elem_ids: + raise ValueError(f"Element set mismatch: case {first.case_id} vs case {res.case_id}.") + for nid in node_ids: + if res.node_disp[nid].shape != first.node_disp[nid].shape: + raise ValueError( + f"Displacement shape mismatch at node {nid}: " + f"case {first.case_id} vs case {res.case_id}." + ) + if res.node_reaction[nid].shape != first.node_reaction[nid].shape: + raise ValueError( + f"Reaction shape mismatch at node {nid}: " + f"case {first.case_id} vs case {res.case_id}." + ) + for eid in elem_ids: + if res.element_forces[eid].shape != first.element_forces[eid].shape: + raise ValueError( + f"Element-force shape mismatch at element {eid}: " + f"case {first.case_id} vs case {res.case_id}." + ) + return n_steps + + +def combine_static_results( + results: Sequence[StaticResults], + factors: Sequence[float], + *, + case_id: int, + case_name: str, +) -> StaticResults: + """Return the weighted sum ``Σ factor_i × results_i``. + + Args: + results: Source static results, one per combination term. + factors: Scale per term (same order as ``results``). + case_id: Tag for the returned results (the combination's id). + case_name: Label for the returned results (the combination's name). + + Raises: + ValueError: on length mismatch, non-finite factor, or + incompatible result shapes. + """ + if len(results) != len(factors): + raise ValueError(f"Results/factors length mismatch: {len(results)} vs {len(factors)}.") + for factor in factors: + if not np.isfinite(factor): + raise ValueError(f"Combination factor must be finite, got {factor!r}.") + n_steps = _check_compatible(results) + + node_disp: dict[int, np.ndarray] = {} + node_reaction: dict[int, np.ndarray] = {} + for nid in results[0].node_disp: + acc_d = np.zeros_like(results[0].node_disp[nid], dtype=float) + acc_r = np.zeros_like(results[0].node_reaction[nid], dtype=float) + for r, f in zip(results, factors, strict=False): + acc_d = acc_d + float(f) * r.node_disp[nid] + acc_r = acc_r + float(f) * r.node_reaction[nid] + node_disp[nid] = acc_d + node_reaction[nid] = acc_r + element_forces: dict[int, np.ndarray] = {} + for eid in results[0].element_forces: + acc_e = np.zeros_like(results[0].element_forces[eid], dtype=float) + for r, f in zip(results, factors, strict=False): + acc_e = acc_e + float(f) * r.element_forces[eid] + element_forces[eid] = acc_e + return StaticResults( + case_id=case_id, + case_name=case_name, + n_steps=n_steps, + node_disp=node_disp, + node_reaction=node_reaction, + element_forces=element_forces, + ) + + +def envelope_static_results( + results: Sequence[StaticResults], + factors: Sequence[float], + *, + case_id: int, + case_name: str, +) -> EnvelopeResults: + """Return per-DOF, per-step max/min across the factored terms. + + Each term is ``factor_i × results_i``; the envelope takes the + element-wise maximum (and minimum) across terms at every step. + """ + if len(results) != len(factors): + raise ValueError(f"Results/factors length mismatch: {len(results)} vs {len(factors)}.") + for factor in factors: + if not np.isfinite(factor): + raise ValueError(f"Combination factor must be finite, got {factor!r}.") + n_steps = _check_compatible(results) + + def _stack(getter: Any) -> dict[int, np.ndarray]: + keys = list(getter(results[0]).keys()) + return { + k: np.stack([float(f) * getter(r)[k] for r, f in zip(results, factors, strict=False)]) + for k in keys + } + + disp = _stack(lambda r: r.node_disp) + reac = _stack(lambda r: r.node_reaction) + elem = _stack(lambda r: r.element_forces) + return EnvelopeResults( + case_id=case_id, + case_name=case_name, + n_steps=n_steps, + node_disp_max={k: v.max(axis=0) for k, v in disp.items()}, + node_disp_min={k: v.min(axis=0) for k, v in disp.items()}, + node_reaction_max={k: v.max(axis=0) for k, v in reac.items()}, + node_reaction_min={k: v.min(axis=0) for k, v in reac.items()}, + element_forces_max={k: v.max(axis=0) for k, v in elem.items()}, + element_forces_min={k: v.min(axis=0) for k, v in elem.items()}, + ) + + +def evaluate_combination( + results_by_case_id: Mapping[int, Any], + combination: LoadCombination, +) -> StaticResults | EnvelopeResults: + """Evaluate ``combination`` against already-computed results. + + Args: + results_by_case_id: ``{analysis_case_id: results}`` — every + referenced case must be present as :class:`StaticResults`. + combination: The named combination to evaluate. + + Returns: + :class:`StaticResults` for ``Linear`` combos, + :class:`EnvelopeResults` for ``Envelope`` combos. + + Raises: + KeyError: if a referenced case has no entry in the map. + TypeError: if any referenced result is not :class:`StaticResults` + (superposition is only meaningful for static results). + ValueError: propagated from the combine/envelope helpers on + shape incompatibility. + """ + ordered: list[StaticResults] = [] + factors: list[float] = [] + for item in combination.items: + if item.case_id not in results_by_case_id: + raise KeyError( + f"Combination {combination.id}: no results for case {item.case_id} — " + "run that case first." + ) + res = results_by_case_id[item.case_id] + if not isinstance(res, StaticResults): + raise TypeError( + f"Combination {combination.id}: case {item.case_id} produced " + f"{type(res).__name__}, not StaticResults — case-result " + "combinations only support (linear) static cases." + ) + ordered.append(res) + factors.append(item.factor) + if combination.kind == "Envelope": + return envelope_static_results( + ordered, factors, case_id=combination.id, case_name=combination.name + ) + return combine_static_results( + ordered, factors, case_id=combination.id, case_name=combination.name + ) diff --git a/src/otko/services/deformation.py b/src/otko/services/deformation.py index a93340f..8c60f7b 100644 --- a/src/otko/services/deformation.py +++ b/src/otko/services/deformation.py @@ -24,7 +24,7 @@ class DeformationSource: displacements to PyVista point arrays. """ - displacements: np.ndarray # shape (n_nodes, 3) — x, y, z components + displacements: np.ndarray # shape (n_nodes, 3) — x, y, z components node_id_to_row: dict[int, int] scale: float = 1.0 @@ -38,8 +38,11 @@ class DeformationSource: def static_to_deformation( - project: Project, results: StaticResults, *, - step: int = -1, scale: float = 1.0, + project: Project, + results: StaticResults, + *, + step: int = -1, + scale: float = 1.0, ) -> DeformationSource: """Build a DeformationSource from a static analysis's nodal displacements. @@ -57,13 +60,16 @@ def static_to_deformation( # Take only translation DOFs (first 2 in 2D, first 3 in 3D). n_take = min(3, snapshot.shape[0]) disp[node_id_to_row[nid], :n_take] = snapshot[:n_take] - return DeformationSource(displacements=disp, - node_id_to_row=node_id_to_row, scale=scale) + return DeformationSource(displacements=disp, node_id_to_row=node_id_to_row, scale=scale) def modal_to_deformation( - project: Project, results: ModalResults, *, - mode: int = 0, scale: float = 1.0, phase: float = 1.0, + project: Project, + results: ModalResults, + *, + mode: int = 0, + scale: float = 1.0, + phase: float = 1.0, ) -> DeformationSource: """Build a DeformationSource from a modal analysis's mode shape. @@ -78,10 +84,9 @@ def modal_to_deformation( mode_number = mode + 1 # mode_shapes is 1-indexed if mode_number not in results.mode_shapes: - return DeformationSource(displacements=disp, - node_id_to_row=node_id_to_row, scale=scale) + return DeformationSource(displacements=disp, node_id_to_row=node_id_to_row, scale=scale) - eigvec = results.mode_shapes[mode_number] # dict: nid → np.ndarray of DOF values + eigvec = results.mode_shapes[mode_number] # dict: nid → np.ndarray of DOF values for nid, vec in eigvec.items(): if nid not in node_id_to_row: continue @@ -101,12 +106,15 @@ def modal_to_deformation( norm_factor = (bbox * 0.05) / max_amp disp *= norm_factor - return DeformationSource(displacements=disp, - node_id_to_row=node_id_to_row, scale=scale) + return DeformationSource(displacements=disp, node_id_to_row=node_id_to_row, scale=scale) def transient_to_deformation_at_step( - project: Project, results, *, step: int = 0, scale: float = 1.0, + project: Project, + results, + *, + step: int = 0, + scale: float = 1.0, ) -> DeformationSource: """Build a DeformationSource from a transient analysis at one step. @@ -139,8 +147,7 @@ def transient_to_deformation_at_step( norm_factor = (bbox * 0.10) / max_amp disp *= norm_factor - return DeformationSource(displacements=disp, - node_id_to_row=node_id_to_row, scale=scale) + return DeformationSource(displacements=disp, node_id_to_row=node_id_to_row, scale=scale) def linear_static_auto_scale(project: Project, results: StaticResults) -> float: diff --git a/src/otko/services/element_forces.py b/src/otko/services/element_forces.py index 36de591..d802579 100644 --- a/src/otko/services/element_forces.py +++ b/src/otko/services/element_forces.py @@ -41,27 +41,36 @@ from otko.services.results import StaticResults class ForceComponent(Enum): """Which force component to plot.""" - N = "N" # axial - V2 = "V2" # shear in local y (in-plane shear for 2D) - V3 = "V3" # shear in local z - T = "T" # torsion - M2 = "M2" # moment about local y - M3 = "M3" # moment about local z (in-plane moment for 2D) + N = "N" # axial + V2 = "V2" # shear in local y (in-plane shear for 2D) + V3 = "V3" # shear in local z + T = "T" # torsion + M2 = "M2" # moment about local y + M3 = "M3" # moment about local z (in-plane moment for 2D) # Map (component, end) → index into the local-force vector for 3D and 2D. _INDEX_3D = { - (ForceComponent.N, "i"): 0, (ForceComponent.N, "j"): 6, - (ForceComponent.V2, "i"): 1, (ForceComponent.V2, "j"): 7, - (ForceComponent.V3, "i"): 2, (ForceComponent.V3, "j"): 8, - (ForceComponent.T, "i"): 3, (ForceComponent.T, "j"): 9, - (ForceComponent.M2, "i"): 4, (ForceComponent.M2, "j"): 10, - (ForceComponent.M3, "i"): 5, (ForceComponent.M3, "j"): 11, + (ForceComponent.N, "i"): 0, + (ForceComponent.N, "j"): 6, + (ForceComponent.V2, "i"): 1, + (ForceComponent.V2, "j"): 7, + (ForceComponent.V3, "i"): 2, + (ForceComponent.V3, "j"): 8, + (ForceComponent.T, "i"): 3, + (ForceComponent.T, "j"): 9, + (ForceComponent.M2, "i"): 4, + (ForceComponent.M2, "j"): 10, + (ForceComponent.M3, "i"): 5, + (ForceComponent.M3, "j"): 11, } _INDEX_2D = { - (ForceComponent.N, "i"): 0, (ForceComponent.N, "j"): 3, - (ForceComponent.V2, "i"): 1, (ForceComponent.V2, "j"): 4, - (ForceComponent.M3, "i"): 2, (ForceComponent.M3, "j"): 5, + (ForceComponent.N, "i"): 0, + (ForceComponent.N, "j"): 3, + (ForceComponent.V2, "i"): 1, + (ForceComponent.V2, "j"): 4, + (ForceComponent.M3, "i"): 2, + (ForceComponent.M3, "j"): 5, } # Truss elements expose a different localForce layout than frames: @@ -69,10 +78,12 @@ _INDEX_2D = { # 3D truss → 6-vector [N_i, 0, 0, N_j, 0, 0] # So we map only the N component; other components return None. _INDEX_TRUSS_2D = { - (ForceComponent.N, "i"): 0, (ForceComponent.N, "j"): 2, + (ForceComponent.N, "i"): 0, + (ForceComponent.N, "j"): 2, } _INDEX_TRUSS_3D = { - (ForceComponent.N, "i"): 0, (ForceComponent.N, "j"): 3, + (ForceComponent.N, "i"): 0, + (ForceComponent.N, "j"): 3, } @@ -122,6 +133,7 @@ def extract_diagram_data( # Local import to avoid a cycle (element classes live in core.geometry). from otko.core import CorotTrussElement, TrussElement + truss_types = (TrussElement, CorotTrussElement) for el in project.elements: diff --git a/src/otko/services/export.py b/src/otko/services/export.py index 1ae5710..5886360 100644 --- a/src/otko/services/export.py +++ b/src/otko/services/export.py @@ -35,7 +35,7 @@ from __future__ import annotations from collections.abc import Callable from typing import Any -from otko import __version__ +from otko._const import OPENSEESPY_VERSION, __version__ from otko.core import ( ModalCase, Project, @@ -49,7 +49,7 @@ from otko.services.opensees_runner import OpenSeesRunner __all__ = ["RecordingOps", "export_opspy", "export_tcl"] #: Pinned solver version written into every script header. -PINNED_OPENSEESPY = "openseespy==3.8.0.0" +PINNED_OPENSEESPY = f"openseespy=={OPENSEESPY_VERSION}" #: Recorded call triple: (ops method name, positional args, keyword args). RecordedCall = tuple[str, tuple[Any, ...], dict[str, Any]] @@ -238,7 +238,7 @@ def _record_calls(project: Project, case_id: int | None) -> list[RecordedCall]: runner.build() else: case = _find_case(project, case_id) - if isinstance(case, (StaticCase, ModalCase, PushoverCase)): + if isinstance(case, StaticCase | ModalCase | PushoverCase): # Safe to run end-to-end: dummy query results let the # harvesting code finish; queries are filtered at render. runner.run(case) @@ -362,7 +362,7 @@ def export_opspy(project: Project, case_id: int | None = None) -> str: are appended after the model. Returns: - The script source. The header pins ``openseespy==3.8.0.0``, + The script source. The header pins the solver version, the OTKO version and the display units. Raises: diff --git a/src/otko/services/material_tester.py b/src/otko/services/material_tester.py index fe70fbc..e494521 100644 --- a/src/otko/services/material_tester.py +++ b/src/otko/services/material_tester.py @@ -63,7 +63,9 @@ class CyclicSegment(BaseModel): current -> *compressive_peak* -> *tensile_peak* -> 0. """ - compressive_peak: float = Field(..., lt=0.0, description="Target compressive strain (negative).") + compressive_peak: float = Field( + ..., lt=0.0, description="Target compressive strain (negative)." + ) tensile_peak: float = Field(..., gt=0.0, description="Target tensile strain (positive).") n_cycles: PositiveInt = Field(1, description="Number of complete excursions to run.") @@ -160,17 +162,35 @@ def _emit_uniaxial(ops: Any, mat: Any) -> None: ops.uniaxialMaterial("Steel01", mat.id, *args) case Steel02(): ops.uniaxialMaterial( - "Steel02", mat.id, mat.Fy, mat.E0, mat.b, mat.R0, mat.cR1, mat.cR2, + "Steel02", + mat.id, + mat.Fy, + mat.E0, + mat.b, + mat.R0, + mat.cR1, + mat.cR2, ) case Concrete01(): ops.uniaxialMaterial( - "Concrete01", mat.id, mat.fpc, mat.epsc0, mat.fpcu, mat.epsU, + "Concrete01", + mat.id, + mat.fpc, + mat.epsc0, + mat.fpcu, + mat.epsU, ) case Concrete02(): ops.uniaxialMaterial( - "Concrete02", mat.id, - mat.fpc, mat.epsc0, mat.fpcu, mat.epsU, - mat.lambda_, mat.ft, mat.Ets, + "Concrete02", + mat.id, + mat.fpc, + mat.epsc0, + mat.fpcu, + mat.epsU, + mat.lambda_, + mat.ft, + mat.Ets, ) case Concrete04(): args = [mat.fpc, mat.epsc0, mat.epscu, mat.Ec] @@ -187,10 +207,25 @@ def _emit_uniaxial(ops: Any, mat: Any) -> None: ops.uniaxialMaterial("ElasticPP", mat.id, *args) case HystereticMaterial(): ops.uniaxialMaterial( - "Hysteretic", mat.id, - mat.s1p, mat.e1p, mat.s2p, mat.e2p, mat.s3p, mat.e3p, - mat.s1n, mat.e1n, mat.s2n, mat.e2n, mat.s3n, mat.e3n, - mat.px, mat.py, mat.d1, mat.d2, mat.beta, + "Hysteretic", + mat.id, + mat.s1p, + mat.e1p, + mat.s2p, + mat.e2p, + mat.s3p, + mat.e3p, + mat.s1n, + mat.e1n, + mat.s2n, + mat.e2n, + mat.s3n, + mat.e3n, + mat.px, + mat.py, + mat.d1, + mat.d2, + mat.beta, ) case _: raise TypeError(f"Unsupported material type: {type(mat).__name__}") @@ -239,10 +274,13 @@ def test_uniaxial_material( """ if ops_module is None: import openseespy.opensees as _ops_default + ops_module = _ops_default ops = ops_module - mat_name = getattr(material, "name", None) or str(getattr(material, "type", type(material).__name__)) + mat_name = getattr(material, "name", None) or str( + getattr(material, "type", type(material).__name__) + ) try: # Clear any leftover OpenSees state from a prior call or runner. diff --git a/src/otko/services/peer_record.py b/src/otko/services/peer_record.py index 618c2e5..ce99a54 100644 --- a/src/otko/services/peer_record.py +++ b/src/otko/services/peer_record.py @@ -11,6 +11,7 @@ building the PathTimeSeries. from __future__ import annotations +import contextlib import re from pathlib import Path @@ -46,7 +47,8 @@ def parse_peer_record(path: str | Path) -> tuple[float, int, list[float]]: break # New NGA format: "3930 0.00500 NPTS, DT" m_new = re.match( - r"^(\d+)\s+([0-9.eE+\-]+)\s+NPTS\s*,\s*DT", stripped, + r"^(\d+)\s+([0-9.eE+\-]+)\s+NPTS\s*,\s*DT", + stripped, ) if m_new: npts = int(m_new.group(1)) @@ -63,10 +65,9 @@ def parse_peer_record(path: str | Path) -> tuple[float, int, list[float]]: values: list[float] = [] for line in lines[data_start:]: for tok in line.split(): - try: + # Skip stray non-numeric tokens. + with contextlib.suppress(ValueError): values.append(float(tok)) - except ValueError: - pass # skip stray tokens if not values: raise ValueError("Header parsed but no numeric data lines found.") return dt, npts, values @@ -82,10 +83,8 @@ def parse_plain_values(path: str | Path) -> list[float]: vals: list[float] = [] for line in text.splitlines(): for tok in line.split(): - try: + with contextlib.suppress(ValueError): vals.append(float(tok)) - except ValueError: - pass if not vals: raise ValueError(f"{path} contains no numeric values.") return vals diff --git a/src/otko/services/qt_workers.py b/src/otko/services/qt_workers.py index 4419a1b..aeaf199 100644 --- a/src/otko/services/qt_workers.py +++ b/src/otko/services/qt_workers.py @@ -32,8 +32,8 @@ class AnalysisWorker(QObject): started = Signal() log = Signal(str) - finished = Signal(object) # emits StaticResults / ModalResults / TransientResults - failed = Signal(str) # human-readable error message + traceback + finished = Signal(object) # emits StaticResults / ModalResults / TransientResults + failed = Signal(str) # human-readable error message + traceback def __init__( self, @@ -52,8 +52,10 @@ class AnalysisWorker(QObject): """Slot to be invoked by ``QThread.started``.""" self.started.emit() try: - self.log.emit(f"Building model: {len(self._project.nodes)} nodes, " - f"{len(self._project.elements)} elements.") + self.log.emit( + f"Building model: {len(self._project.nodes)} nodes, " + f"{len(self._project.elements)} elements." + ) runner = OpenSeesRunner(self._project) self.log.emit(f"Running case '{self._case.name}' ({type(self._case).__name__}) ...") results = runner.run(self._case, results_dir=self._results_dir) diff --git a/src/otko/services/results.py b/src/otko/services/results.py index 43b608f..3163ed5 100644 --- a/src/otko/services/results.py +++ b/src/otko/services/results.py @@ -56,8 +56,8 @@ class PushoverResults: n_steps: int control_node: int control_dof: int - control_disp: np.ndarray # shape (n_steps + 1,), includes t=0 - base_shear: np.ndarray # shape (n_steps + 1,), signed + control_disp: np.ndarray # shape (n_steps + 1,), includes t=0 + base_shear: np.ndarray # shape (n_steps + 1,), signed node_disp: dict[int, np.ndarray] = field(default_factory=dict) """node_id → shape (n_steps + 1, ndf) displacement history.""" element_forces: dict[int, np.ndarray] = field(default_factory=dict) @@ -158,7 +158,7 @@ class ResponseSpectrumResults: case_id: int case_name: str direction: int - combination: str # "SRSS" or "CQC" + combination: str # "SRSS" or "CQC" combined_disp: dict[int, np.ndarray] = field(default_factory=dict) """node_id → 3-vector of peak combined translational displacements.""" modes: list = field(default_factory=list) diff --git a/src/otko/services/section_bbox.py b/src/otko/services/section_bbox.py index edabcc6..18f5a20 100644 --- a/src/otko/services/section_bbox.py +++ b/src/otko/services/section_bbox.py @@ -52,8 +52,10 @@ def _angle_in_arc(angle: float, start: float, span: float) -> bool: return ((angle - start) % 360.0) <= span + 1e-9 -def bbox_for_section(section: object, project: "Project | None" = None, - ) -> tuple[float, float] | None: +def bbox_for_section( + section: object, + project: Project | None = None, +) -> tuple[float, float] | None: """Return ``(width_y, height_z)`` of the section's local bounding box. ``None`` means "size could not be inferred" — the caller should skip @@ -69,6 +71,7 @@ def bbox_for_section(section: object, project: "Project | None" = None, SectionAggregator, StraightLayer, ) + # ── ElasticSection: prefer explicit display dims; else back-solve. if isinstance(section, ElasticSection): explicit = _elastic_explicit_dims(section) diff --git a/src/otko/services/section_properties.py b/src/otko/services/section_properties.py index a041721..a637428 100644 --- a/src/otko/services/section_properties.py +++ b/src/otko/services/section_properties.py @@ -21,7 +21,6 @@ import numpy as np from otko.core.sections import ( CircularPatch, FiberSection, - Fibre, RectangularPatch, StraightLayer, ) @@ -35,9 +34,9 @@ class SectionProps: area: float centroid_y: float centroid_z: float - Iy: float # about centroid - Iz: float # about centroid - fibre_yz: np.ndarray # (n, 3): y, z, area + Iy: float # about centroid + Iz: float # about centroid + fibre_yz: np.ndarray # (n, 3): y, z, area def expand_fibres(sec: FiberSection) -> np.ndarray: @@ -65,9 +64,11 @@ def expand_fibres(sec: FiberSection) -> np.ndarray: yc = p.y_center + r_mid * math.cos(theta) zc = p.z_center + r_mid * math.sin(theta) # Annular sector area: (r_outer² - r_inner²) * dθ / (2·n_rad) - a = ((p.r_inner + (ir + 1) * d_r) ** 2 - - (p.r_inner + ir * d_r) ** 2) \ - * math.radians(d_theta) / 2.0 + a = ( + ((p.r_inner + (ir + 1) * d_r) ** 2 - (p.r_inner + ir * d_r) ** 2) + * math.radians(d_theta) + / 2.0 + ) rows.append((yc, zc, a)) for lay in sec.layers: @@ -102,8 +103,8 @@ def compute_section_props(sec: FiberSection) -> SectionProps: zc = float(np.sum(a * z) / total_a) # Second moments of area about centroid (parallel axis from each fibre). - Iz = float(np.sum(a * (y - yc) ** 2)) # about z-axis - Iy = float(np.sum(a * (z - zc) ** 2)) # about y-axis + Iz = float(np.sum(a * (y - yc) ** 2)) # about z-axis + Iy = float(np.sum(a * (z - zc) ** 2)) # about y-axis return SectionProps( n_fibres=len(fibres), diff --git a/src/otko/services/spectrum.py b/src/otko/services/spectrum.py index a242dbf..9cde026 100644 --- a/src/otko/services/spectrum.py +++ b/src/otko/services/spectrum.py @@ -24,20 +24,22 @@ from otko.services.results import ModalResults class ModeContribution: """Per-mode metadata for a response-spectrum analysis.""" - mode_number: int # 1-indexed - period: float # s - frequency: float # Hz - angular_frequency: float # rad/s - participation_factor: float # Γ_i for the chosen direction - effective_mass: float # M_eff,i = Γ_i² · M_i - mass_ratio: float # M_eff,i / Σ m - sa_at_period: float # Sa(T_i) from spectrum + mode_number: int # 1-indexed + period: float # s + frequency: float # Hz + angular_frequency: float # rad/s + participation_factor: float # Γ_i for the chosen direction + effective_mass: float # M_eff,i = Γ_i² · M_i + mass_ratio: float # M_eff,i / Σ m + sa_at_period: float # Sa(T_i) from spectrum modal_peak_disp: dict[int, np.ndarray] = field(default_factory=dict) """node_id → peak modal displacement vector (3D translations).""" def mass_participation( - project: Project, modal: ModalResults, direction: int, + project: Project, + modal: ModalResults, + direction: int, ) -> list[ModeContribution]: """Compute Γ_i, M_eff,i and frequency for every mode. @@ -79,21 +81,23 @@ def mass_participation( m_eff = 0.0 else: gamma = numerator / denominator - m_eff = gamma ** 2 * denominator + m_eff = gamma**2 * denominator omega = float(np.sqrt(abs(modal.eigenvalues[mode_number - 1]))) period = (2.0 * np.pi / omega) if omega > 0.0 else float("inf") ratio = (m_eff / total_mass) if total_mass > 0.0 else 0.0 - out.append(ModeContribution( - mode_number=mode_number, - period=period, - frequency=omega / (2.0 * np.pi) if omega > 0.0 else 0.0, - angular_frequency=omega, - participation_factor=gamma, - effective_mass=m_eff, - mass_ratio=ratio, - sa_at_period=0.0, # filled in by combine_spectrum - )) + out.append( + ModeContribution( + mode_number=mode_number, + period=period, + frequency=omega / (2.0 * np.pi) if omega > 0.0 else 0.0, + angular_frequency=omega, + participation_factor=gamma, + effective_mass=m_eff, + mass_ratio=ratio, + sa_at_period=0.0, # filled in by combine_spectrum + ) + ) return out @@ -137,8 +141,7 @@ def combine_modal_response( m.sa_at_period = 0.0 continue m.sa_at_period = interp_sa(spectrum, m.period) - scale = (m.participation_factor * m.sa_at_period - / (m.angular_frequency ** 2)) + scale = m.participation_factor * m.sa_at_period / (m.angular_frequency**2) shape = modal.mode_shapes[m.mode_number] for nid, vec in shape.items(): n_take = min(3, vec.size) @@ -156,7 +159,7 @@ def combine_modal_response( for m in modes: u = m.modal_peak_disp.get(nid) if u is not None: - sq_sum += u ** 2 + sq_sum += u**2 combined[nid] = np.sqrt(sq_sum) elif method.upper() == "CQC": zeta = damping if damping is not None else spectrum.damping_ratio @@ -169,8 +172,8 @@ def combine_modal_response( if wi <= 0.0 or wj <= 0.0: continue r = wj / wi - num = 8.0 * zeta ** 2 * (1.0 + r) * r ** 1.5 - denom = (1.0 - r ** 2) ** 2 + 4.0 * zeta ** 2 * r * (1.0 + r) ** 2 + num = 8.0 * zeta**2 * (1.0 + r) * r**1.5 + denom = (1.0 - r**2) ** 2 + 4.0 * zeta**2 * r * (1.0 + r) ** 2 rho[i, j] = num / denom if denom > 0.0 else 0.0 for nid in node_ids: sq_sum = np.zeros(3) diff --git a/src/otko/viewmodels/analysis_runner.py b/src/otko/viewmodels/analysis_runner.py index 7046e0c..5773307 100644 --- a/src/otko/viewmodels/analysis_runner.py +++ b/src/otko/viewmodels/analysis_runner.py @@ -20,7 +20,7 @@ class AnalysisRunner(QObject): started = Signal() log = Signal(str) - finished = Signal(object) # StaticResults / ModalResults / TransientResults + finished = Signal(object) # StaticResults / ModalResults / TransientResults failed = Signal(str) runningChanged = Signal(bool) diff --git a/src/otko/viewmodels/project_vm.py b/src/otko/viewmodels/project_vm.py index 148371d..9ea06d1 100644 --- a/src/otko/viewmodels/project_vm.py +++ b/src/otko/viewmodels/project_vm.py @@ -11,6 +11,7 @@ a re-paint without losing camera/selection state. from __future__ import annotations +import contextlib from pathlib import Path from PySide6.QtCore import QObject, Signal, Slot @@ -23,8 +24,8 @@ from otko.services import load_project, save_project class ProjectViewModel(QObject): """Holds the current Project, its file path, dirty state, and undo stack.""" - projectChanged = Signal(object) # emits Project | None - modelMutated = Signal() # same project, mutated by a command + projectChanged = Signal(object) # emits Project | None + modelMutated = Signal() # same project, mutated by a command dirtyChanged = Signal(bool) def __init__(self, parent: QObject | None = None) -> None: @@ -40,10 +41,8 @@ class ProjectViewModel(QObject): @Slot(bool) def _on_stack_clean_changed(self, clean: bool) -> None: # Guard against the late-fire that Qt sends during destruction. - try: + with contextlib.suppress(RuntimeError): self._set_dirty(not clean) - except RuntimeError: - pass # ── read ───────────────────────────────────────────────────────── @property diff --git a/src/otko/views/action_handlers.py b/src/otko/views/action_handlers.py index e0f3651..4352b00 100644 --- a/src/otko/views/action_handlers.py +++ b/src/otko/views/action_handlers.py @@ -49,6 +49,7 @@ from otko.views.dialogs import ( AssignZeroLengthSectionDialog, CoordinateGridSystemsDialog, LinearTimeSeriesDialog, + LoadCombinationManagerDialog, MaterialLibraryDialog, MaterialTesterDialog, MirrorDialog, @@ -917,6 +918,11 @@ class ActionHandlers: self._on_new() AnalysisCaseManagerDialog(self._vm, self).exec() + def _on_combo_manager(self) -> None: + if self._vm.project is None: + self._on_new() + LoadCombinationManagerDialog(self._vm, self).exec() + def _on_run_analysis(self) -> None: if self._vm.project is None: QMessageBox.information(self, "Run Analysis", "Open or create a project first.") @@ -961,10 +967,7 @@ class ActionHandlers: def _on_about(self) -> None: box = QMessageBox(self) box.setWindowTitle("About OTKO") - box.setText( - f"

OTKO {__version__}

" - "

A modern desktop GUI for OpenSeesPy.

" - ) + box.setText(f"

OTKO {__version__}

" "

A modern desktop GUI for OpenSeesPy.

") app_svg = Path(__file__).resolve().parent.parent / "resources" / "icons" / "app.svg" if app_svg.is_file(): box.setIconPixmap(QIcon(str(app_svg)).pixmap(64, 64)) diff --git a/src/otko/views/canvas3d/diagram_renderer.py b/src/otko/views/canvas3d/diagram_renderer.py index 949fa35..05c5f2c 100644 --- a/src/otko/views/canvas3d/diagram_renderer.py +++ b/src/otko/views/canvas3d/diagram_renderer.py @@ -22,6 +22,7 @@ Convention: from __future__ import annotations +import contextlib import logging from typing import Any @@ -34,14 +35,21 @@ from otko.services.element_forces import DiagramData, ForceComponent _LOG = logging.getLogger("otko.diagram") # Components that draw perpendicular to the element axis vs along it. -_PERPENDICULAR = {ForceComponent.V2, ForceComponent.V3, - ForceComponent.M2, ForceComponent.M3, ForceComponent.T} +_PERPENDICULAR = { + ForceComponent.V2, + ForceComponent.V3, + ForceComponent.M2, + ForceComponent.M3, + ForceComponent.T, +} # Which local axis the value is plotted along (2 = local y, 3 = local z). _LOCAL_AXIS = { - ForceComponent.V2: 2, ForceComponent.M3: 2, - ForceComponent.V3: 3, ForceComponent.M2: 3, - ForceComponent.T: 2, + ForceComponent.V2: 2, + ForceComponent.M3: 2, + ForceComponent.V3: 3, + ForceComponent.M2: 3, + ForceComponent.T: 2, } @@ -69,8 +77,10 @@ class DiagramRenderer: # All values are zero → no diagram to draw. This is normal — # e.g. asking for "torsion" on a planar bending model. Log # a hint so the user understands the empty viewport. - _LOG.info("All '%s' values are zero for this analysis step " - "— nothing to draw.", data.component.name) + _LOG.info( + "All '%s' values are zero for this analysis step " "— nothing to draw.", + data.component.name, + ) return node_pos = {n.id: np.asarray(n.coords, dtype=float) for n in project.nodes} @@ -79,8 +89,8 @@ class DiagramRenderer: is_perpendicular = data.component in _PERPENDICULAR axis_id = _LOCAL_AXIS.get(data.component, 2) - polys: list[np.ndarray] = [] # vertex arrays for each polygon - scalars: list[float] = [] # one value per polygon (avg of end values) + polys: list[np.ndarray] = [] # vertex arrays for each polygon + scalars: list[float] = [] # one value per polygon (avg of end values) for k, eid in enumerate(data.element_ids): elem = elem_lookup.get(int(eid)) if elem is None: @@ -106,15 +116,19 @@ class DiagramRenderer: perp = self._local_perp(pi, pj, 2) if perp is None: continue - width = abs(v_i) * 0.5 # half-width fall-off + width = abs(v_i) * 0.5 # half-width fall-off if width == 0.0: width = abs(v_j) * 0.5 if width == 0.0: continue - quad = np.vstack([ - pi - perp * width, pj - perp * width, - pj + perp * width, pi + perp * width, - ]) + quad = np.vstack( + [ + pi - perp * width, + pj - perp * width, + pj + perp * width, + pi + perp * width, + ] + ) polys.append(quad) scalars.append(0.5 * (data.values_i[k] + data.values_j[k])) @@ -138,7 +152,9 @@ class DiagramRenderer: # Symmetric color range so zero stays at the colormap mid-point. vmax = float(np.max(np.abs(scalars))) or 1.0 # Defensive: accept either ForceComponent enum or its name string. - comp_label = data.component.value if hasattr(data.component, "value") else str(data.component) + comp_label = ( + data.component.value if hasattr(data.component, "value") else str(data.component) + ) self._actor = self._plotter.add_mesh( mesh, scalars="value", @@ -157,8 +173,7 @@ class DiagramRenderer: # ── Numerical labels at the global min and max element ends. ── self._label_actor = self._add_value_labels(project, data, scale) - def _add_value_labels(self, project: Project, data: DiagramData, - scale: float) -> Any: + def _add_value_labels(self, project: Project, data: DiagramData, scale: float) -> Any: """Annotate the diagram's extreme ends with their numerical values. Avoids visual clutter by labelling only the two ends carrying the @@ -203,9 +218,10 @@ class DiagramRenderer: try: return self._plotter.add_point_labels( - positions, labels, + positions, + labels, font_size=14, - point_size=0, # don't draw the underlying points + point_size=0, # don't draw the underlying points shape=None, always_visible=True, pickable=False, @@ -239,10 +255,8 @@ class DiagramRenderer: pass self._actor = None if self._label_actor is not None: - try: + with contextlib.suppress(Exception): self._plotter.remove_actor(self._label_actor, render=False) - except Exception: - pass self._label_actor = None # ── helpers ───────────────────────────────────────────────────── diff --git a/src/otko/views/canvas3d/model_canvas.py b/src/otko/views/canvas3d/model_canvas.py index b761d62..b1b3417 100644 --- a/src/otko/views/canvas3d/model_canvas.py +++ b/src/otko/views/canvas3d/model_canvas.py @@ -12,7 +12,6 @@ needing a reference to the renderer. from __future__ import annotations -import os from typing import Any import numpy as np @@ -26,7 +25,7 @@ from otko.views.canvas3d.model_renderer import ModelRenderer from otko.views.canvas3d.selection import SelectionState from otko.views.canvas3d.style import RenderStyle -_PICK_DEBUG = os.environ.get("OSS_PICK_DEBUG") == "1" +PICK_DEBUG = False class ModelCanvas(QtInteractor): # type: ignore[misc] @@ -127,7 +126,7 @@ class ModelCanvas(QtInteractor): # type: ignore[misc] cx = qt_x * dpr cy = (h_logical - qt_y) * dpr - if _PICK_DEBUG: + if PICK_DEBUG: print( f"[pick] click qt=({qt_x:.0f},{qt_y:.0f}) → vtk=({cx:.0f},{cy:.0f}) " f"viewport_logical=({self.width()}x{h_logical}) dpr={dpr}" @@ -148,7 +147,7 @@ class ModelCanvas(QtInteractor): # type: ignore[misc] if node_screen is not None and len(node_screen): d2 = (node_screen[:, 0] - cx) ** 2 + (node_screen[:, 1] - cy) ** 2 idx = int(np.argmin(d2)) - if _PICK_DEBUG: + if PICK_DEBUG: print( f"[pick] nearest node id={node_ids[idx]} " f"screen={node_screen[idx]} d={np.sqrt(d2[idx]):.1f}px " @@ -181,7 +180,7 @@ class ModelCanvas(QtInteractor): # type: ignore[misc] closest = a_screen + t[:, None] * ab d2 = ((p - closest) ** 2).sum(axis=1) idx = int(np.argmin(d2)) - if _PICK_DEBUG: + if PICK_DEBUG: print( f"[pick] nearest frame id={frame_ids[idx]} " f"d={float(np.sqrt(d2[idx])):.1f}px " @@ -191,7 +190,7 @@ class ModelCanvas(QtInteractor): # type: ignore[misc] self._dispatch_pick("element", int(frame_ids[idx])) return - if _PICK_DEBUG: + if PICK_DEBUG: print("[pick] no hit within tolerance") # ── Empty-click fallback: pixel-space snap to grid intersections. ── @@ -202,7 +201,7 @@ class ModelCanvas(QtInteractor): # type: ignore[misc] snapped = self._nearest_grid_intersection_px(cx, cy, grid_tol_px) if snapped is not None: self.emptyClicked.emit(float(snapped[0]), float(snapped[1]), float(snapped[2])) - elif _PICK_DEBUG: + elif PICK_DEBUG: print("[pick] off-grid click — no snap target within tolerance") def _grid_intersections_world(self) -> np.ndarray | None: @@ -260,7 +259,7 @@ class ModelCanvas(QtInteractor): # type: ignore[misc] return None d2 = (screen[:, 0] - cx) ** 2 + (screen[:, 1] - cy) ** 2 idx = int(np.argmin(d2)) - if _PICK_DEBUG: + if PICK_DEBUG: print( f"[grid-snap] nearest intersection " f"world={world_points[idx]} d={float(np.sqrt(d2[idx])):.1f}px " diff --git a/src/otko/views/canvas3d/model_renderer.py b/src/otko/views/canvas3d/model_renderer.py index 568c079..9eb9274 100644 --- a/src/otko/views/canvas3d/model_renderer.py +++ b/src/otko/views/canvas3d/model_renderer.py @@ -10,9 +10,10 @@ Mode-aware: MODEL / DEFORMED / MODAL change only the points array. from __future__ import annotations +import contextlib import enum from dataclasses import dataclass -from typing import Any +from typing import Any, ClassVar import numpy as np import pyvista as pv @@ -24,11 +25,11 @@ from otko.core import ( ElasticBeamColumn, ForceBeamColumn, NodalLoad, - QuadElement, - UniformElementLoad, PlainLoadPattern, Project, + QuadElement, TrussElement, + UniformElementLoad, ZeroLengthElement, ZeroLengthSectionElement, ) @@ -184,8 +185,8 @@ class ModelRenderer: r, g, b = (int(round(x * 255)) for x in rgb) return f"#{r:02x}{g:02x}{b:02x}" - _NODE_LUT = ["#d9d9d9", "#00ffff"] # gray normal, cyan selected - _FRAME_LUT = ["#338cd9", "#00ffff"] # blue normal, cyan selected + _NODE_LUT: ClassVar[list[str]] = ["#d9d9d9", "#00ffff"] # gray normal, cyan selected + _FRAME_LUT: ClassVar[list[str]] = ["#338cd9", "#00ffff"] # blue normal, cyan selected def __init__(self, plotter: Any, style: RenderStyle | None = None) -> None: self._plotter = plotter @@ -219,10 +220,8 @@ class ModelRenderer: # plan view at Z=3 doesn't see the Z=0 grid cluttering the view. self._working_plane: tuple[str, float] | None = None - try: + with contextlib.suppress(Exception): self._plotter.enable_anti_aliasing("ssaa") - except Exception: - pass # ── public API ─────────────────────────────────────────────────── def render(self, project: Project | None) -> None: @@ -329,10 +328,8 @@ class ModelRenderer: """ # Remove any previous marker. if self._hover_actor is not None: - try: + with contextlib.suppress(Exception): self._plotter.remove_actor(self._hover_actor, render=False) - except Exception: - pass self._hover_actor = None if world_point is None: @@ -376,10 +373,8 @@ class ModelRenderer: sphere = pv.Sphere(radius=radius, theta_resolution=8, phi_resolution=8) glyph = self._node_pd.glyph(geom=sphere, scale=False, orient=False) if self._node_actor is not None: - try: + with contextlib.suppress(Exception): self._plotter.remove_actor(self._node_actor, render=False) - except Exception: - pass self._node_glyph = glyph self._node_actor = self._plotter.add_mesh( glyph, @@ -484,10 +479,17 @@ class ModelRenderer: plane_axis = axis_idx plane_offset_local = plane_off - cs_shift - def _on_active_plane(local_pt: tuple[float, float, float]) -> bool: - if plane_axis is None: + # Bind the per-iteration plane state as defaults so the closure + # cannot read a later iteration's values (B023); the helper is + # only ever called from within this loop body. + def _on_active_plane( + local_pt: tuple[float, float, float], + _axis: int | None = plane_axis, + _offset: float | None = plane_offset_local, + ) -> bool: + if _axis is None or _offset is None: return True - return abs(local_pt[plane_axis] - plane_offset_local) < 1e-6 # type: ignore[operator] + return abs(local_pt[_axis] - _offset) < 1e-6 # Collect active + dim segments separately so they get their # own polydata + actor (different opacity / color). @@ -496,13 +498,21 @@ class ModelRenderer: dim_pts: list[tuple[float, float, float]] = [] dim_cells: list[int] = [] - def add_seg(p1: tuple[float, float, float], p2: tuple[float, float, float]) -> None: + def add_seg( + p1: tuple[float, float, float], + p2: tuple[float, float, float], + _active_pts: list[tuple[float, float, float]] = active_pts, + _active_cells: list[int] = active_cells, + _dim_pts: list[tuple[float, float, float]] = dim_pts, + _dim_cells: list[int] = dim_cells, + _cs: Any = cs, + ) -> None: on_active = _on_active_plane(p1) and _on_active_plane(p2) - bucket_pts = active_pts if on_active else dim_pts - bucket_cells = active_cells if on_active else dim_cells + bucket_pts = _active_pts if on_active else _dim_pts + bucket_cells = _active_cells if on_active else _dim_cells i = len(bucket_pts) - bucket_pts.append(cs.coord.local_to_world(p1)) - bucket_pts.append(cs.coord.local_to_world(p2)) + bucket_pts.append(_cs.coord.local_to_world(p1)) + bucket_pts.append(_cs.coord.local_to_world(p2)) bucket_cells.extend([2, i, i + 1]) z_planes = zs if zs else [0.0] @@ -614,7 +624,7 @@ class ModelRenderer: continue # By design: area-only / thickness-only / material-only # elements carry no section. - if isinstance(el, (TrussElement, CorotTrussElement, QuadElement, ZeroLengthElement)): + if isinstance(el, TrussElement | CorotTrussElement | QuadElement | ZeroLengthElement): continue node_i = node_by_id.get(el.nodes[0]) node_j = node_by_id.get(el.nodes[1]) @@ -665,18 +675,16 @@ class ModelRenderer: diag = self._diag_of_points(self._node_original_points) cap = max(diag * 0.08, 1e-6) for el in project.elements: - if isinstance(el, (QuadElement, ZeroLengthElement, ZeroLengthSectionElement)): + if isinstance(el, QuadElement | ZeroLengthElement | ZeroLengthSectionElement): continue if not isinstance( el, - ( - ElasticBeamColumn, - DispBeamColumn, - ForceBeamColumn, - BeamWithHingesElement, - TrussElement, - CorotTrussElement, - ), + ElasticBeamColumn + | DispBeamColumn + | ForceBeamColumn + | BeamWithHingesElement + | TrussElement + | CorotTrussElement, ): continue node_i = node_by_id.get(el.nodes[0]) @@ -1094,15 +1102,11 @@ class ModelRenderer: self._clear_label_actors() for a in (self._node_actor, self._frame_actor): if a is not None: - try: + with contextlib.suppress(Exception): self._plotter.remove_actor(a, render=False) - except Exception: - pass for a in self._aux_actors: - try: + with contextlib.suppress(Exception): self._plotter.remove_actor(a, render=False) - except Exception: - pass self._node_actor = None self._frame_actor = None self._aux_actors.clear() @@ -1120,10 +1124,8 @@ class ModelRenderer: def _clear_label_actors(self) -> None: for actor in (self._node_label_actor, self._element_label_actor): if actor is not None: - try: + with contextlib.suppress(Exception): self._plotter.remove_actor(actor, render=False) - except Exception: - pass self._node_label_actor = None self._element_label_actor = None diff --git a/src/otko/views/canvas3d/style.py b/src/otko/views/canvas3d/style.py index 21b7a34..f25d674 100644 --- a/src/otko/views/canvas3d/style.py +++ b/src/otko/views/canvas3d/style.py @@ -19,15 +19,15 @@ class RenderStyle: background_top: str = "#dbe2ef" background_bottom: str = "#f5f7fb" - node_color: str = "#f0a500" # warm gold - node_selected_color: str = "#00d4ff" # bright cyan + node_color: str = "#f0a500" # warm gold + node_selected_color: str = "#00d4ff" # bright cyan frame_color: str = "#1f1f1f" truss_color: str = "#2e5cb8" zerolength_color: str = "#a020f0" selected_color: str = "#00d4ff" - fix_color: str = "#c0392b" # firebrick + fix_color: str = "#c0392b" # firebrick pin_color: str = "#c0392b" roller_color: str = "#e67e22" custom_support_color: str = "#7f8c8d" @@ -45,7 +45,7 @@ class RenderStyle: support_relative_size: float = 0.025 support_min_size: float = 0.10 - load_relative_length: float = 0.10 # arrow length / bbox_diag + load_relative_length: float = 0.10 # arrow length / bbox_diag load_min_length: float = 0.5 selection_thickness_factor: float = 1.6 # multiplier for selected actors diff --git a/src/otko/views/dialogs/__init__.py b/src/otko/views/dialogs/__init__.py index 73305a1..d2a3b45 100644 --- a/src/otko/views/dialogs/__init__.py +++ b/src/otko/views/dialogs/__init__.py @@ -12,6 +12,7 @@ from otko.views.dialogs.assign_property import ( from otko.views.dialogs.assign_support import PRESETS, AssignSupportDialog from otko.views.dialogs.assign_zls import AssignZeroLengthSectionDialog from otko.views.dialogs.case_manager import AnalysisCaseManagerDialog +from otko.views.dialogs.combination_dialog import LoadCombinationManagerDialog from otko.views.dialogs.coord_grid_systems import ( CoordinateGridSystemsDialog, CoordSystemDataDialog, @@ -60,6 +61,7 @@ __all__ = [ "DisplayOptionsDialog", "GridSystemDialog", "LinearTimeSeriesDialog", + "LoadCombinationManagerDialog", "MaterialLibraryDialog", "MaterialTesterDialog", "MirrorDialog", diff --git a/src/otko/views/dialogs/add_node.py b/src/otko/views/dialogs/add_node.py index d58c577..1c5ddc5 100644 --- a/src/otko/views/dialogs/add_node.py +++ b/src/otko/views/dialogs/add_node.py @@ -40,12 +40,15 @@ class AddNodeDialog(QDialog): the node lands on the active plane by default. """ - def __init__(self, next_node_id: int, - grid: GridSystem, - ndm: int = 3, - parent: QWidget | None = None, - *, - plane_elevation: float | None = None) -> None: + def __init__( + self, + next_node_id: int, + grid: GridSystem, + ndm: int = 3, + parent: QWidget | None = None, + *, + plane_elevation: float | None = None, + ) -> None: super().__init__(parent) self.setWindowTitle("Add Node") self._next_id = next_node_id @@ -71,16 +74,18 @@ class AddNodeDialog(QDialog): else: self._z.setVisible(False) if self._plane_elevation is not None and self._ndm == 3: - form.addRow(QLabel( - f"Suggested Z = active working-plane level " - f"({self._plane_elevation:g}).", - )) + form.addRow( + QLabel( + f"Suggested Z = active working-plane level " + f"({self._plane_elevation:g}).", + ) + ) root.addLayout(form) self._snap_cb = QCheckBox("Snap to nearest grid intersection") - self._snap_cb.setChecked(bool( - self._grid.x_lines or self._grid.y_lines or self._grid.z_lines - )) + self._snap_cb.setChecked( + bool(self._grid.x_lines or self._grid.y_lines or self._grid.z_lines) + ) root.addWidget(self._snap_cb) buttons = QDialogButtonBox( diff --git a/src/otko/views/dialogs/assign_equal_dof.py b/src/otko/views/dialogs/assign_equal_dof.py index 38f5094..9ebf0ec 100644 --- a/src/otko/views/dialogs/assign_equal_dof.py +++ b/src/otko/views/dialogs/assign_equal_dof.py @@ -42,10 +42,12 @@ class AssignEqualDOFDialog(QDialog): def _build_ui(self) -> None: layout = QVBoxLayout(self) - layout.addWidget(QLabel( - "Tie selected nodes together in chosen DOFs using OpenSees " - "equalDOF.", - )) + layout.addWidget( + QLabel( + "Tie selected nodes together in chosen DOFs using OpenSees " + "equalDOF.", + ) + ) form = QFormLayout() @@ -64,7 +66,7 @@ class AssignEqualDOFDialog(QDialog): dof_form = QFormLayout() self._dof_boxes: list[QCheckBox] = [] labels = ("Ux", "Uy", "Uz", "Rx", "Ry", "Rz") - active_labels = labels[:2] + (labels[5:6] if self._ndf == 3 else labels[2:self._ndf]) + active_labels = labels[:2] + (labels[5:6] if self._ndf == 3 else labels[2 : self._ndf]) if self._ndf == 2: active_labels = labels[:2] elif self._ndf == 3: @@ -79,7 +81,10 @@ class AssignEqualDOFDialog(QDialog): if self._suggested_dofs is not None and dof_id in self._suggested_dofs: cb.setChecked(True) self._dof_boxes.append(cb) - dof_form.addRow(f"DOF {i if self._ndf != 3 else (1 if label=='Ux' else 2 if label=='Uy' else 3)}:", cb) + dof_form.addRow( + f"DOF {i if self._ndf != 3 else (1 if label=='Ux' else 2 if label=='Uy' else 3)}:", + cb, + ) layout.addLayout(dof_form) buttons = QDialogButtonBox( diff --git a/src/otko/views/dialogs/assign_hinge.py b/src/otko/views/dialogs/assign_hinge.py index bdf993d..7e5f4e3 100644 --- a/src/otko/views/dialogs/assign_hinge.py +++ b/src/otko/views/dialogs/assign_hinge.py @@ -64,9 +64,7 @@ class AssignHingeDialog(QDialog): def _build_ui(self, n_selected: int) -> None: layout = QVBoxLayout(self) - header = ( - f"Convert {n_selected} selected element(s) to BeamWithHinges." - ) + header = f"Convert {n_selected} selected element(s) to BeamWithHinges." prefill = self._selection_prefill() if prefill is None and self._element_ids: header += " Selection is mixed — unit-scaled defaults shown." @@ -160,18 +158,20 @@ class AssignHingeDialog(QDialog): if el is None: return None if getattr(el, "type", None) == "BeamWithHinges": - collected.append({ - "section_i_id": getattr(el, "section_i_id", None), - "section_j_id": getattr(el, "section_j_id", None), - "lp_i": getattr(el, "lp_i", None), - "lp_j": getattr(el, "lp_j", None), - "E": getattr(el, "E", None), - "A": getattr(el, "A", None), - "Iz": getattr(el, "Iz", None), - "Iy": getattr(el, "Iy", None), - "G": getattr(el, "G", None), - "J": getattr(el, "J", None), - }) + collected.append( + { + "section_i_id": getattr(el, "section_i_id", None), + "section_j_id": getattr(el, "section_j_id", None), + "lp_i": getattr(el, "lp_i", None), + "lp_j": getattr(el, "lp_j", None), + "E": getattr(el, "E", None), + "A": getattr(el, "A", None), + "Iz": getattr(el, "Iz", None), + "Iy": getattr(el, "Iy", None), + "G": getattr(el, "G", None), + "J": getattr(el, "J", None), + } + ) continue sec_id = getattr(el, "section_id", None) if sec_id is None: @@ -185,21 +185,22 @@ class AssignHingeDialog(QDialog): # prefill the hinge pickers with the section itself. collected.append({"section_i_id": sec_id, "section_j_id": sec_id}) continue - collected.append({ - "section_i_id": sec_id, - "section_j_id": sec_id, - "E": getattr(sec, "E", None), - "A": getattr(sec, "A", None), - "Iz": getattr(sec, "Iz", None), - "Iy": getattr(sec, "Iy", None), - "G": getattr(sec, "G", None), - "J": getattr(sec, "J", None), - }) + collected.append( + { + "section_i_id": sec_id, + "section_j_id": sec_id, + "E": getattr(sec, "E", None), + "A": getattr(sec, "A", None), + "Iz": getattr(sec, "Iz", None), + "Iy": getattr(sec, "Iy", None), + "G": getattr(sec, "G", None), + "J": getattr(sec, "J", None), + } + ) if not collected: return None merged: dict = {} - for key in ("section_i_id", "section_j_id", "lp_i", "lp_j", - "E", "A", "Iz", "Iy", "G", "J"): + for key in ("section_i_id", "section_j_id", "lp_i", "lp_j", "E", "A", "Iz", "Iy", "G", "J"): vals = {c.get(key) for c in collected if c.get(key) is not None} if len(vals) == 1: merged[key] = next(iter(vals)) diff --git a/src/otko/views/dialogs/assign_load.py b/src/otko/views/dialogs/assign_load.py index c77d2ad..534ba69 100644 --- a/src/otko/views/dialogs/assign_load.py +++ b/src/otko/views/dialogs/assign_load.py @@ -8,7 +8,6 @@ from PySide6.QtWidgets import ( QDialogButtonBox, QDoubleSpinBox, QFormLayout, - QInputDialog, QLabel, QLineEdit, QVBoxLayout, @@ -80,9 +79,7 @@ class AssignLoadDialog(QDialog): pf.addRow("Active:", self._active_pattern_label) self._new_name_edit = QLineEdit("Pattern") - self._new_name_edit.setPlaceholderText( - "Name for the new pattern (e.g. RefMoment)" - ) + self._new_name_edit.setPlaceholderText("Name for the new pattern (e.g. RefMoment)") pf.addRow("New name:", self._new_name_edit) # TimeSeries type selector — only used when creating a new diff --git a/src/otko/views/dialogs/assign_masses.py b/src/otko/views/dialogs/assign_masses.py index 5a8f59c..f87a710 100644 --- a/src/otko/views/dialogs/assign_masses.py +++ b/src/otko/views/dialogs/assign_masses.py @@ -8,6 +8,8 @@ it maps cleanly onto our existing :class:`SetMassCommand`. from __future__ import annotations +import contextlib + from PySide6.QtWidgets import ( QCheckBox, QDialog, @@ -56,16 +58,22 @@ class AssignMassesDialog(QDialog): cur = self._current or (0.0, 0.0, 0.0, 0.0, 0.0, 0.0) form = QFormLayout() - self._mx = self._spin(cur[0]); form.addRow("Translation X:", self._mx) - self._my = self._spin(cur[1]); form.addRow("Translation Y:", self._my) + self._mx = self._spin(cur[0]) + form.addRow("Translation X:", self._mx) + self._my = self._spin(cur[1]) + form.addRow("Translation Y:", self._my) if self._ndf >= 3: - self._mz = self._spin(cur[2]); form.addRow("Translation Z:", self._mz) + self._mz = self._spin(cur[2]) + form.addRow("Translation Z:", self._mz) else: self._mz = self._spin(cur[2]) if self._ndf == 6: - self._mxx = self._spin(cur[3]); form.addRow("Rotation X (Ixx):", self._mxx) - self._myy = self._spin(cur[4]); form.addRow("Rotation Y (Iyy):", self._myy) - self._mzz = self._spin(cur[5]); form.addRow("Rotation Z (Izz):", self._mzz) + self._mxx = self._spin(cur[3]) + form.addRow("Rotation X (Ixx):", self._mxx) + self._myy = self._spin(cur[4]) + form.addRow("Rotation Y (Iyy):", self._myy) + self._mzz = self._spin(cur[5]) + form.addRow("Rotation Z (Izz):", self._mzz) else: self._mxx = self._spin(cur[3]) self._myy = self._spin(cur[4]) @@ -77,8 +85,7 @@ class AssignMassesDialog(QDialog): root.addWidget(self._xy_link) buttons = QDialogButtonBox( - QDialogButtonBox.StandardButton.Ok - | QDialogButtonBox.StandardButton.Cancel, + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, ) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) @@ -98,14 +105,16 @@ class AssignMassesDialog(QDialog): self._my.setValue(self._mx.value()) self._mx.valueChanged.connect(self._my.setValue) else: - try: + with contextlib.suppress(RuntimeError, TypeError): self._mx.valueChanged.disconnect(self._my.setValue) - except (RuntimeError, TypeError): - pass def mass_vector(self) -> tuple[float, float, float, float, float, float]: """Return the 6-tuple (Mx, My, Mz, Mxx, Myy, Mzz).""" return ( - self._mx.value(), self._my.value(), self._mz.value(), - self._mxx.value(), self._myy.value(), self._mzz.value(), + self._mx.value(), + self._my.value(), + self._mz.value(), + self._mxx.value(), + self._myy.value(), + self._mzz.value(), ) diff --git a/src/otko/views/dialogs/assign_property.py b/src/otko/views/dialogs/assign_property.py index 6127e99..5d62f4a 100644 --- a/src/otko/views/dialogs/assign_property.py +++ b/src/otko/views/dialogs/assign_property.py @@ -41,9 +41,9 @@ class AssignSectionDialog(QDialog): layout.addWidget(QLabel(self._current_note())) if not self._sections: - layout.addWidget(QLabel( - "No sections defined. Open Define → Section Library first." - )) + layout.addWidget( + QLabel("No sections defined. Open Define → Section Library first.") + ) self._combo = QComboBox() for s in self._sections: @@ -115,15 +115,15 @@ class AssignMaterialDialog(QDialog): def _build_ui(self, n_elements: int) -> None: layout = QVBoxLayout(self) - layout.addWidget(QLabel( - f"Apply material to {n_elements} selected truss/zero-length element(s)." - )) + layout.addWidget( + QLabel(f"Apply material to {n_elements} selected truss/zero-length element(s).") + ) layout.addWidget(QLabel(self._current_note())) if not self._materials: - layout.addWidget(QLabel( - "No materials defined. Open Define → Material Library first." - )) + layout.addWidget( + QLabel("No materials defined. Open Define → Material Library first.") + ) self._combo = QComboBox() for m in self._materials: diff --git a/src/otko/views/dialogs/assign_support.py b/src/otko/views/dialogs/assign_support.py index 61f4872..8b3a5bc 100644 --- a/src/otko/views/dialogs/assign_support.py +++ b/src/otko/views/dialogs/assign_support.py @@ -8,7 +8,6 @@ model's actual ``ndf`` at translation time. from __future__ import annotations -from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QButtonGroup, QCheckBox, @@ -22,13 +21,12 @@ from PySide6.QtWidgets import ( QWidget, ) - # Preset → 6-tuple (Ux, Uy, Uz, Rx, Ry, Rz) PRESETS: dict[str, tuple[bool, bool, bool, bool, bool, bool]] = { - "Free": (False, False, False, False, False, False), - "Roller (Z)": (False, False, True, False, False, False), - "Pin": (True, True, True, False, False, False), - "Fix": (True, True, True, True, True, True), + "Free": (False, False, False, False, False, False), + "Roller (Z)": (False, False, True, False, False, False), + "Pin": (True, True, True, False, False, False), + "Fix": (True, True, True, True, True, True), } diff --git a/src/otko/views/dialogs/assign_zls.py b/src/otko/views/dialogs/assign_zls.py index c1d23f0..b2c580c 100644 --- a/src/otko/views/dialogs/assign_zls.py +++ b/src/otko/views/dialogs/assign_zls.py @@ -25,8 +25,9 @@ from otko.core import Project class AssignZeroLengthSectionDialog(QDialog): """Modal dialog: pick a section, confirm creation between 2 nodes.""" - def __init__(self, project: Project, node_ids: tuple[int, int], - parent: QWidget | None = None) -> None: + def __init__( + self, project: Project, node_ids: tuple[int, int], parent: QWidget | None = None + ) -> None: super().__init__(parent) self.setWindowTitle("Assign Zero-Length Section") self._project = project @@ -35,12 +36,14 @@ class AssignZeroLengthSectionDialog(QDialog): def _build_ui(self) -> None: root = QVBoxLayout(self) - root.addWidget(QLabel( - f"Connect node {self._node_ids[0]} and " - f"{self._node_ids[1]} through a zero-length " - "section element. The two nodes must share the same " - "coordinates." - )) + root.addWidget( + QLabel( + f"Connect node {self._node_ids[0]} and " + f"{self._node_ids[1]} through a zero-length " + "section element. The two nodes must share the same " + "coordinates." + ) + ) form = QFormLayout() self._section_cb = QComboBox() @@ -53,8 +56,7 @@ class AssignZeroLengthSectionDialog(QDialog): root.addLayout(form) buttons = QDialogButtonBox( - QDialogButtonBox.StandardButton.Ok - | QDialogButtonBox.StandardButton.Cancel, + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, ) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) diff --git a/src/otko/views/dialogs/case_forms.py b/src/otko/views/dialogs/case_forms.py index be1309c..d608992 100644 --- a/src/otko/views/dialogs/case_forms.py +++ b/src/otko/views/dialogs/case_forms.py @@ -21,7 +21,6 @@ from PySide6.QtWidgets import ( QListWidget, QListWidgetItem, QSpinBox, - QVBoxLayout, QWidget, ) @@ -37,9 +36,14 @@ from otko.core import ( # ─────────────────────────── helpers ─────────────────────────── -def _spin(default: float = 0.0, *, decimals: int = 6, - minimum: float = -1e15, maximum: float = 1e15, - step: float = 1.0) -> QDoubleSpinBox: +def _spin( + default: float = 0.0, + *, + decimals: int = 6, + minimum: float = -1e15, + maximum: float = 1e15, + step: float = 1.0, +) -> QDoubleSpinBox: sb = QDoubleSpinBox() sb.setRange(minimum, maximum) sb.setDecimals(decimals) @@ -227,7 +231,14 @@ class CaseFormBase(QWidget): # ─────────────────────────── Static ─────────────────────────── -_STATIC_SYSTEMS = ["BandGeneral", "BandSPD", "ProfileSPD", "SparseGeneral", "UmfPack", "FullGeneral"] +_STATIC_SYSTEMS = [ + "BandGeneral", + "BandSPD", + "ProfileSPD", + "SparseGeneral", + "UmfPack", + "FullGeneral", +] _CONSTRAINTS = ["Plain", "Lagrange", "Penalty", "Transformation"] _INTEGRATORS_STATIC = ["LoadControl", "DisplacementControl", "ArcLength"] _ALGORITHMS = ["Linear", "Newton", "ModifiedNewton", "KrylovNewton", "BFGS", "Broyden"] @@ -291,7 +302,8 @@ class StaticCaseForm(CaseFormBase): def _read_specific(self, cid: int) -> StaticCase: return StaticCase( - id=cid, name=self._name_edit.text(), + id=cid, + name=self._name_edit.text(), pattern_ids=_require_pattern_ids(self._patterns_picker), **_factor_kwargs(StaticCase, self._patterns_picker, self._pattern_factor_spins), n_steps=self._n_steps.value(), @@ -322,9 +334,9 @@ class ModalCaseForm(CaseFormBase): self._solver.addItems(["genBandArpack", "fullGenLapack", "symmBandLapack"]) self._layout.addRow("Number of modes:", self._n_modes) self._layout.addRow("Solver:", self._solver) - self._layout.addRow(QLabel( - "The runner auto-falls back to fullGenLapack for very small models." - )) + self._layout.addRow( + QLabel("The runner auto-falls back to fullGenLapack for very small models.") + ) def _populate_specific(self, c: ModalCase) -> None: self._n_modes.setValue(c.n_modes) @@ -332,7 +344,8 @@ class ModalCaseForm(CaseFormBase): def _read_specific(self, cid: int) -> ModalCase: return ModalCase( - id=cid, name=self._name_edit.text(), + id=cid, + name=self._name_edit.text(), n_modes=self._n_modes.value(), solver=self._solver.currentText(), ) @@ -359,13 +372,19 @@ class TransientCaseForm(CaseFormBase): self._remove_patterns_picker = _make_pattern_picker(patterns) self._dt = _spin(0.01, decimals=8, minimum=1e-12, step=1e-3) self._n_steps = _int_spin(1000, minimum=1, maximum=10_000_000) - self._system = QComboBox(); self._system.addItems(_STATIC_SYSTEMS) - self._constraints = QComboBox(); self._constraints.addItems(_CONSTRAINTS) - self._integrator = QComboBox(); self._integrator.addItems(_INTEGRATORS_TRANSIENT) + self._system = QComboBox() + self._system.addItems(_STATIC_SYSTEMS) + self._constraints = QComboBox() + self._constraints.addItems(_CONSTRAINTS) + self._integrator = QComboBox() + self._integrator.addItems(_INTEGRATORS_TRANSIENT) self._gamma = _spin(0.5, decimals=4, minimum=0.0, maximum=1.0, step=0.01) self._beta = _spin(0.25, decimals=4, minimum=0.0, maximum=1.0, step=0.01) - self._algorithm = QComboBox(); self._algorithm.addItems(_ALGORITHMS); self._algorithm.setCurrentText("Newton") - self._test = QComboBox(); self._test.addItems(_TESTS) + self._algorithm = QComboBox() + self._algorithm.addItems(_ALGORITHMS) + self._algorithm.setCurrentText("Newton") + self._test = QComboBox() + self._test.addItems(_TESTS) self._tol = _spin(1e-6, decimals=12, minimum=1e-15, step=1e-7) self._max_iter = _int_spin(25) self._alpha_m = _spin(0.0, decimals=8, minimum=0.0, maximum=1e12, step=1e-4) @@ -376,10 +395,12 @@ class TransientCaseForm(CaseFormBase): self._layout.addRow(self._patterns_picker) self._layout.addRow(QLabel("Preload static cases (optional):")) self._layout.addRow(self._preload_picker) - self._layout.addRow(QLabel( - "Run these Static cases first, then hold them constant via " - "loadConst -time 0.0 before the transient starts." - )) + self._layout.addRow( + QLabel( + "Run these Static cases first, then hold them constant via " + "loadConst -time 0.0 before the transient starts." + ) + ) self._layout.addRow(QLabel("Patterns to remove after preload (optional):")) self._layout.addRow(self._remove_patterns_picker) self._layout.addRow("dt:", self._dt) @@ -396,17 +417,20 @@ class TransientCaseForm(CaseFormBase): self._layout.addRow("Rayleigh αM:", self._alpha_m) self._layout.addRow("Rayleigh βK:", self._beta_k) self._layout.addRow("Mode-1 damping ratio:", self._mode1_damping) - self._layout.addRow(QLabel( - "If mode-1 damping is > 0, the runner computes βK = 2ζ/√λ1 " - "after preload and uses it instead of the manual βK value." - )) + self._layout.addRow( + QLabel( + "If mode-1 damping is > 0, the runner computes βK = 2ζ/√λ1 " + "after preload and uses it instead of the manual βK value." + ) + ) def _populate_specific(self, c: TransientCase) -> None: _select_pattern_ids(self._patterns_picker, c.pattern_ids) _set_factor_spins(self._pattern_factor_spins, getattr(c, "pattern_factors", {}) or {}) _select_case_ids(self._preload_picker, c.preload_case_ids) _select_pattern_ids(self._remove_patterns_picker, c.remove_patterns) - self._dt.setValue(c.dt); self._n_steps.setValue(c.n_steps) + self._dt.setValue(c.dt) + self._n_steps.setValue(c.n_steps) self._system.setCurrentText(c.system) self._constraints.setCurrentText(c.constraints) self._integrator.setCurrentText(c.integrator) @@ -423,12 +447,14 @@ class TransientCaseForm(CaseFormBase): def _read_specific(self, cid: int) -> TransientCase: mode1_damping = self._mode1_damping.value() return TransientCase( - id=cid, name=self._name_edit.text(), + id=cid, + name=self._name_edit.text(), pattern_ids=_require_pattern_ids(self._patterns_picker), **_factor_kwargs(TransientCase, self._patterns_picker, self._pattern_factor_spins), preload_case_ids=_selected_case_ids(self._preload_picker), remove_patterns=_selected_pattern_ids(self._remove_patterns_picker), - dt=self._dt.value(), n_steps=self._n_steps.value(), + dt=self._dt.value(), + n_steps=self._n_steps.value(), system=self._system.currentText(), constraints=self._constraints.currentText(), integrator=self._integrator.currentText(), @@ -462,12 +488,18 @@ class PushoverCaseForm(CaseFormBase): self._target = _spin(0.1, decimals=6, minimum=-1e6, maximum=1e6, step=0.001) self._step = _spin(0.001, decimals=8, minimum=1e-12, step=1e-4) self._base_nodes = QLineEdit() - self._base_nodes.setPlaceholderText("comma-separated node ids (leave blank for all supports)") - self._system = QComboBox(); self._system.addItems(_STATIC_SYSTEMS) - self._constraints = QComboBox(); self._constraints.addItems(_CONSTRAINTS) - self._algorithm = QComboBox(); self._algorithm.addItems(_ALGORITHMS) + self._base_nodes.setPlaceholderText( + "comma-separated node ids (leave blank for all supports)" + ) + self._system = QComboBox() + self._system.addItems(_STATIC_SYSTEMS) + self._constraints = QComboBox() + self._constraints.addItems(_CONSTRAINTS) + self._algorithm = QComboBox() + self._algorithm.addItems(_ALGORITHMS) self._algorithm.setCurrentText("Newton") - self._test = QComboBox(); self._test.addItems(_TESTS) + self._test = QComboBox() + self._test.addItems(_TESTS) self._tol = _spin(1e-6, decimals=12, minimum=1e-15, step=1e-7) self._max_iter = _int_spin(25) @@ -475,10 +507,12 @@ class PushoverCaseForm(CaseFormBase): self._layout.addRow(self._patterns_picker) self._layout.addRow(QLabel("Preload static cases (optional):")) self._layout.addRow(self._preload_picker) - self._layout.addRow(QLabel( - "Run these Static cases first, then hold them constant via " - "loadConst -time 0.0 before the pushover starts." - )) + self._layout.addRow( + QLabel( + "Run these Static cases first, then hold them constant via " + "loadConst -time 0.0 before the pushover starts." + ) + ) self._layout.addRow("Control node:", self._control_node) self._layout.addRow("Control DOF:", self._control_dof) self._layout.addRow("Target displacement:", self._target) @@ -511,7 +545,8 @@ class PushoverCaseForm(CaseFormBase): txt = self._base_nodes.text().strip() base_ids = [int(x) for x in txt.replace(",", " ").split() if x] if txt else [] return PushoverCase( - id=cid, name=self._name_edit.text(), + id=cid, + name=self._name_edit.text(), pattern_ids=_require_pattern_ids(self._patterns_picker), **_factor_kwargs(PushoverCase, self._patterns_picker, self._pattern_factor_spins), preload_case_ids=_selected_case_ids(self._preload_picker), @@ -552,10 +587,12 @@ class ResponseSpectrumCaseForm(CaseFormBase): self._layout.addRow("Direction (DOF):", self._direction) self._layout.addRow("Combination:", self._combination) self._layout.addRow("Damping (CQC override):", self._damping) - self._layout.addRow(QLabel( - "Damping is used by CQC modal correlation only; " - "leave at 0 to use the spectrum's own damping ratio.", - )) + self._layout.addRow( + QLabel( + "Damping is used by CQC modal correlation only; " + "leave at 0 to use the spectrum's own damping ratio.", + ) + ) def _populate_specific(self, c: ResponseSpectrumCase) -> None: self._modal_case.setValue(c.modal_case_id) @@ -568,7 +605,8 @@ class ResponseSpectrumCaseForm(CaseFormBase): def _read_specific(self, cid: int) -> ResponseSpectrumCase: damp_val = self._damping.value() return ResponseSpectrumCase( - id=cid, name=self._name_edit.text(), + id=cid, + name=self._name_edit.text(), modal_case_id=self._modal_case.value(), spectrum_id=self._spectrum_id.value(), direction=self._direction.value(), diff --git a/src/otko/views/dialogs/case_manager.py b/src/otko/views/dialogs/case_manager.py index d451d2b..3ba5f29 100644 --- a/src/otko/views/dialogs/case_manager.py +++ b/src/otko/views/dialogs/case_manager.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from pydantic import ValidationError from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QDialog, @@ -19,7 +20,6 @@ from PySide6.QtWidgets import ( QVBoxLayout, QWidget, ) -from pydantic import ValidationError from otko.commands import ( AddAnalysisCasesCommand, @@ -27,10 +27,14 @@ from otko.commands import ( UpdateAnalysisCaseCommand, ) from otko.core import ( - ModalCase, PushoverCase, ResponseSpectrumCase, StaticCase, TransientCase, + ModalCase, + PushoverCase, + ResponseSpectrumCase, + StaticCase, + TransientCase, ) from otko.viewmodels import ProjectViewModel -from otko.views.dialogs.case_forms import FORM_REGISTRY, form_for +from otko.views.dialogs.case_forms import form_for if TYPE_CHECKING: from otko.core import Project @@ -56,16 +60,23 @@ def _default_case(kind: str, cid: int, project: Project): # type: ignore[no-unt return ModalCase(id=cid, name="Modal", n_modes=3) if kind == "Transient": return TransientCase( - id=cid, name="Transient", pattern_ids=[_first_pattern_id(project)], - dt=0.01, n_steps=1000, + id=cid, + name="Transient", + pattern_ids=[_first_pattern_id(project)], + dt=0.01, + n_steps=1000, ) if kind == "Pushover": if not project.nodes: raise ValueError("Define at least one node before adding a Pushover case.") return PushoverCase( - id=cid, name="Pushover", pattern_ids=[_first_pattern_id(project)], - control_node=project.nodes[0].id, control_dof=1, - target_disp=0.1, step_size=0.001, + id=cid, + name="Pushover", + pattern_ids=[_first_pattern_id(project)], + control_node=project.nodes[0].id, + control_dof=1, + target_disp=0.1, + step_size=0.001, ) if kind == "ResponseSpectrum": modal_ids = [c.id for c in project.analyses if isinstance(c, ModalCase)] @@ -73,13 +84,15 @@ def _default_case(kind: str, cid: int, project: Project): # type: ignore[no-unt raise ValueError("Add a Modal case before adding a ResponseSpectrum case.") if not project.spectra: raise ValueError( - "Define at least one response spectrum before adding " - "a ResponseSpectrum case." + "Define at least one response spectrum before adding " "a ResponseSpectrum case." ) return ResponseSpectrumCase( - id=cid, name="ResponseSpectrum", - modal_case_id=modal_ids[0], spectrum_id=project.spectra[0].id, - direction=1, combination="SRSS", + id=cid, + name="ResponseSpectrum", + modal_case_id=modal_ids[0], + spectrum_id=project.spectra[0].id, + direction=1, + combination="SRSS", ) raise ValueError(f"Unknown analysis type: {kind}.") @@ -150,7 +163,7 @@ class AnalysisCaseManagerDialog(QDialog): if self._list.currentItem() is not None: selected_id = self._list.currentItem().data(Qt.ItemDataRole.UserRole) self._list.clear() - for c in (self._vm.project.analyses if self._vm.project else []): + for c in self._vm.project.analyses if self._vm.project else []: label = f"#{c.id} {c.name or '(unnamed)'} [{c.type}]" item = QListWidgetItem(label) item.setData(Qt.ItemDataRole.UserRole, c.id) @@ -201,22 +214,31 @@ class AnalysisCaseManagerDialog(QDialog): # patterns). Do NOT fall through to the general type picker — # Static/Transient/Pushover would get dangling pattern refs. QMessageBox.information( - self, "No patterns", + self, + "No patterns", "No load patterns defined yet — only a Modal case can be " "added (Modal works without patterns). Define a pattern " "first for Static, Transient, or Pushover cases.", ) kind, ok = QInputDialog.getItem( - self, "Add analysis case", "Type:", - ["Modal"], current=0, editable=False, + self, + "Add analysis case", + "Type:", + ["Modal"], + current=0, + editable=False, ) if not ok: return self._create_case_of_kind("Modal") return kind, ok = QInputDialog.getItem( - self, "Add analysis case", "Type:", - list(_DEFAULTS.keys()), current=0, editable=False, + self, + "Add analysis case", + "Type:", + list(_DEFAULTS.keys()), + current=0, + editable=False, ) if not ok: return diff --git a/src/otko/views/dialogs/combination_dialog.py b/src/otko/views/dialogs/combination_dialog.py new file mode 100644 index 0000000..882736d --- /dev/null +++ b/src/otko/views/dialogs/combination_dialog.py @@ -0,0 +1,258 @@ +"""Load Combination manager — library-style CRUD over case-result combos. + +A combination names a weighted set of *completed* static-case results +(e.g. ``1.2×Dead + 1.6×Live``). Evaluation is post-processing +superposition in :mod:`otko.services.combinations` — the dialog only +edits the definition; running happens from File → Run → Combinations. +""" + +from __future__ import annotations + +from pydantic import ValidationError +from PySide6.QtCore import Qt +from PySide6.QtWidgets import ( + QComboBox, + QDialog, + QDialogButtonBox, + QDoubleSpinBox, + QFormLayout, + QHBoxLayout, + QLabel, + QLineEdit, + QListWidget, + QListWidgetItem, + QMessageBox, + QPushButton, + QVBoxLayout, + QWidget, +) + +from otko.commands import ( + AddCombinationsCommand, + DeleteCombinationsCommand, + UpdateCombinationCommand, +) +from otko.core import LoadCombination, LoadCombinationItem +from otko.viewmodels import ProjectViewModel + + +def _spin(default: float = 1.0) -> QDoubleSpinBox: + """Factor editor: wide range, 3 decimals, auto-selects its row on edit.""" + sb = QDoubleSpinBox() + sb.setRange(-1e9, 1e9) + sb.setDecimals(3) + sb.setSingleStep(0.1) + sb.setValue(default) + sb.setMaximumWidth(100) + sb.setToolTip("Scale applied to this case's results (1.0 = full).") + return sb + + +class CombinationForm(QWidget): + """Edit one combination: name, kind, and per-case factors.""" + + def __init__(self, vm: ProjectViewModel, parent: QWidget | None = None) -> None: + super().__init__(parent) + self._vm = vm + self._combo_id: int | None = None + self._spins: dict[int, QDoubleSpinBox] = {} + layout = QFormLayout(self) + self._name_edit = QLineEdit() + layout.addRow("Name:", self._name_edit) + self._kind = QComboBox() + self._kind.addItems(["Linear", "Envelope"]) + self._kind.setToolTip( + "Linear = weighted sum Σ factor×results. " + "Envelope = per-DOF max/min across factored terms." + ) + layout.addRow("Kind:", self._kind) + layout.addRow(QLabel("Cases (tick to include; factor beside each):")) + self._cases = QListWidget() + self._cases.setMaximumHeight(220) + layout.addRow(self._cases) + layout.addRow( + QLabel( + "Only linear-static cases combine meaningfully. " + "Transient / modal / pushover results are rejected at evaluation." + ) + ) + + # ── populate / read ────────────────────────────────────────── + def populate(self, combo: LoadCombination) -> None: + """Fill the form from ``combo`` (case list rebuilt from the project).""" + self._combo_id = combo.id + self._name_edit.setText(combo.name) + self._kind.setCurrentText(combo.kind) + self._rebuild_case_list(combo) + + def _rebuild_case_list(self, combo: LoadCombination | None = None) -> None: + factors = {i.case_id: i.factor for i in combo.items} if combo else {} + self._cases.clear() + self._spins.clear() + project = self._vm.project + if project is None: + return + for case in project.analyses: + item = QListWidgetItem() + item.setData(Qt.ItemDataRole.UserRole, case.id) + self._cases.addItem(item) + cell = QWidget() + row = QHBoxLayout(cell) + row.setContentsMargins(2, 0, 2, 0) + row.addWidget(QLabel(f"#{case.id} {case.name or '(unnamed)'} [{case.type}]"), 1) + spin = _spin(float(factors.get(case.id, 1.0))) + spin.valueChanged.connect(lambda _v, _it=item: _it.setSelected(True)) + row.addWidget(spin) + self._cases.setItemWidget(item, cell) + self._spins[case.id] = spin + if case.id in factors: + item.setSelected(True) + + def read(self) -> LoadCombination: + """Build a validated combination from the form (raises on bad input).""" + if self._combo_id is None: + raise ValueError("No combination selected.") + selected = [i.data(Qt.ItemDataRole.UserRole) for i in self._cases.selectedItems()] + if not selected: + raise ValueError("Select at least one analysis case for this combination.") + items = [ + LoadCombinationItem(case_id=int(cid), factor=float(self._spins[int(cid)].value())) + for cid in selected + ] + return LoadCombination( + id=self._combo_id, + name=self._name_edit.text(), + kind=self._kind.currentText(), # type: ignore[arg-type] + items=items, + ) + + +class LoadCombinationManagerDialog(QDialog): + """CRUD dialog for load combinations.""" + + def __init__(self, vm: ProjectViewModel, parent: QWidget | None = None) -> None: + super().__init__(parent) + self.setWindowTitle("Load Combinations") + self.resize(720, 520) + self._vm = vm + self._build_ui() + self._refresh_list() + self._vm.modelMutated.connect(self._refresh_list) + + def _build_ui(self) -> None: + outer = QVBoxLayout(self) + outer.addWidget( + QLabel( + "Load Combinations — weighted sets of completed static-case " + "results (e.g. 1.2×Dead + 1.6×Live). Evaluate from File → Run." + ) + ) + body = QHBoxLayout() + outer.addLayout(body, stretch=1) + + left = QVBoxLayout() + self._list = QListWidget() + self._list.currentRowChanged.connect(self._on_row_changed) + left.addWidget(self._list, stretch=1) + btn_row = QHBoxLayout() + self._add_btn = QPushButton("Add…") + self._delete_btn = QPushButton("Delete") + self._add_btn.clicked.connect(self._on_add) + self._delete_btn.clicked.connect(self._on_delete) + btn_row.addWidget(self._add_btn) + btn_row.addWidget(self._delete_btn) + left.addLayout(btn_row) + body.addLayout(left, stretch=1) + + right = QVBoxLayout() + self._form = CombinationForm(self._vm) + right.addWidget(self._form, stretch=1) + self._apply_btn = QPushButton("Apply changes") + self._apply_btn.clicked.connect(self._on_apply) + right.addWidget(self._apply_btn, alignment=Qt.AlignmentFlag.AlignRight) + body.addLayout(right, stretch=2) + + buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close, parent=self) + buttons.rejected.connect(self.reject) + buttons.accepted.connect(self.accept) + outer.addWidget(buttons) + + # ── list ───────────────────────────────────────────────────── + def _refresh_list(self) -> None: + selected_id = None + if self._list.currentItem() is not None: + selected_id = self._list.currentItem().data(Qt.ItemDataRole.UserRole) + self._list.clear() + for c in self._vm.project.combinations if self._vm.project else []: + label = f"#{c.id} {c.name or '(unnamed)'} [{c.kind}]" + item = QListWidgetItem(label) + item.setData(Qt.ItemDataRole.UserRole, c.id) + self._list.addItem(item) + if selected_id is not None: + self._select_by_id(selected_id) + if self._list.currentRow() < 0 and self._list.count(): + self._list.setCurrentRow(0) + if not self._list.count(): + self._form._combo_id = None + + def _selected(self) -> LoadCombination | None: + item = self._list.currentItem() + if item is None or self._vm.project is None: + return None + cid = item.data(Qt.ItemDataRole.UserRole) + return next((c for c in self._vm.project.combinations if c.id == cid), None) + + def _on_row_changed(self, _row: int) -> None: + combo = self._selected() + if combo is None: + return + self._form.populate(combo) + + def _select_by_id(self, target_id: int) -> None: + for i in range(self._list.count()): + if self._list.item(i).data(Qt.ItemDataRole.UserRole) == target_id: + self._list.setCurrentRow(i) + return + + # ── mutations ──────────────────────────────────────────────── + def _on_apply(self) -> None: + try: + new_combo = self._form.read() + self._vm.apply_command(UpdateCombinationCommand(self._vm, new_combo)) + except (ValidationError, ValueError) as exc: + QMessageBox.critical(self, "Validation error", str(exc)) + + def _on_add(self) -> None: + if self._vm.project is None: + return + if not self._vm.project.analyses: + QMessageBox.information( + self, + "No cases", + "Define at least one analysis case first (Analyze → Cases…).", + ) + return + new_id = self._vm.project.next_combination_id() + first = self._vm.project.analyses[0] + try: + combo = LoadCombination( + id=new_id, + name="New", + items=[LoadCombinationItem(case_id=first.id)], + ) + except (ValidationError, ValueError) as exc: + QMessageBox.warning(self, "Could not create combination", str(exc)) + return + self._vm.apply_command(AddCombinationsCommand(self._vm, [combo])) + self._select_by_id(new_id) + + def _on_delete(self) -> None: + combo = self._selected() + if combo is None: + return + reply = QMessageBox.question( + self, "Delete combination", f"Delete load combination #{combo.id}?" + ) + if reply != QMessageBox.StandardButton.Yes: + return + self._vm.apply_command(DeleteCombinationsCommand(self._vm, {combo.id})) diff --git a/src/otko/views/dialogs/coord_grid_systems.py b/src/otko/views/dialogs/coord_grid_systems.py index 78459a1..595464e 100644 --- a/src/otko/views/dialogs/coord_grid_systems.py +++ b/src/otko/views/dialogs/coord_grid_systems.py @@ -61,8 +61,7 @@ class CoordSystemDataDialog(QDialog): ) -> None: super().__init__(parent) self.setWindowTitle( - "Edit Coordinate/Grid System" - if existing is not None else "Add Coordinate/Grid System" + "Edit Coordinate/Grid System" if existing is not None else "Add Coordinate/Grid System" ) self._is_global = is_global self._existing = existing @@ -88,28 +87,33 @@ class CoordSystemDataDialog(QDialog): loc_box = QGroupBox("Location and Orientation (relative to Global)") loc_form = QFormLayout(loc_box) - self._ox = self._spin(); self._oy = self._spin(); self._oz = self._spin() + self._ox = self._spin() + self._oy = self._spin() + self._oz = self._spin() origin_row = QHBoxLayout() for label, w in (("X", self._ox), ("Y", self._oy), ("Z", self._oz)): origin_row.addWidget(QLabel(f"{label}:")) origin_row.addWidget(w) - origin_wrap = QWidget(); origin_wrap.setLayout(origin_row) + origin_wrap = QWidget() + origin_wrap.setLayout(origin_row) loc_form.addRow("Origin:", origin_wrap) - self._rx = self._rot_spin(); self._ry = self._rot_spin(); self._rz = self._rot_spin() + self._rx = self._rot_spin() + self._ry = self._rot_spin() + self._rz = self._rot_spin() rot_row = QHBoxLayout() for label, w in (("about X", self._rx), ("about Y", self._ry), ("about Z", self._rz)): rot_row.addWidget(QLabel(f"{label}:")) rot_row.addWidget(w) - rot_wrap = QWidget(); rot_wrap.setLayout(rot_row) + rot_wrap = QWidget() + rot_wrap.setLayout(rot_row) loc_form.addRow("Rotation (deg):", rot_wrap) if self._is_global: for w in (self._ox, self._oy, self._oz, self._rx, self._ry, self._rz): w.setEnabled(False) loc_box.setToolTip( - "Global system is anchored at the world origin " - "with identity orientation.", + "Global system is anchored at the world origin " "with identity orientation.", ) layout.addWidget(loc_box) @@ -160,8 +164,7 @@ class CoordSystemDataDialog(QDialog): # ── Buttons ────────────────────────────────────────────── buttons = QDialogButtonBox( - QDialogButtonBox.StandardButton.Ok - | QDialogButtonBox.StandardButton.Cancel, + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, ) buttons.accepted.connect(self._on_accept) buttons.rejected.connect(self.reject) @@ -189,9 +192,13 @@ class CoordSystemDataDialog(QDialog): def _load_existing(self, cs: CoordinateGridSystem) -> None: self._name_edit.setText(cs.name) ox, oy, oz = cs.coord.origin - self._ox.setValue(ox); self._oy.setValue(oy); self._oz.setValue(oz) + self._ox.setValue(ox) + self._oy.setValue(oy) + self._oz.setValue(oz) rx, ry, rz = cs.coord.rotation_deg - self._rx.setValue(rx); self._ry.setValue(ry); self._rz.setValue(rz) + self._rx.setValue(rx) + self._ry.setValue(ry) + self._rz.setValue(rz) self._x_edit.setText(_spacings_text(cs.grid.x_lines)) self._y_edit.setText(_spacings_text(cs.grid.y_lines)) self._z_edit.setText(_spacings_text(cs.grid.z_lines)) @@ -245,9 +252,7 @@ class CoordinateGridSystemsDialog(QDialog): super().__init__(parent) self.setWindowTitle("Coordinate/Grid Systems") self.resize(600, 420) - self._systems: list[CoordinateGridSystem] = [ - s.model_copy(deep=True) for s in systems - ] + self._systems: list[CoordinateGridSystem] = [s.model_copy(deep=True) for s in systems] self._build_ui() self._refresh_list() @@ -290,8 +295,7 @@ class CoordinateGridSystemsDialog(QDialog): right.addStretch(1) buttons = QDialogButtonBox( - QDialogButtonBox.StandardButton.Ok - | QDialogButtonBox.StandardButton.Cancel, + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, ) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) @@ -332,9 +336,7 @@ class CoordinateGridSystemsDialog(QDialog): self._btn_modify.setEnabled(cs is not None) self._cb_general.blockSignals(True) self._cb_general.setChecked(cs.grid.is_general if cs is not None else False) - self._cb_general.setEnabled( - cs is not None and not cs.grid.is_general - ) + self._cb_general.setEnabled(cs is not None and not cs.grid.is_general) self._cb_general.blockSignals(False) # ── actions ────────────────────────────────────────────────── @@ -351,14 +353,17 @@ class CoordinateGridSystemsDialog(QDialog): from otko.views.dialogs.define_grid_data import ( DefineGridSystemDataDialog, ) + dlg = DefineGridSystemDataDialog(parent=self) if dlg.exec() != QDialog.DialogCode.Accepted: return new_sys = dlg.system() if any(s.name == new_sys.name for s in self._systems): - new_sys = new_sys.model_copy(update={ - "name": self._unique_name(new_sys.name), - }) + new_sys = new_sys.model_copy( + update={ + "name": self._unique_name(new_sys.name), + } + ) self._systems.append(new_sys) self._refresh_list(select_name=new_sys.name) @@ -380,8 +385,11 @@ class CoordinateGridSystemsDialog(QDialog): from otko.views.dialogs.define_grid_data import ( DefineGridSystemDataDialog, ) + dlg = DefineGridSystemDataDialog( - existing=cs, is_global=cs.is_global(), parent=self, + existing=cs, + is_global=cs.is_global(), + parent=self, ) if dlg.exec() != QDialog.DialogCode.Accepted: return @@ -392,9 +400,11 @@ class CoordinateGridSystemsDialog(QDialog): # Enforce unique names against siblings. others = [s for s in self._systems if s is not cs] if any(s.name == updated.name for s in others): - updated = updated.model_copy(update={ - "name": self._unique_name(updated.name), - }) + updated = updated.model_copy( + update={ + "name": self._unique_name(updated.name), + } + ) row = self._list.currentRow() self._systems[row] = updated self._refresh_list(select_name=updated.name) @@ -414,7 +424,8 @@ class CoordinateGridSystemsDialog(QDialog): if cs.grid.is_general and not checked: # SAP2000: once converted to General, can't convert back. QMessageBox.information( - self, "Convert to General", + self, + "Convert to General", "Once a system is converted to General, it cannot be " "converted back to a regular Cartesian system.", ) diff --git a/src/otko/views/dialogs/define_grid_data.py b/src/otko/views/dialogs/define_grid_data.py index 5ff378a..7440b04 100644 --- a/src/otko/views/dialogs/define_grid_data.py +++ b/src/otko/views/dialogs/define_grid_data.py @@ -18,6 +18,8 @@ Top-level controls: from __future__ import annotations +from typing import ClassVar + from PySide6.QtCore import Qt from PySide6.QtGui import QColor from PySide6.QtWidgets import ( @@ -27,7 +29,6 @@ from PySide6.QtWidgets import ( QComboBox, QDialog, QDialogButtonBox, - QDoubleSpinBox, QFormLayout, QGroupBox, QHBoxLayout, @@ -52,7 +53,6 @@ from otko.core.geometry import ( GridSystem, ) - _LINE_TYPE_CHOICES = ["Primary", "Secondary"] _BUBBLE_LOC_CHOICES = ["Start", "End"] @@ -60,7 +60,14 @@ _BUBBLE_LOC_CHOICES = ["Start", "End"] class _AxisGridTable(QWidget): """A single X/Y/Z spreadsheet — one row per GridLine.""" - COLUMNS = ["Grid ID", "Ordinate", "Line Type", "Visibility", "Bubble Loc", "Color"] + COLUMNS: ClassVar[list[str]] = [ + "Grid ID", + "Ordinate", + "Line Type", + "Visibility", + "Bubble Loc", + "Color", + ] def __init__(self, axis: str, parent: QWidget | None = None) -> None: super().__init__(parent) @@ -127,9 +134,9 @@ class _AxisGridTable(QWidget): prev = 0.0 for i, v in enumerate(ords): if i == 0: - out.append(v) # first row: absolute position + out.append(v) # first row: absolute position else: - out.append(v - prev) # subsequent rows: spacing from previous + out.append(v - prev) # subsequent rows: spacing from previous prev = v return out @@ -169,23 +176,28 @@ class _AxisGridTable(QWidget): visible_widget = self._table.cellWidget(row, 3) bubble_widget = self._table.cellWidget(row, 4) color_item = self._table.item(row, 5) - out.append(GridLine( - id=(id_item.text().strip() if id_item else f"{self.axis}{row + 1}"), - ordinate=ords[row] if row < len(ords) else 0.0, - line_type=( - line_type_widget.currentText() # type: ignore[union-attr] - if isinstance(line_type_widget, QComboBox) else "Primary" - ), - visible=( - visible_widget.isChecked() # type: ignore[union-attr] - if isinstance(visible_widget, QCheckBox) else True - ), - bubble_loc=( - bubble_widget.currentText() # type: ignore[union-attr] - if isinstance(bubble_widget, QComboBox) else "End" - ), - color=(color_item.text() if color_item else "#808080"), - )) + out.append( + GridLine( + id=(id_item.text().strip() if id_item else f"{self.axis}{row + 1}"), + ordinate=ords[row] if row < len(ords) else 0.0, + line_type=( + line_type_widget.currentText() # type: ignore[union-attr] + if isinstance(line_type_widget, QComboBox) + else "Primary" + ), + visible=( + visible_widget.isChecked() # type: ignore[union-attr] + if isinstance(visible_widget, QCheckBox) + else True + ), + bubble_loc=( + bubble_widget.currentText() # type: ignore[union-attr] + if isinstance(bubble_widget, QComboBox) + else "End" + ), + color=(color_item.text() if color_item else "#808080"), + ) + ) return out # ── row ops ─────────────────────────────────────────────────── @@ -218,7 +230,8 @@ class _AxisGridTable(QWidget): vis = QCheckBox() vis.setChecked(ln.visible) vis_wrap = QWidget() - lay = QHBoxLayout(vis_wrap); lay.setContentsMargins(0, 0, 0, 0) + lay = QHBoxLayout(vis_wrap) + lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(vis) lay.setAlignment(Qt.AlignmentFlag.AlignCenter) # We keep the QCheckBox directly; the wrap is optional visual @@ -250,13 +263,13 @@ class _AxisGridTable(QWidget): lines = self.collect_lines() lines.sort(key=lambda ln: ln.ordinate) self._table.setRowCount(0) - self._show_spacing = False # reset to ordinate view after sort + self._show_spacing = False # reset to ordinate view after sort self._table.setHorizontalHeaderLabels(self.COLUMNS) for ln in lines: self._append_row(ln) def _on_cell_double_clicked(self, row: int, col: int) -> None: - if col == 5: # Color picker + if col == 5: # Color picker item = self._table.item(row, col) current = QColor(item.text() if item else "#808080") new = QColorDialog.getColor(current, self, "Grid Line Color") @@ -278,8 +291,7 @@ class DefineGridSystemDataDialog(QDialog): ) -> None: super().__init__(parent) self.setWindowTitle( - "Define Grid System Data" if existing is not None - else "Add Grid System" + "Define Grid System Data" if existing is not None else "Add Grid System" ) self.resize(780, 620) self._is_global = is_global @@ -328,9 +340,7 @@ class DefineGridSystemDataDialog(QDialog): self._btn_locate.clicked.connect(self._on_locate_origin) if self._is_global: self._btn_locate.setEnabled(False) - self._btn_locate.setToolTip( - "Global system is anchored at the world origin." - ) + self._btn_locate.setToolTip("Global system is anchored at the world origin.") head.addWidget(self._btn_locate) root.addLayout(head) @@ -375,8 +385,7 @@ class DefineGridSystemDataDialog(QDialog): bottom.addWidget(self._btn_quick) bottom.addStretch(1) buttons = QDialogButtonBox( - QDialogButtonBox.StandardButton.Ok - | QDialogButtonBox.StandardButton.Cancel, + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, ) buttons.accepted.connect(self._on_accept) buttons.rejected.connect(self.reject) @@ -388,8 +397,7 @@ class DefineGridSystemDataDialog(QDialog): ox, oy, oz = self._origin rx, ry, rz = self._rotation_deg self._origin_label.setText( - f"Origin: ({ox:g}, {oy:g}, {oz:g}) " - f"Rotation: ({rx:g}°, {ry:g}°, {rz:g}°)" + f"Origin: ({ox:g}, {oy:g}, {oz:g}) " f"Rotation: ({rx:g}°, {ry:g}°, {rz:g}°)" ) def _load_existing(self, cs: CoordinateGridSystem) -> None: @@ -415,6 +423,7 @@ class DefineGridSystemDataDialog(QDialog): from otko.views.dialogs.locate_origin import ( CoordSystemLocationOrientationDialog, ) + dlg = CoordSystemLocationOrientationDialog( origin=self._origin, rotation_deg=self._rotation_deg, @@ -429,11 +438,13 @@ class DefineGridSystemDataDialog(QDialog): from otko.views.dialogs.quick_grid_lines import ( QuickGridLinesDialog, ) + dlg = QuickGridLinesDialog(parent=self) if dlg.exec() != QDialog.DialogCode.Accepted: return xs, ys, zs = dlg.ordinates() from otko.core.geometry import make_grid_lines + # Revert to ordinate display mode before replacing rows. self._rb_ordinates.setChecked(True) self._tab_x.load_lines(make_grid_lines("X", xs)) @@ -465,7 +476,8 @@ class DefineGridSystemDataDialog(QDialog): return CoordinateGridSystem( name=self._name_edit.text().strip(), coord=CoordinateSystem( - origin=self._origin, rotation_deg=self._rotation_deg, + origin=self._origin, + rotation_deg=self._rotation_deg, ), grid=grid, ) diff --git a/src/otko/views/dialogs/distributed_load.py b/src/otko/views/dialogs/distributed_load.py index 19945d4..306a678 100644 --- a/src/otko/views/dialogs/distributed_load.py +++ b/src/otko/views/dialogs/distributed_load.py @@ -141,9 +141,11 @@ class AssignDistributedLoadDialog(QDialog): def values(self) -> tuple[float, float, float]: """Return (wy, wz, wx).""" - return (self._spinboxes["wy"].value(), - self._spinboxes["wz"].value(), - self._spinboxes["wx"].value()) + return ( + self._spinboxes["wy"].value(), + self._spinboxes["wz"].value(), + self._spinboxes["wx"].value(), + ) def selected_pattern_id(self) -> int | None: """Return the chosen pattern id, or ``None`` if the user picked diff --git a/src/otko/views/dialogs/grid_system.py b/src/otko/views/dialogs/grid_system.py index 4f94f71..c1ac05c 100644 --- a/src/otko/views/dialogs/grid_system.py +++ b/src/otko/views/dialogs/grid_system.py @@ -17,8 +17,8 @@ from PySide6.QtWidgets import ( QDialog, QDialogButtonBox, QFormLayout, - QLineEdit, QLabel, + QLineEdit, QVBoxLayout, QWidget, ) @@ -63,9 +63,9 @@ def _coords_from_spacings(spacings: list[float], origin: float = 0.0) -> list[fl class GridSystemDialog(QDialog): """Dialog for entering X/Y/Z grid-line coordinates.""" - def __init__(self, next_node_id: int, - existing: GridSystem | None = None, - parent: QWidget | None = None) -> None: + def __init__( + self, next_node_id: int, existing: GridSystem | None = None, parent: QWidget | None = None + ) -> None: super().__init__(parent) self.setWindowTitle("Grid System") self._next_id = next_node_id @@ -104,9 +104,7 @@ class GridSystemDialog(QDialog): self._visible_cb.setChecked(True) layout.addWidget(self._visible_cb) - self._generate_nodes_cb = QCheckBox( - "Also create nodes at every intersection" - ) + self._generate_nodes_cb = QCheckBox("Also create nodes at every intersection") self._generate_nodes_cb.setChecked(False) layout.addWidget(self._generate_nodes_cb) @@ -128,6 +126,7 @@ class GridSystemDialog(QDialog): def _load_existing(self, grid: GridSystem) -> None: """Pre-fill editors from an existing GridSystem (as spacings).""" + def to_spacings_text(coords: list[float]) -> str: if not coords: return "" @@ -136,6 +135,7 @@ class GridSystemDialog(QDialog): # Single line at nonzero origin — degenerate, show empty. return "" return ", ".join(f"{s:g}" for s in spacings) + self._x_edit.setText(to_spacings_text(grid.x_lines)) self._y_edit.setText(to_spacings_text(grid.y_lines)) self._z_edit.setText(to_spacings_text(grid.z_lines)) @@ -152,8 +152,7 @@ class GridSystemDialog(QDialog): else: extra = "
Nodes will NOT be created automatically." self._preview.setText( - f"Grid: {len(xs)} × {len(ys)} × {len(zs)} lines." - f"{extra}" + f"Grid: {len(xs)} × {len(ys)} × {len(zs)} lines." f"{extra}" ) except (ValueError, IndexError) as exc: self._preview.setText(f"Parse error: {exc}") diff --git a/src/otko/views/dialogs/locate_origin.py b/src/otko/views/dialogs/locate_origin.py index 5121c0e..28a8205 100644 --- a/src/otko/views/dialogs/locate_origin.py +++ b/src/otko/views/dialogs/locate_origin.py @@ -13,7 +13,6 @@ from PySide6.QtWidgets import ( QDoubleSpinBox, QFormLayout, QGroupBox, - QHBoxLayout, QLabel, QVBoxLayout, QWidget, @@ -36,28 +35,35 @@ class CoordSystemLocationOrientationDialog(QDialog): def _build_ui(self) -> None: root = QVBoxLayout(self) - root.addWidget(QLabel( - "Enter the system's origin and orientation relative to Global. " - "Rotations are XYZ Euler angles in degrees." - )) + root.addWidget( + QLabel( + "Enter the system's origin and orientation relative to Global. " + "Rotations are XYZ Euler angles in degrees." + ) + ) origin_box = QGroupBox("Origin (world units)") of = QFormLayout(origin_box) - self._ox = self._spin(); of.addRow("X:", self._ox) - self._oy = self._spin(); of.addRow("Y:", self._oy) - self._oz = self._spin(); of.addRow("Z:", self._oz) + self._ox = self._spin() + of.addRow("X:", self._ox) + self._oy = self._spin() + of.addRow("Y:", self._oy) + self._oz = self._spin() + of.addRow("Z:", self._oz) root.addWidget(origin_box) rot_box = QGroupBox("Rotation about axes (degrees)") rf = QFormLayout(rot_box) - self._rx = self._rot_spin(); rf.addRow("about X:", self._rx) - self._ry = self._rot_spin(); rf.addRow("about Y:", self._ry) - self._rz = self._rot_spin(); rf.addRow("about Z:", self._rz) + self._rx = self._rot_spin() + rf.addRow("about X:", self._rx) + self._ry = self._rot_spin() + rf.addRow("about Y:", self._ry) + self._rz = self._rot_spin() + rf.addRow("about Z:", self._rz) root.addWidget(rot_box) buttons = QDialogButtonBox( - QDialogButtonBox.StandardButton.Ok - | QDialogButtonBox.StandardButton.Cancel, + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, ) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) diff --git a/src/otko/views/dialogs/material_forms.py b/src/otko/views/dialogs/material_forms.py index 2c3a155..e151f59 100644 --- a/src/otko/views/dialogs/material_forms.py +++ b/src/otko/views/dialogs/material_forms.py @@ -33,10 +33,16 @@ from otko.core import ( # ─────────────────────────── helpers ─────────────────────────── -def _spin(default: float = 0.0, *, decimals: int = 6, - minimum: float = -1e15, maximum: float = 1e15, - step: float = 1.0) -> QDoubleSpinBox: +def _spin( + default: float = 0.0, + *, + decimals: int = 6, + minimum: float = -1e15, + maximum: float = 1e15, + step: float = 1.0, +) -> QDoubleSpinBox: from PySide6.QtCore import QLocale + sb = QDoubleSpinBox() # Force C locale so "." is always the decimal separator. sb.setLocale(QLocale(QLocale.Language.C)) @@ -110,8 +116,11 @@ class Steel01Form(MaterialFormBase): def _read_specific(self, mid: int) -> Steel01: return Steel01( - id=mid, name=self._name_edit.text(), - Fy=self._fy.value(), E0=self._e0.value(), b=self._b.value(), + id=mid, + name=self._name_edit.text(), + Fy=self._fy.value(), + E0=self._e0.value(), + b=self._b.value(), ) @@ -127,19 +136,34 @@ class Steel02Form(MaterialFormBase): self._r0 = _spin(18.0, decimals=2, minimum=10.0, maximum=20.0, step=0.5) self._cR1 = _spin(0.925, decimals=4, step=0.01) self._cR2 = _spin(0.15, decimals=4, step=0.01) - for label, w in (("Fy:", self._fy), ("E0:", self._e0), ("b:", self._b), - ("R0:", self._r0), ("cR1:", self._cR1), ("cR2:", self._cR2)): + for label, w in ( + ("Fy:", self._fy), + ("E0:", self._e0), + ("b:", self._b), + ("R0:", self._r0), + ("cR1:", self._cR1), + ("cR2:", self._cR2), + ): self._layout.addRow(label, w) def _populate_specific(self, m: Steel02) -> None: - self._fy.setValue(m.Fy); self._e0.setValue(m.E0); self._b.setValue(m.b) - self._r0.setValue(m.R0); self._cR1.setValue(m.cR1); self._cR2.setValue(m.cR2) + self._fy.setValue(m.Fy) + self._e0.setValue(m.E0) + self._b.setValue(m.b) + self._r0.setValue(m.R0) + self._cR1.setValue(m.cR1) + self._cR2.setValue(m.cR2) def _read_specific(self, mid: int) -> Steel02: return Steel02( - id=mid, name=self._name_edit.text(), - Fy=self._fy.value(), E0=self._e0.value(), b=self._b.value(), - R0=self._r0.value(), cR1=self._cR1.value(), cR2=self._cR2.value(), + id=mid, + name=self._name_edit.text(), + Fy=self._fy.value(), + E0=self._e0.value(), + b=self._b.value(), + R0=self._r0.value(), + cR1=self._cR1.value(), + cR2=self._cR2.value(), ) @@ -156,19 +180,28 @@ class Concrete01Form(MaterialFormBase): self._epsc0 = _spin(-0.002, decimals=6, minimum=-1.0, maximum=0.0, step=1e-4) self._fpcu = _spin(-15e6, step=1.0, maximum=0.0) self._epsU = _spin(-0.005, decimals=6, minimum=-1.0, maximum=0.0, step=1e-4) - for label, w in (("f'c (-):", self._fpc), ("ε_c0 (-):", self._epsc0), - ("f'cu (-):", self._fpcu), ("ε_U (-):", self._epsU)): + for label, w in ( + ("f'c (-):", self._fpc), + ("ε_c0 (-):", self._epsc0), + ("f'cu (-):", self._fpcu), + ("ε_U (-):", self._epsU), + ): self._layout.addRow(label, w) def _populate_specific(self, m: Concrete01) -> None: - self._fpc.setValue(m.fpc); self._epsc0.setValue(m.epsc0) - self._fpcu.setValue(m.fpcu); self._epsU.setValue(m.epsU) + self._fpc.setValue(m.fpc) + self._epsc0.setValue(m.epsc0) + self._fpcu.setValue(m.fpcu) + self._epsU.setValue(m.epsU) def _read_specific(self, mid: int) -> Concrete01: return Concrete01( - id=mid, name=self._name_edit.text(), - fpc=self._fpc.value(), epsc0=self._epsc0.value(), - fpcu=self._fpcu.value(), epsU=self._epsU.value(), + id=mid, + name=self._name_edit.text(), + fpc=self._fpc.value(), + epsc0=self._epsc0.value(), + fpcu=self._fpcu.value(), + epsU=self._epsU.value(), ) @@ -187,23 +220,36 @@ class Concrete02Form(MaterialFormBase): self._lambda = _spin(0.1, decimals=4, minimum=0.0, maximum=1.0, step=0.01) self._ft = _spin(3e6, step=1.0, minimum=1e-9) self._ets = _spin(2e9, step=1.0, minimum=1e-9) - for label, w in (("f'c (-):", self._fpc), ("ε_c0 (-):", self._epsc0), - ("f'cu (-):", self._fpcu), ("ε_U (-):", self._epsU), - ("λ (unload ratio):", self._lambda), - ("ft (tensile):", self._ft), ("Ets (soften):", self._ets)): + for label, w in ( + ("f'c (-):", self._fpc), + ("ε_c0 (-):", self._epsc0), + ("f'cu (-):", self._fpcu), + ("ε_U (-):", self._epsU), + ("λ (unload ratio):", self._lambda), + ("ft (tensile):", self._ft), + ("Ets (soften):", self._ets), + ): self._layout.addRow(label, w) def _populate_specific(self, m: Concrete02) -> None: - self._fpc.setValue(m.fpc); self._epsc0.setValue(m.epsc0) - self._fpcu.setValue(m.fpcu); self._epsU.setValue(m.epsU) - self._lambda.setValue(m.lambda_); self._ft.setValue(m.ft); self._ets.setValue(m.Ets) + self._fpc.setValue(m.fpc) + self._epsc0.setValue(m.epsc0) + self._fpcu.setValue(m.fpcu) + self._epsU.setValue(m.epsU) + self._lambda.setValue(m.lambda_) + self._ft.setValue(m.ft) + self._ets.setValue(m.Ets) def _read_specific(self, mid: int) -> Concrete02: return Concrete02( - id=mid, name=self._name_edit.text(), - fpc=self._fpc.value(), epsc0=self._epsc0.value(), - fpcu=self._fpcu.value(), epsU=self._epsU.value(), - ft=self._ft.value(), Ets=self._ets.value(), + id=mid, + name=self._name_edit.text(), + fpc=self._fpc.value(), + epsc0=self._epsc0.value(), + fpcu=self._fpcu.value(), + epsU=self._epsU.value(), + ft=self._ft.value(), + Ets=self._ets.value(), **{"lambda": self._lambda.value()}, ) @@ -243,9 +289,12 @@ class Concrete04Form(MaterialFormBase): fct_val = self._fct.value() et_val = self._et.value() return Concrete04( - id=mid, name=self._name_edit.text(), - fpc=self._fpc.value(), epsc0=self._epsc0.value(), - epscu=self._epscu.value(), Ec=self._Ec.value(), + id=mid, + name=self._name_edit.text(), + fpc=self._fpc.value(), + epsc0=self._epsc0.value(), + epscu=self._epscu.value(), + Ec=self._Ec.value(), fct=fct_val if fct_val > 0.0 else None, et=et_val if et_val > 0.0 else None, ) @@ -263,12 +312,15 @@ class ElasticUniaxialForm(MaterialFormBase): self._layout.addRow("η (damping):", self._eta) def _populate_specific(self, m: ElasticUniaxial) -> None: - self._e.setValue(m.E); self._eta.setValue(m.eta) + self._e.setValue(m.E) + self._eta.setValue(m.eta) def _read_specific(self, mid: int) -> ElasticUniaxial: return ElasticUniaxial( - id=mid, name=self._name_edit.text(), - E=self._e.value(), eta=self._eta.value(), + id=mid, + name=self._name_edit.text(), + E=self._e.value(), + eta=self._eta.value(), ) @@ -286,12 +338,17 @@ class ElasticIsotropicForm(MaterialFormBase): self._layout.addRow("ρ (density):", self._rho) def _populate_specific(self, m: ElasticIsotropic) -> None: - self._e.setValue(m.E); self._nu.setValue(m.nu); self._rho.setValue(m.rho) + self._e.setValue(m.E) + self._nu.setValue(m.nu) + self._rho.setValue(m.rho) def _read_specific(self, mid: int) -> ElasticIsotropic: return ElasticIsotropic( - id=mid, name=self._name_edit.text(), - E=self._e.value(), nu=self._nu.value(), rho=self._rho.value(), + id=mid, + name=self._name_edit.text(), + E=self._e.value(), + nu=self._nu.value(), + rho=self._rho.value(), ) @@ -307,12 +364,15 @@ class ElasticPPForm(MaterialFormBase): self._layout.addRow("ε_y (yield strain):", self._epsy) def _populate_specific(self, m: ElasticPP) -> None: - self._e.setValue(m.E); self._epsy.setValue(m.epsy_pos) + self._e.setValue(m.E) + self._epsy.setValue(m.epsy_pos) def _read_specific(self, mid: int) -> ElasticPP: return ElasticPP( - id=mid, name=self._name_edit.text(), - E=self._e.value(), epsy_pos=self._epsy.value(), + id=mid, + name=self._name_edit.text(), + E=self._e.value(), + epsy_pos=self._epsy.value(), ) diff --git a/src/otko/views/dialogs/material_library.py b/src/otko/views/dialogs/material_library.py index 93ad07a..371a507 100644 --- a/src/otko/views/dialogs/material_library.py +++ b/src/otko/views/dialogs/material_library.py @@ -7,9 +7,9 @@ committed. Each Add/Edit/Delete is its own undoable step. from __future__ import annotations +from pydantic import ValidationError from PySide6.QtCore import Qt from PySide6.QtWidgets import ( - QComboBox, QDialog, QDialogButtonBox, QHBoxLayout, @@ -23,7 +23,6 @@ from PySide6.QtWidgets import ( QVBoxLayout, QWidget, ) -from pydantic import ValidationError from otko.commands import ( AddMaterialsCommand, @@ -150,7 +149,12 @@ class MaterialLibraryDialog(QDialog): return kinds = list(FORM_REGISTRY.keys()) kind, ok = QInputDialog.getItem( - self, "Add material", "Type:", kinds, current=0, editable=False, + self, + "Add material", + "Type:", + kinds, + current=0, + editable=False, ) if not ok: return @@ -179,9 +183,10 @@ class MaterialLibraryDialog(QDialog): if material is None: return reply = QMessageBox.question( - self, "Delete material", + self, + "Delete material", f"Delete material #{material.id} ({material.type})?\n" - "Elements that reference it will be invalid until reassigned." + "Elements that reference it will be invalid until reassigned.", ) if reply != QMessageBox.StandardButton.Yes: return diff --git a/src/otko/views/dialogs/mirror.py b/src/otko/views/dialogs/mirror.py index d5131e7..7be07f6 100644 --- a/src/otko/views/dialogs/mirror.py +++ b/src/otko/views/dialogs/mirror.py @@ -24,12 +24,12 @@ class MirrorDialog(QDialog): def _build_ui(self, n_nodes: int, n_elements: int) -> None: layout = QVBoxLayout(self) - layout.addWidget(QLabel( - f"Mirror {n_nodes} node(s) and {n_elements} element(s)." - )) - layout.addWidget(QLabel( - "Only elements whose endpoints are both in the selection are copied." - )) + layout.addWidget( + QLabel(f"Mirror {n_nodes} node(s) and {n_elements} element(s).") + ) + layout.addWidget( + QLabel("Only elements whose endpoints are both in the selection are copied.") + ) box = QGroupBox("Reflection plane (passes through origin)") box_layout = QVBoxLayout(box) diff --git a/src/otko/views/dialogs/path_time_series.py b/src/otko/views/dialogs/path_time_series.py index af5df29..66a6b34 100644 --- a/src/otko/views/dialogs/path_time_series.py +++ b/src/otko/views/dialogs/path_time_series.py @@ -44,11 +44,13 @@ class PathTimeSeriesDialog(QDialog): def _build_ui(self) -> None: root = QVBoxLayout(self) - root.addWidget(QLabel( - "Path TimeSeries — tabulated values sampled at a " - "uniform time step. Used by UniformExcitation (ground " - "motion) and by PlainLoadPattern scaled forces." - )) + root.addWidget( + QLabel( + "Path TimeSeries — tabulated values sampled at a " + "uniform time step. Used by UniformExcitation (ground " + "motion) and by PlainLoadPattern scaled forces." + ) + ) form = QFormLayout() self._name_edit = QLineEdit("GroundMotion") @@ -65,7 +67,7 @@ class PathTimeSeriesDialog(QDialog): self._factor_spin.setRange(-1e12, 1e12) self._factor_spin.setDecimals(6) self._factor_spin.setSingleStep(1.0) - self._factor_spin.setValue(386.4) # default: convert g → in/s² + self._factor_spin.setValue(386.4) # default: convert g → in/s² self._factor_spin.setToolTip( "Multiplier applied to every value at runtime. Typical use: " "386.4 for ground motion in g → in/s² (US_IN_KIP), 9.81 for " @@ -84,9 +86,7 @@ class PathTimeSeriesDialog(QDialog): btn_row.addWidget(self._btn_plain) root.addLayout(btn_row) - self._status = QLabel( - "No data loaded — use one of the import buttons." - ) + self._status = QLabel("No data loaded — use one of the import buttons.") self._status.setWordWrap(True) self._status.setStyleSheet("color: #666;") root.addWidget(self._status) @@ -102,8 +102,7 @@ class PathTimeSeriesDialog(QDialog): root.addWidget(self._preview, 1) buttons = QDialogButtonBox( - QDialogButtonBox.StandardButton.Ok - | QDialogButtonBox.StandardButton.Cancel, + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, ) buttons.accepted.connect(self._on_accept) buttons.rejected.connect(self.reject) @@ -112,14 +111,16 @@ class PathTimeSeriesDialog(QDialog): # ── file-import slots ─────────────────────────────────────────── def _on_import_peer(self) -> None: fname, _ = QFileDialog.getOpenFileName( - self, "Import PEER record", - "", "PEER records (*.at2 *.AT2);;All files (*)", + self, + "Import PEER record", + "", + "PEER records (*.at2 *.AT2);;All files (*)", ) if not fname: return try: dt, npts, vals = parse_peer_record(fname) - except Exception as exc: # noqa: BLE001 + except Exception as exc: QMessageBox.critical(self, "PEER import failed", str(exc)) return self._values = vals @@ -132,20 +133,21 @@ class PathTimeSeriesDialog(QDialog): def _on_import_plain(self) -> None: fname, _ = QFileDialog.getOpenFileName( - self, "Import plain values", - "", "Text files (*.txt *.csv *.dat);;All files (*)", + self, + "Import plain values", + "", + "Text files (*.txt *.csv *.dat);;All files (*)", ) if not fname: return try: vals = parse_plain_values(fname) - except Exception as exc: # noqa: BLE001 + except Exception as exc: QMessageBox.critical(self, "Import failed", str(exc)) return self._values = vals self._status.setText( - f"Loaded {len(vals)} values from plain-text file. " - "Set Δt manually above." + f"Loaded {len(vals)} values from plain-text file. " "Set Δt manually above." ) self._refresh_preview() @@ -167,7 +169,9 @@ class PathTimeSeriesDialog(QDialog): def _on_accept(self) -> None: if not self._values: QMessageBox.warning( - self, "No data", "Import a record first.", + self, + "No data", + "Import a record first.", ) return self.accept() diff --git a/src/otko/views/dialogs/quick_grid_lines.py b/src/otko/views/dialogs/quick_grid_lines.py index 876a2f3..7928d60 100644 --- a/src/otko/views/dialogs/quick_grid_lines.py +++ b/src/otko/views/dialogs/quick_grid_lines.py @@ -11,7 +11,6 @@ from PySide6.QtWidgets import ( QDialog, QDialogButtonBox, QDoubleSpinBox, - QFormLayout, QGroupBox, QHBoxLayout, QLabel, @@ -31,39 +30,56 @@ class QuickGridLinesDialog(QDialog): def _build_ui(self) -> None: root = QVBoxLayout(self) - root.addWidget(QLabel( - "Quick-define a regular (Cartesian) grid.
" - "Each axis: number of lines, equal spacing, first line coordinate." - )) + root.addWidget( + QLabel( + "Quick-define a regular (Cartesian) grid.
" + "Each axis: number of lines, equal spacing, first line coordinate." + ) + ) self._x_n, self._x_s, self._x_f = self._axis_group("X Grid Data", root) self._y_n, self._y_s, self._y_f = self._axis_group("Y Grid Data", root) self._z_n, self._z_s, self._z_f = self._axis_group("Z Grid Data", root) # Sensible defaults. - self._x_n.setValue(3); self._x_s.setValue(6.0); self._x_f.setValue(0.0) - self._y_n.setValue(3); self._y_s.setValue(6.0); self._y_f.setValue(0.0) - self._z_n.setValue(2); self._z_s.setValue(3.0); self._z_f.setValue(0.0) + self._x_n.setValue(3) + self._x_s.setValue(6.0) + self._x_f.setValue(0.0) + self._y_n.setValue(3) + self._y_s.setValue(6.0) + self._y_f.setValue(0.0) + self._z_n.setValue(2) + self._z_s.setValue(3.0) + self._z_f.setValue(0.0) buttons = QDialogButtonBox( - QDialogButtonBox.StandardButton.Ok - | QDialogButtonBox.StandardButton.Cancel, + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, ) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) root.addWidget(buttons) def _axis_group( - self, title: str, parent_layout: QVBoxLayout, + self, + title: str, + parent_layout: QVBoxLayout, ) -> tuple[QSpinBox, QDoubleSpinBox, QDoubleSpinBox]: box = QGroupBox(title) row = QHBoxLayout(box) - n = QSpinBox(); n.setRange(0, 200); n.setValue(3) - s = QDoubleSpinBox(); s.setRange(0.0, 1e6); s.setDecimals(4) - s.setSingleStep(0.5); s.setValue(1.0) - f = QDoubleSpinBox(); f.setRange(-1e6, 1e6); f.setDecimals(4) - f.setSingleStep(0.5); f.setValue(0.0) + n = QSpinBox() + n.setRange(0, 200) + n.setValue(3) + s = QDoubleSpinBox() + s.setRange(0.0, 1e6) + s.setDecimals(4) + s.setSingleStep(0.5) + s.setValue(1.0) + f = QDoubleSpinBox() + f.setRange(-1e6, 1e6) + f.setDecimals(4) + f.setSingleStep(0.5) + f.setValue(0.0) row.addWidget(QLabel("Number of lines:")) row.addWidget(n) @@ -76,8 +92,10 @@ class QuickGridLinesDialog(QDialog): def ordinates(self) -> tuple[list[float], list[float], list[float]]: """Return (xs, ys, zs) — flat lists of ordinate values.""" + def axis(n: QSpinBox, s: QDoubleSpinBox, f: QDoubleSpinBox) -> list[float]: return [f.value() + i * s.value() for i in range(n.value())] + return ( axis(self._x_n, self._x_s, self._x_f), axis(self._y_n, self._y_s, self._y_f), diff --git a/src/otko/views/dialogs/quick_guide.py b/src/otko/views/dialogs/quick_guide.py index 85daf78..9b0c424 100644 --- a/src/otko/views/dialogs/quick_guide.py +++ b/src/otko/views/dialogs/quick_guide.py @@ -29,7 +29,7 @@ with Define → Add Plain Load Pattern… or Define → Add Uniform Excitation… for seismic input.

4 — Draw the model

-

Tools toolbar (left): Select, Draw Node (F1), +

Tools toolbar (top): Select, Draw Node (F1), Draw Frame (F2), Draw Truss (F3, icon-less by design). Define → Coordinate System/Grids… (Ctrl+G) and Add Node… (Ctrl+N) for precise layout; use the top-toolbar Level combo in Top/Front/Right views.

diff --git a/src/otko/views/dialogs/replicate.py b/src/otko/views/dialogs/replicate.py index 2c63cef..f627ec3 100644 --- a/src/otko/views/dialogs/replicate.py +++ b/src/otko/views/dialogs/replicate.py @@ -42,12 +42,12 @@ class ReplicateDialog(QDialog): def _build_ui(self, n_nodes: int, n_elements: int) -> None: layout = QVBoxLayout(self) - layout.addWidget(QLabel( - f"Replicate {n_nodes} node(s) and {n_elements} element(s)." - )) - layout.addWidget(QLabel( - "Only elements whose endpoints are both in the selection are copied." - )) + layout.addWidget( + QLabel(f"Replicate {n_nodes} node(s) and {n_elements} element(s).") + ) + layout.addWidget( + QLabel("Only elements whose endpoints are both in the selection are copied.") + ) form = QFormLayout() diff --git a/src/otko/views/dialogs/run_analysis.py b/src/otko/views/dialogs/run_analysis.py index 4833400..b1beb12 100644 --- a/src/otko/views/dialogs/run_analysis.py +++ b/src/otko/views/dialogs/run_analysis.py @@ -57,6 +57,18 @@ class RunAnalysisDialog(QDialog): row.addWidget(self._case_combo, stretch=1) layout.addLayout(row) + combo_row = QHBoxLayout() + combo_row.addWidget(QLabel("Combination:")) + self._combo_combo = QComboBox() + combo_row.addWidget(self._combo_combo, stretch=1) + self._eval_btn = QPushButton("Evaluate") + self._eval_btn.setToolTip( + "Run each referenced static case, then combine results " + "in post-processing (linear superposition, no extra solver input)." + ) + combo_row.addWidget(self._eval_btn) + layout.addLayout(combo_row) + # Run-time damping overrides for transient cases only. self._damping_box = QGroupBox("Rayleigh damping (transient only)") form = QFormLayout(self._damping_box) @@ -79,12 +91,14 @@ class RunAnalysisDialog(QDialog): self._mode1_damping.setSingleStep(0.01) form.addRow("Mode-1 damping zeta:", self._mode1_damping) - form.addRow(QLabel( - "Values are applied for this run only via a copy of the case — " - "the stored case is not modified. If mode-1 damping is " - "greater than zero, the runner computes betaK from the first " - "mode after preload and overrides the manual betaK value.", - )) + form.addRow( + QLabel( + "Values are applied for this run only via a copy of the case — " + "the stored case is not modified. If mode-1 damping is " + "greater than zero, the runner computes betaK from the first " + "mode after preload and overrides the manual betaK value.", + ) + ) layout.addWidget(self._damping_box) self._progress = QProgressBar() @@ -98,7 +112,8 @@ class RunAnalysisDialog(QDialog): layout.addWidget(self._log, stretch=1) self._buttons = QDialogButtonBox( - QDialogButtonBox.StandardButton.Close, parent=self, + QDialogButtonBox.StandardButton.Close, + parent=self, ) self._run_btn = QPushButton("Run") self._buttons.addButton(self._run_btn, QDialogButtonBox.ButtonRole.ActionRole) @@ -106,6 +121,7 @@ class RunAnalysisDialog(QDialog): def _wire(self) -> None: self._run_btn.clicked.connect(self._on_run) + self._eval_btn.clicked.connect(self._on_evaluate_combo) self._buttons.rejected.connect(self.reject) self._case_combo.currentIndexChanged.connect(self._on_case_changed) @@ -133,15 +149,21 @@ class RunAnalysisDialog(QDialog): def _populate_cases(self) -> None: self._case_combo.clear() + self._combo_combo.clear() if self._vm.project is None: self._run_btn.setEnabled(False) + self._eval_btn.setEnabled(False) self._damping_box.setVisible(False) return for case in self._vm.project.analyses: label = f"#{case.id} {case.name or '(unnamed)'} [{case.type}]" self._case_combo.addItem(label, userData=case.id) + for combo in self._vm.project.combinations: + label = f"#{combo.id} {combo.name or '(unnamed)'} [{combo.kind}]" + self._combo_combo.addItem(label, userData=combo.id) has_cases = self._case_combo.count() > 0 self._run_btn.setEnabled(has_cases) + self._eval_btn.setEnabled(self._combo_combo.count() > 0) if not has_cases: # No transient case can be selected, so the run-only damping # overrides are meaningless — hide them alongside the disabled Run. @@ -157,11 +179,13 @@ class RunAnalysisDialog(QDialog): return if case.type == "Transient": mode1 = float(self._mode1_damping.value()) - case = case.model_copy(update={ - "rayleigh_alpha_m": float(self._alpha_m.value()), - "rayleigh_beta_k": float(self._beta_k.value()), - "rayleigh_mode1_damping": mode1 if mode1 > 0.0 else None, - }) + case = case.model_copy( + update={ + "rayleigh_alpha_m": float(self._alpha_m.value()), + "rayleigh_beta_k": float(self._beta_k.value()), + "rayleigh_mode1_damping": mode1 if mode1 > 0.0 else None, + } + ) self._results = None self._log.clear() results_dir: Path | None = None @@ -169,9 +193,52 @@ class RunAnalysisDialog(QDialog): results_dir = self._vm.path.parent / f"{self._vm.path.stem}_results" try: self._runner.run(self._vm.project, case, results_dir=results_dir) - except Exception as exc: # noqa: BLE001 + except Exception as exc: self._log.appendPlainText(f"Could not start: {exc}") + def _on_evaluate_combo(self) -> None: + """Run each referenced static case, then combine in post-processing. + + Synchronous (linear statics are fast): each case runs through + :class:`OpenSeesRunner` on the GUI thread, then + :func:`evaluate_combination` superposes the results. The combined + output is forwarded through ``runner.finished`` so the main + window displays it like any other result. + """ + from otko.services.combinations import evaluate_combination + from otko.services.opensees_runner import OpenSeesRunner + + if self._runner.is_running or self._vm.project is None: + return + combo_id = self._combo_combo.currentData() + project = self._vm.project + combo = next((c for c in project.combinations if c.id == combo_id), None) + if combo is None: + return + self._results = None + self._log.clear() + self._log.appendPlainText(f"Evaluating combination '{combo.name}' [{combo.kind}] ...") + try: + case_by_id = {c.id: c for c in project.analyses} + collected: dict[int, Any] = {} + for item in combo.items: + case = case_by_id.get(item.case_id) + if case is None: + raise ValueError(f"Combination references missing case id={item.case_id}.") + self._log.appendPlainText( + f" running case #{case.id} '{case.name}' × {item.factor:g} ..." + ) + collected[item.case_id] = OpenSeesRunner(project).run(case) + combined = evaluate_combination(collected, combo) + self._results = combined + self._log.appendPlainText(f"--- Done. Returned {type(combined).__name__}. ---") + self._runner.finished.emit(combined) + except Exception as exc: + import traceback + + self._log.appendPlainText("--- FAILED ---") + self._log.appendPlainText(f"{exc}\n{traceback.format_exc()}") + def _on_started(self) -> None: self._log.appendPlainText("--- Analysis started ---") @@ -191,4 +258,6 @@ class RunAnalysisDialog(QDialog): self._progress.setVisible(running) # With zero cases Run must stay disabled even when idle. self._run_btn.setEnabled((not running) and self._case_combo.count() > 0) + self._eval_btn.setEnabled((not running) and self._combo_combo.count() > 0) self._case_combo.setEnabled(not running) + self._combo_combo.setEnabled(not running) diff --git a/src/otko/views/dialogs/section_editor.py b/src/otko/views/dialogs/section_editor.py index ba2937c..fdb9031 100644 --- a/src/otko/views/dialogs/section_editor.py +++ b/src/otko/views/dialogs/section_editor.py @@ -12,7 +12,6 @@ from __future__ import annotations from typing import Any -import numpy as np import pyqtgraph as pg from PySide6.QtCore import Qt from PySide6.QtWidgets import ( @@ -45,22 +44,31 @@ from otko.services.section_properties import ( ) _PATCH_COLORS = [ - "#4e79a7", "#f28e2b", "#e15759", "#76b7b2", - "#59a14f", "#edc948", "#b07aa1", "#ff9da7", + "#4e79a7", + "#f28e2b", + "#e15759", + "#76b7b2", + "#59a14f", + "#edc948", + "#b07aa1", + "#ff9da7", ] class FiberSectionEditor(QDialog): """Modal dialog: build a FiberSection from patches + layers.""" - def __init__(self, material_ids: list[int], - existing: FiberSection | None = None, - parent: QWidget | None = None) -> None: + def __init__( + self, + material_ids: list[int], + existing: FiberSection | None = None, + parent: QWidget | None = None, + ) -> None: super().__init__(parent) self.setWindowTitle("Fiber Section Editor") self.resize(1000, 650) self._material_ids = material_ids - self._patches: list[Any] = [] # RectangularPatch | CircularPatch + self._patches: list[Any] = [] # RectangularPatch | CircularPatch self._layers: list[StraightLayer] = [] self._section_id: int = existing.id if existing else 1 self._section_name: str = existing.name if existing else "" @@ -106,25 +114,37 @@ class FiberSectionEditor(QDialog): pf.addRow("Material:", self._patch_mat) # Rect fields - self._rect_yi = self._spin(-0.15); self._rect_zi = self._spin(-0.15) - self._rect_yj = self._spin(0.15); self._rect_zj = self._spin(0.15) - self._rect_ny = self._ispin(8); self._rect_nz = self._ispin(8) + self._rect_yi = self._spin(-0.15) + self._rect_zi = self._spin(-0.15) + self._rect_yj = self._spin(0.15) + self._rect_zj = self._spin(0.15) + self._rect_ny = self._ispin(8) + self._rect_nz = self._ispin(8) self._rect_rows = [ - ("y_i:", self._rect_yi), ("z_i:", self._rect_zi), - ("y_j:", self._rect_yj), ("z_j:", self._rect_zj), - ("n_fib_y:", self._rect_ny), ("n_fib_z:", self._rect_nz), + ("y_i:", self._rect_yi), + ("z_i:", self._rect_zi), + ("y_j:", self._rect_yj), + ("z_j:", self._rect_zj), + ("n_fib_y:", self._rect_ny), + ("n_fib_z:", self._rect_nz), ] for label, widget in self._rect_rows: pf.addRow(label, widget) # Circ fields (initially hidden) - self._circ_yc = self._spin(0.0); self._circ_zc = self._spin(0.0) - self._circ_ri = self._spin(0.0); self._circ_ro = self._spin(0.15) - self._circ_nc = self._ispin(16); self._circ_nr = self._ispin(4) + self._circ_yc = self._spin(0.0) + self._circ_zc = self._spin(0.0) + self._circ_ri = self._spin(0.0) + self._circ_ro = self._spin(0.15) + self._circ_nc = self._ispin(16) + self._circ_nr = self._ispin(4) self._circ_rows = [ - ("y_center:", self._circ_yc), ("z_center:", self._circ_zc), - ("r_inner:", self._circ_ri), ("r_outer:", self._circ_ro), - ("n_circ:", self._circ_nc), ("n_rad:", self._circ_nr), + ("y_center:", self._circ_yc), + ("z_center:", self._circ_zc), + ("r_inner:", self._circ_ri), + ("r_outer:", self._circ_ro), + ("n_circ:", self._circ_nc), + ("n_rad:", self._circ_nr), ] for label, widget in self._circ_rows: pf.addRow(label, widget) @@ -145,12 +165,16 @@ class FiberSectionEditor(QDialog): lf.addRow("Material:", self._layer_mat) self._layer_nbars = self._ispin(4) self._layer_area = self._spin(0.0005, step=0.0001, minimum=1e-12) - self._layer_ys = self._spin(-0.12); self._layer_zs = self._spin(-0.12) - self._layer_ye = self._spin(0.12); self._layer_ze = self._spin(-0.12) + self._layer_ys = self._spin(-0.12) + self._layer_zs = self._spin(-0.12) + self._layer_ye = self._spin(0.12) + self._layer_ze = self._spin(-0.12) lf.addRow("n_bars:", self._layer_nbars) lf.addRow("bar_area:", self._layer_area) - lf.addRow("y_start:", self._layer_ys); lf.addRow("z_start:", self._layer_zs) - lf.addRow("y_end:", self._layer_ye); lf.addRow("z_end:", self._layer_ze) + lf.addRow("y_start:", self._layer_ys) + lf.addRow("z_start:", self._layer_zs) + lf.addRow("y_end:", self._layer_ye) + lf.addRow("z_end:", self._layer_ze) self._add_layer_btn = QPushButton("Add layer") self._add_layer_btn.clicked.connect(self._on_add_layer) lf.addRow(self._add_layer_btn) @@ -197,8 +221,7 @@ class FiberSectionEditor(QDialog): # ── helpers ───────────────────────────────────────────────────── @staticmethod - def _spin(default: float = 0.0, *, step: float = 0.01, - minimum: float = -1e6) -> QDoubleSpinBox: + def _spin(default: float = 0.0, *, step: float = 0.01, minimum: float = -1e6) -> QDoubleSpinBox: sb = QDoubleSpinBox() sb.setRange(minimum, 1e6) sb.setDecimals(6) @@ -214,7 +237,7 @@ class FiberSectionEditor(QDialog): return sb def _on_patch_type_changed(self, idx: int) -> None: - is_rect = (idx == 0) + is_rect = idx == 0 for _, w in self._rect_rows: w.setVisible(is_rect) for _, w in self._circ_rows: @@ -226,28 +249,38 @@ class FiberSectionEditor(QDialog): if self._patch_type.currentIndex() == 0: p = RectangularPatch( material_id=mid, - n_fib_y=self._rect_ny.value(), n_fib_z=self._rect_nz.value(), - y_i=self._rect_yi.value(), z_i=self._rect_zi.value(), - y_j=self._rect_yj.value(), z_j=self._rect_zj.value(), + n_fib_y=self._rect_ny.value(), + n_fib_z=self._rect_nz.value(), + y_i=self._rect_yi.value(), + z_i=self._rect_zi.value(), + y_j=self._rect_yj.value(), + z_j=self._rect_zj.value(), ) self._patches.append(p) - self._item_list.addItem(QListWidgetItem( - f"Rect patch mat={mid} " - f"({p.y_i:.3f},{p.z_i:.3f})→({p.y_j:.3f},{p.z_j:.3f}) " - f"{p.n_fib_y}×{p.n_fib_z}", - )) + self._item_list.addItem( + QListWidgetItem( + f"Rect patch mat={mid} " + f"({p.y_i:.3f},{p.z_i:.3f})→({p.y_j:.3f},{p.z_j:.3f}) " + f"{p.n_fib_y}×{p.n_fib_z}", + ) + ) else: p = CircularPatch( material_id=mid, - n_fib_circ=self._circ_nc.value(), n_fib_rad=self._circ_nr.value(), - y_center=self._circ_yc.value(), z_center=self._circ_zc.value(), - r_inner=self._circ_ri.value(), r_outer=self._circ_ro.value(), + n_fib_circ=self._circ_nc.value(), + n_fib_rad=self._circ_nr.value(), + y_center=self._circ_yc.value(), + z_center=self._circ_zc.value(), + r_inner=self._circ_ri.value(), + r_outer=self._circ_ro.value(), ) self._patches.append(p) - self._item_list.addItem(QListWidgetItem( - f"Circ patch mat={mid} " - f"r={p.r_inner:.3f}→{p.r_outer:.3f} {p.n_fib_circ}×{p.n_fib_rad}", - )) + self._item_list.addItem( + QListWidgetItem( + f"Circ patch mat={mid} " + f"r={p.r_inner:.3f}→{p.r_outer:.3f} {p.n_fib_circ}×{p.n_fib_rad}", + ) + ) self._refresh_preview() def _on_add_layer(self) -> None: @@ -256,13 +289,17 @@ class FiberSectionEditor(QDialog): material_id=mid, n_bars=self._layer_nbars.value(), bar_area=self._layer_area.value(), - y_start=self._layer_ys.value(), z_start=self._layer_zs.value(), - y_end=self._layer_ye.value(), z_end=self._layer_ze.value(), + y_start=self._layer_ys.value(), + z_start=self._layer_zs.value(), + y_end=self._layer_ye.value(), + z_end=self._layer_ze.value(), ) self._layers.append(lay) - self._item_list.addItem(QListWidgetItem( - f"Layer mat={mid} {lay.n_bars} bars A={lay.bar_area:.4g}", - )) + self._item_list.addItem( + QListWidgetItem( + f"Layer mat={mid} {lay.n_bars} bars A={lay.bar_area:.4g}", + ) + ) self._refresh_preview() def _on_remove(self) -> None: @@ -280,14 +317,12 @@ class FiberSectionEditor(QDialog): # ── preview ───────────────────────────────────────────────────── def _refresh_preview(self) -> None: self._preview.clear() - sec = FiberSection(id=999999, patches=list(self._patches), - layers=list(self._layers)) + sec = FiberSection(id=999999, patches=list(self._patches), layers=list(self._layers)) props = compute_section_props(sec) if props.n_fibres == 0: self._props_label.setText("Add patches or layers to see the preview.") return - f = props.fibre_yz # Color by material: assign a palette index per unique material_id. mat_ids = [] for p in self._patches: @@ -295,8 +330,9 @@ class FiberSectionEditor(QDialog): for lay in self._layers: mat_ids.append(lay.material_id) unique_mats = sorted(set(mat_ids)) if mat_ids else [1] - mat_to_color = {mid: _PATCH_COLORS[i % len(_PATCH_COLORS)] - for i, mid in enumerate(unique_mats)} + mat_to_color = { + mid: _PATCH_COLORS[i % len(_PATCH_COLORS)] for i, mid in enumerate(unique_mats) + } # Draw patch fibres as squares, layer fibres as circles. # Expand per-patch for color assignment. @@ -307,9 +343,13 @@ class FiberSectionEditor(QDialog): continue color = mat_to_color.get(p.material_id, "#888888") self._preview.plot( - sub[:, 0], sub[:, 1], - pen=None, symbol="s", symbolSize=6, - symbolBrush=color, symbolPen=None, + sub[:, 0], + sub[:, 1], + pen=None, + symbol="s", + symbolSize=6, + symbolBrush=color, + symbolPen=None, ) for lay in self._layers: sub_sec = FiberSection(id=999999, layers=[lay]) @@ -318,16 +358,24 @@ class FiberSectionEditor(QDialog): continue color = mat_to_color.get(lay.material_id, "#ff0000") self._preview.plot( - sub[:, 0], sub[:, 1], - pen=None, symbol="o", symbolSize=8, - symbolBrush=color, symbolPen=pg.mkPen("#ffffff", width=1), + sub[:, 0], + sub[:, 1], + pen=None, + symbol="o", + symbolSize=8, + symbolBrush=color, + symbolPen=pg.mkPen("#ffffff", width=1), ) # Centroid marker self._preview.plot( - [props.centroid_y], [props.centroid_z], - pen=None, symbol="+", symbolSize=16, - symbolBrush=None, symbolPen=pg.mkPen("#ff0000", width=2), + [props.centroid_y], + [props.centroid_z], + pen=None, + symbol="+", + symbolSize=16, + symbolBrush=None, + symbolPen=pg.mkPen("#ff0000", width=2), ) self._props_label.setText( @@ -341,16 +389,22 @@ class FiberSectionEditor(QDialog): for p in sec.patches: self._patches.append(p) if isinstance(p, RectangularPatch): - self._item_list.addItem(QListWidgetItem( - f"Rect patch mat={p.material_id} {p.n_fib_y}×{p.n_fib_z}", - )) + self._item_list.addItem( + QListWidgetItem( + f"Rect patch mat={p.material_id} {p.n_fib_y}×{p.n_fib_z}", + ) + ) elif isinstance(p, CircularPatch): - self._item_list.addItem(QListWidgetItem( - f"Circ patch mat={p.material_id} {p.n_fib_circ}×{p.n_fib_rad}", - )) + self._item_list.addItem( + QListWidgetItem( + f"Circ patch mat={p.material_id} {p.n_fib_circ}×{p.n_fib_rad}", + ) + ) for lay in sec.layers: if isinstance(lay, StraightLayer): self._layers.append(lay) - self._item_list.addItem(QListWidgetItem( - f"Layer mat={lay.material_id} {lay.n_bars} bars", - )) + self._item_list.addItem( + QListWidgetItem( + f"Layer mat={lay.material_id} {lay.n_bars} bars", + ) + ) diff --git a/src/otko/views/dialogs/section_forms.py b/src/otko/views/dialogs/section_forms.py index 7276efe..ec386e4 100644 --- a/src/otko/views/dialogs/section_forms.py +++ b/src/otko/views/dialogs/section_forms.py @@ -19,15 +19,23 @@ from PySide6.QtWidgets import ( ) from otko.core import ( + AngleShape, ElasticSection, FiberSection, + PipeShape, + RectShape, SectionAggregator, ) -def _spin(default: float = 0.0, *, decimals: int = 8, - minimum: float = 1e-12, maximum: float = 1e15, - step: float = 1.0) -> QDoubleSpinBox: +def _spin( + default: float = 0.0, + *, + decimals: int = 8, + minimum: float = 1e-12, + maximum: float = 1e15, + step: float = 1.0, +) -> QDoubleSpinBox: sb = QDoubleSpinBox() sb.setRange(minimum, maximum) sb.setDecimals(decimals) @@ -89,14 +97,13 @@ class SectionFormBase(QWidget): class ElasticSectionForm(SectionFormBase): type_label = "Elastic Section" - #: Shape → visible display-only dimensions. ``None`` hides them all. + #: Shape kind → visible display-only dimensions (see the core + #: ``SectionShape`` union). ``None`` hides them all. _SHAPE_DIMS: ClassVar[dict[str | None, tuple[str, ...]]] = { None: (), - "rect": ("b", "h"), - "I": ("b", "d", "tw", "tf"), - "H": ("b", "d", "tw", "tf"), - "pipe": ("diameter",), - "circle": ("diameter",), + "rect": ("b", "d"), + "angle": ("b", "d", "t"), + "pipe": ("od", "t"), } def __init__(self, parent: QWidget | None = None) -> None: @@ -107,9 +114,14 @@ class ElasticSectionForm(SectionFormBase): self._iy = _spin(8.33e-6, step=1e-7) self._g = _spin(80e9, step=1e9) self._j = _spin(1e-6, step=1e-7) - for label, w in (("E:", self._e), ("A:", self._a), - ("Iz:", self._iz), ("Iy:", self._iy), - ("G:", self._g), ("J:", self._j)): + for label, w in ( + ("E:", self._e), + ("A:", self._a), + ("Iz:", self._iz), + ("Iy:", self._iy), + ("G:", self._g), + ("J:", self._j), + ): self._layout.addRow(label, w) self._layout.addRow(QLabel("Iy, G, J required for 3D models.")) @@ -117,14 +129,14 @@ class ElasticSectionForm(SectionFormBase): self._layout.addRow(QLabel("Extruded-view shape (display only).")) self._shape = QComboBox() self._shape.addItem("(none)", None) - for key in ("rect", "I", "H", "pipe", "circle"): + for key in ("rect", "angle", "pipe"): self._shape.addItem(key, key) self._shape.currentIndexChanged.connect(self._update_dim_visibility) self._layout.addRow("Shape:", self._shape) self._dim_spins: dict[str, QDoubleSpinBox] = {} self._dim_labels: dict[str, QLabel] = {} - for key in ("b", "h", "d", "tw", "tf", "diameter"): + for key in ("b", "d", "t", "od"): spin = _opt_spin() lbl = QLabel(f"{key}:") self._dim_spins[key] = spin @@ -150,20 +162,15 @@ class ElasticSectionForm(SectionFormBase): self._g.setValue(s.G) if s.J is not None: self._j.setValue(s.J) - idx = self._shape.findData(s.shape) + kind = getattr(s.shape, "kind", None) if s.shape is not None else None + idx = self._shape.findData(kind) self._shape.setCurrentIndex(idx if idx >= 0 else 0) for key, spin in self._dim_spins.items(): - v = getattr(s, key, None) + v = getattr(s.shape, key, None) if s.shape is not None else None spin.setValue(v if v is not None else _UNSET_SENTINEL) self._update_dim_visibility() def _read_specific(self, sid: int) -> ElasticSection: - shape = self._shape.currentData() - visible = set(self._SHAPE_DIMS.get(shape, ())) - dims = { - key: (_opt_value(spin) if key in visible else None) - for key, spin in self._dim_spins.items() - } return ElasticSection( id=sid, name=self._name_edit.text(), @@ -173,10 +180,38 @@ class ElasticSectionForm(SectionFormBase): Iy=self._iy.value(), G=self._g.value(), J=self._j.value(), - shape=shape, - **dims, # type: ignore[arg-type] + shape=self._read_shape(), ) + def _read_shape(self) -> PipeShape | AngleShape | RectShape | None: + """Build the display-only shape hint from the form fields. + + Dimensions are optional in the UI, so an incomplete shape degrades + to ``None`` instead of raising — the hint is never emitted to + OpenSees and cannot change an analysis result. + """ + + def dim(key: str) -> float | None: + return _opt_value(self._dim_spins[key]) + + kind = self._shape.currentData() + if kind == "rect": + b, d = dim("b"), dim("d") + if b is None or d is None: + return None + return RectShape(b=b, d=d) + if kind == "angle": + b, d, t = dim("b"), dim("d"), dim("t") + if b is None or d is None or t is None: + return None + return AngleShape(b=b, d=d, t=t) + if kind == "pipe": + od, t = dim("od"), dim("t") + if od is None or t is None: + return None + return PipeShape(od=od, t=t) + return None + class FiberSectionSummaryForm(SectionFormBase): """Read-only overview of a :class:`FiberSection`. @@ -196,10 +231,12 @@ class FiberSectionSummaryForm(SectionFormBase): self._summary.setWordWrap(True) self._summary.setStyleSheet("color: #555;") self._layout.addRow(self._summary) - self._layout.addRow(QLabel( - "Edit this fiber section from the Section Library list — " - "Add / Modify uses the visual Fiber Section Editor." - )) + self._layout.addRow( + QLabel( + "Edit this fiber section from the Section Library list — " + "Add / Modify uses the visual Fiber Section Editor." + ) + ) self._cached: FiberSection | None = None def _populate_specific(self, s: FiberSection) -> None: @@ -240,9 +277,9 @@ class SectionAggregatorSummaryForm(SectionFormBase): def _populate_specific(self, s: SectionAggregator) -> None: self._cached = s - pairings = "
".join( - f" mat #{p.material_id} on DOF {p.dof}" for p in s.pairings - ) or "(none)" + pairings = ( + "
".join(f" mat #{p.material_id} on DOF {p.dof}" for p in s.pairings) or "(none)" + ) self._summary.setText( f"{s.name or 'Section Aggregator'}
" f"Wraps section: {s.section_id}
" @@ -268,10 +305,9 @@ def form_for(section: Any) -> SectionFormBase: # Graceful fallback — unknown section types display a minimal # placeholder instead of crashing the entire dialog. form = SectionFormBase() - form._layout.addRow(QLabel( - f"No form registered for section type " - f"{section.type} yet." - )) + form._layout.addRow( + QLabel(f"No form registered for section type " f"{section.type} yet.") + ) form._section_id = section.id form._name_edit.setText(getattr(section, "name", "") or "") form._name_edit.setEnabled(False) diff --git a/src/otko/views/dialogs/section_library.py b/src/otko/views/dialogs/section_library.py index 0647580..198c821 100644 --- a/src/otko/views/dialogs/section_library.py +++ b/src/otko/views/dialogs/section_library.py @@ -2,6 +2,7 @@ from __future__ import annotations +from pydantic import ValidationError from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QDialog, @@ -17,7 +18,6 @@ from PySide6.QtWidgets import ( QVBoxLayout, QWidget, ) -from pydantic import ValidationError from otko.commands import ( AddSectionsCommand, @@ -113,6 +113,7 @@ class SectionLibraryDialog(QDialog): """ from otko.core import FiberSection from otko.views.dialogs.section_editor import FiberSectionEditor + sec = self._selected_section() if not isinstance(sec, FiberSection) or self._vm.project is None: return @@ -156,7 +157,12 @@ class SectionLibraryDialog(QDialog): summary_kinds = {"FiberSection", "SectionAggregator"} kinds = [k for k in FORM_REGISTRY if k not in summary_kinds] kind, ok = QInputDialog.getItem( - self, "Add section", "Type:", kinds, current=0, editable=False, + self, + "Add section", + "Type:", + kinds, + current=0, + editable=False, ) if not ok: return @@ -176,6 +182,7 @@ class SectionLibraryDialog(QDialog): if self._vm.project is None: return from otko.views.dialogs.section_editor import FiberSectionEditor + mat_ids = [m.id for m in self._vm.project.materials] new_id = self._vm.project.next_section_id() dlg = FiberSectionEditor(mat_ids, parent=self) @@ -196,9 +203,10 @@ class SectionLibraryDialog(QDialog): if section is None: return reply = QMessageBox.question( - self, "Delete section", + self, + "Delete section", f"Delete section #{section.id} ({section.type})?\n" - "Frame elements that reference it will be invalid until reassigned." + "Frame elements that reference it will be invalid until reassigned.", ) if reply != QMessageBox.StandardButton.Yes: return diff --git a/src/otko/views/dialogs/uniform_excitation.py b/src/otko/views/dialogs/uniform_excitation.py index f5c7cd7..b7cc471 100644 --- a/src/otko/views/dialogs/uniform_excitation.py +++ b/src/otko/views/dialogs/uniform_excitation.py @@ -23,7 +23,6 @@ from PySide6.QtWidgets import ( from otko.core import Project, UniformExcitationPattern - _DIRECTION_CHOICES: list[tuple[int, str]] = [ (1, "1 — X (horizontal)"), (2, "2 — Y (vertical for ndm=2, lateral for ndm=3)"), @@ -51,10 +50,12 @@ class UniformExcitationDialog(QDialog): def _build_ui(self) -> None: root = QVBoxLayout(self) - root.addWidget(QLabel( - "UniformExcitation — apply a base ground motion " - "to every free node in the chosen DOF direction." - )) + root.addWidget( + QLabel( + "UniformExcitation — apply a base ground motion " + "to every free node in the chosen DOF direction." + ) + ) form = QFormLayout() self._name_edit = QLineEdit("GroundMotion") @@ -84,16 +85,13 @@ class UniformExcitationDialog(QDialog): self._factor_spin.setDecimals(6) self._factor_spin.setSingleStep(0.1) self._factor_spin.setValue(1.0) - self._factor_spin.setToolTip( - "Extra scale applied on top of the TimeSeries' own factor." - ) + self._factor_spin.setToolTip("Extra scale applied on top of the TimeSeries' own factor.") form.addRow("Factor:", self._factor_spin) root.addLayout(form) buttons = QDialogButtonBox( - QDialogButtonBox.StandardButton.Ok - | QDialogButtonBox.StandardButton.Cancel, + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, ) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) diff --git a/src/otko/views/dock_manager.py b/src/otko/views/dock_manager.py index b8876ae..df4b70e 100644 --- a/src/otko/views/dock_manager.py +++ b/src/otko/views/dock_manager.py @@ -11,7 +11,7 @@ from __future__ import annotations from contextlib import suppress from PySide6.QtCore import Qt -from PySide6.QtWidgets import QDockWidget, QMessageBox, QPlainTextEdit, QTreeWidget, QTreeWidgetItem +from PySide6.QtWidgets import QDockWidget, QMessageBox, QTreeWidget, QTreeWidgetItem from otko.services.deformation import ( linear_static_auto_scale, @@ -34,6 +34,7 @@ from otko.services.results import ( ) from otko.views.canvas3d.model_renderer import RendererMode from otko.views.docks import ( + ConsoleDock, DeformedShapeView, ForceDiagramView, HysteresisView, @@ -103,13 +104,9 @@ class DockManager: self.tabifyDockWidget(props_dock, tree_dock) tree_dock.raise_() - self._console = QPlainTextEdit() - self._console.setReadOnly(True) - self._console.setPlaceholderText("Logs and OpenSeesPy output will appear here.") - console_dock = QDockWidget("Console", self) - console_dock.setWidget(self._console) - self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, console_dock) - self._console_dock = console_dock + self._console_dock = ConsoleDock(self) + self._console = self._console_dock.view + self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self._console_dock) self._table = TableDock() table_dock = QDockWidget("Table", self) @@ -122,7 +119,7 @@ class DockManager: results_dock.setWidget(self._results_panel) self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, results_dock) self._results_dock = results_dock - self.tabifyDockWidget(console_dock, table_dock) + self.tabifyDockWidget(self._console_dock, table_dock) self.tabifyDockWidget(table_dock, results_dock) table_dock.raise_() diff --git a/src/otko/views/docks/__init__.py b/src/otko/views/docks/__init__.py index 5b292c3..c479a97 100644 --- a/src/otko/views/docks/__init__.py +++ b/src/otko/views/docks/__init__.py @@ -1,5 +1,6 @@ """Reusable dock widget contents.""" +from otko.views.docks.console import ConsoleDock from otko.views.docks.deformed_shape import DeformedShapeView from otko.views.docks.force_diagram import ForceDiagramView from otko.views.docks.hysteresis import HysteresisView @@ -12,8 +13,15 @@ from otko.views.docks.table_dock import TableDock from otko.views.docks.time_history import TimeHistoryView __all__ = [ - "PropertyEditorDock", "ResultsPanel", "TableDock", - "DeformedShapeView", "ModeShapeAnimator", - "ForceDiagramView", "TimeHistoryView", "HysteresisView", - "PushoverCurveView", "ResponseSpectrumView", + "PropertyEditorDock", + "ResultsPanel", + "TableDock", + "DeformedShapeView", + "ModeShapeAnimator", + "ForceDiagramView", + "TimeHistoryView", + "HysteresisView", + "PushoverCurveView", + "ResponseSpectrumView", + "ConsoleDock", ] diff --git a/src/otko/views/docks/console.py b/src/otko/views/docks/console.py new file mode 100644 index 0000000..e00308c --- /dev/null +++ b/src/otko/views/docks/console.py @@ -0,0 +1,108 @@ +# Ported from otko-development (MIT), (c) 2026 OTKO contributors +"""The console dock: the timestamped analysis log.""" + +from __future__ import annotations + +from datetime import datetime + +from PySide6.QtGui import QFont, QFontDatabase +from PySide6.QtWidgets import ( + QApplication, + QDockWidget, + QHBoxLayout, + QPlainTextEdit, + QPushButton, + QVBoxLayout, + QWidget, +) + +__all__ = ["ConsoleDock"] + +#: Recognised severities; anything else is treated as information. +_SEVERITIES = ("info", "warning", "error", "success") + + +class ConsoleDock(QDockWidget): + """Show analysis log lines with a timestamp and severity label. + + The dock never decides anything about the model: it only renders text the + viewmodel or worker emits. Lines are appended, so the log is a faithful + running record of the session. + """ + + def __init__(self, parent: QWidget | None = None) -> None: + """Create the console dock with a read-only, monospaced log view.""" + super().__init__("Console", parent) + self.setObjectName("consoleDock") + + self._view = QPlainTextEdit(self) + self._view.setReadOnly(True) + self._view.setObjectName("consoleView") + self._view.setMaximumBlockCount(5000) + self._view.setPlaceholderText("Logs and OpenSeesPy output will appear here.") + self._view.setFont(self._monospace_font()) + + clear_button = QPushButton("Clear", self) + clear_button.setToolTip("Clear the console log") + clear_button.setStatusTip("Clear the console log") + clear_button.clicked.connect(self.clear) + copy_button = QPushButton("Copy All", self) + copy_button.setToolTip("Copy the whole log to the clipboard") + copy_button.setStatusTip("Copy the whole log to the clipboard") + copy_button.clicked.connect(self._copy_all) + + buttons = QHBoxLayout() + buttons.addStretch(1) + buttons.addWidget(copy_button) + buttons.addWidget(clear_button) + + layout = QVBoxLayout() + layout.setContentsMargins(4, 4, 4, 4) + layout.setSpacing(4) + layout.addWidget(self._view, 1) + layout.addLayout(buttons) + + container = QWidget(self) + container.setLayout(layout) + self.setWidget(container) + + # -- API ----------------------------------------------------------- + + def log(self, message: str, severity: str = "info") -> None: + """Append ``message`` with a timestamp and ``severity`` tag. + + Every line carries a wall-clock time and a severity so a failure is + easy to spot after a run. + """ + tag = severity if severity in _SEVERITIES else "info" + stamp = datetime.now().strftime("%H:%M:%S") + self._view.appendPlainText(f"{stamp} [{tag.upper():<7}] {message}") + + def clear(self) -> None: + """Remove every line from the log.""" + self._view.clear() + + @property + def text(self) -> str: + """Return the full log text (used by tests and copy-all).""" + return self._view.toPlainText() + + @property + def view(self) -> QPlainTextEdit: + """Return the inner text view (for legacy append-only call sites).""" + return self._view + + # -- Internals ----------------------------------------------------- + + def _copy_all(self) -> None: + """Copy the whole log to the clipboard.""" + clipboard = QApplication.clipboard() + if clipboard is not None: + clipboard.setText(self.text) + + @staticmethod + def _monospace_font() -> QFont: + """Return a fixed-width font so timestamps align.""" + font = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont) + font.setPointSize(10) + return font diff --git a/src/otko/views/docks/deformed_shape.py b/src/otko/views/docks/deformed_shape.py index 3745c64..de89664 100644 --- a/src/otko/views/docks/deformed_shape.py +++ b/src/otko/views/docks/deformed_shape.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING from PySide6.QtCore import Qt, Signal from PySide6.QtWidgets import ( @@ -31,8 +31,7 @@ class DeformedShapeView(QWidget): scaleChanged = Signal(float) closed = Signal() - def __init__(self, suggested_scale: float = 1.0, - parent: QWidget | None = None) -> None: + def __init__(self, suggested_scale: float = 1.0, parent: QWidget | None = None) -> None: super().__init__(parent) self._suggested = max(suggested_scale, 1e-6) self._build_ui() @@ -41,9 +40,9 @@ class DeformedShapeView(QWidget): layout = QVBoxLayout(self) layout.setContentsMargins(8, 8, 8, 8) layout.addWidget(QLabel("Deformed Shape")) - layout.addWidget(QLabel( - "The slider scales displacement around the suggested factor." - )) + layout.addWidget( + QLabel("The slider scales displacement around the suggested factor.") + ) form = QFormLayout() @@ -53,8 +52,8 @@ class DeformedShapeView(QWidget): # Multiplier ranging 0.1× — 10× the suggested scale. self._slider = QSlider(Qt.Orientation.Horizontal) - self._slider.setRange(1, 1000) # represents 0.01 — 10.00 - self._slider.setValue(100) # 1.00 × + self._slider.setRange(1, 1000) # represents 0.01 — 10.00 + self._slider.setValue(100) # 1.00 × self._slider.valueChanged.connect(self._on_slider) self._spin = QDoubleSpinBox() diff --git a/src/otko/views/docks/force_diagram.py b/src/otko/views/docks/force_diagram.py index 855f822..a19e99d 100644 --- a/src/otko/views/docks/force_diagram.py +++ b/src/otko/views/docks/force_diagram.py @@ -10,8 +10,6 @@ Exposes two signals: from __future__ import annotations -import math - from PySide6.QtCore import Qt, Signal from PySide6.QtWidgets import ( QComboBox, @@ -31,12 +29,11 @@ from otko.services.element_forces import ForceComponent class ForceDiagramView(QWidget): """Compact controls for live-updating an element-force diagram.""" - componentChanged = Signal(object) # ForceComponent - changed = Signal(object, float) # (ForceComponent, scale) + componentChanged = Signal(object) # ForceComponent + changed = Signal(object, float) # (ForceComponent, scale) closed = Signal() - def __init__(self, suggested_scale: float = 1.0, - parent: QWidget | None = None) -> None: + def __init__(self, suggested_scale: float = 1.0, parent: QWidget | None = None) -> None: super().__init__(parent) self._scale_base = max(suggested_scale, 1e-12) self._pending_component_change = False @@ -87,7 +84,7 @@ class ForceDiagramView(QWidget): self._slider = QSlider(Qt.Orientation.Horizontal) self._slider.setRange(1, 1000) - self._slider.setValue(500) # midpoint = suggested scale + self._slider.setValue(500) # midpoint = suggested scale form.addRow("", self._slider) root.addWidget(group) diff --git a/src/otko/views/docks/hysteresis.py b/src/otko/views/docks/hysteresis.py index cc56c3e..c8f0a79 100644 --- a/src/otko/views/docks/hysteresis.py +++ b/src/otko/views/docks/hysteresis.py @@ -15,6 +15,7 @@ For 2D it's [N1, Vy1, Mz1, N2, Vy2, Mz2]. from __future__ import annotations +import contextlib from typing import Any import pyqtgraph as pg @@ -337,8 +338,6 @@ class HysteresisView(QWidget): def _clear_curve(self) -> None: if self._curve is not None: - try: + with contextlib.suppress(Exception): self._plot.removeItem(self._curve) - except Exception: - pass self._curve = None diff --git a/src/otko/views/docks/property_editor.py b/src/otko/views/docks/property_editor.py index 5d42684..b6c5ed0 100644 --- a/src/otko/views/docks/property_editor.py +++ b/src/otko/views/docks/property_editor.py @@ -8,9 +8,8 @@ dumb: no direct Project mutation, no Qt ↔ OpenSees coupling. from __future__ import annotations -from typing import Callable +from collections.abc import Callable -from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QComboBox, QDoubleSpinBox, @@ -26,14 +25,16 @@ from PySide6.QtWidgets import ( from otko.core import Project - # Element types the Properties dock lets the user switch between. # "ElasticBeamColumn" needs a section; "Truss" / "CorotTruss" need a # uniaxial material + area. The Convert command drops/adds fields to # bridge between them. _CONVERTIBLE_ELEMENT_TYPES = [ - "Truss", "CorotTruss", - "ElasticBeamColumn", "ForceBeamColumn", "DispBeamColumn", + "Truss", + "CorotTruss", + "ElasticBeamColumn", + "ForceBeamColumn", + "DispBeamColumn", ] @@ -53,7 +54,9 @@ class PropertyEditorDock(QScrollArea): self._layout.setContentsMargins(8, 8, 8, 8) self.setWidget(self._inner) self._project: Project | None = None - self.on_apply_mass: Callable[[int, tuple[float, float, float, float, float, float]], None] | None = None + self.on_apply_mass: ( + Callable[[int, tuple[float, float, float, float, float, float]], None] | None + ) = None # Element callbacks — all optional; wired by MainWindow. self.on_change_element_type: Callable[[int, str], None] | None = None self.on_change_element_material: Callable[[int, int], None] | None = None @@ -121,9 +124,12 @@ class PropertyEditorDock(QScrollArea): self._layout.addWidget(QLabel(f"

Node #{node.id}

")) form = QFormLayout() form.addRow("Name:", QLabel(node.name or "—")) - form.addRow("X, Y, Z:", QLabel( - f"{node.coords[0]:.4f}, {node.coords[1]:.4f}, {node.coords[2]:.4f}", - )) + form.addRow( + "X, Y, Z:", + QLabel( + f"{node.coords[0]:.4f}, {node.coords[1]:.4f}, {node.coords[2]:.4f}", + ), + ) form.addRow("Restraint:", QLabel(self._fmt_restraint(node.restraint))) self._layout.addLayout(form) @@ -179,6 +185,7 @@ class PropertyEditorDock(QScrollArea): def _on_type_changed(new_type: str, _eid: int = el.id) -> None: if new_type != el.type and self.on_change_element_type is not None: self.on_change_element_type(_eid, new_type) + type_cb.currentTextChanged.connect(_on_type_changed) form.addRow("Type:", type_cb) else: @@ -188,16 +195,19 @@ class PropertyEditorDock(QScrollArea): form.addRow("Nodes:", QLabel(", ".join(str(n) for n in el.nodes))) # ── Section picker for frame elements. ── - if (hasattr(el, "section_id") - and self._project is not None - and self._project.sections - and self.on_change_element_section is not None): + if ( + hasattr(el, "section_id") + and self._project is not None + and self._project.sections + and self.on_change_element_section is not None + ): sec_cb = QComboBox() for s in self._project.sections: sec_cb.addItem( - f"#{s.id} {s.name or s.type}", s.id, + f"#{s.id} {s.name or s.type}", + s.id, ) - idx = sec_cb.findData(el.section_id) # type: ignore[attr-defined] + idx = sec_cb.findData(el.section_id) # type: ignore[attr-defined] if idx >= 0: sec_cb.setCurrentIndex(idx) @@ -205,20 +215,23 @@ class PropertyEditorDock(QScrollArea): sid = sec_cb.currentData() if sid is not None and self.on_change_element_section is not None: self.on_change_element_section(_eid, int(sid)) + sec_cb.currentIndexChanged.connect(_on_section_changed) form.addRow("Section:", sec_cb) elif hasattr(el, "section_id"): form.addRow("Section id:", QLabel(str(el.section_id))) # ── Material picker for truss / uniaxial-material elements. ── - if (hasattr(el, "material_id") - and self._project is not None - and self._project.materials - and self.on_change_element_material is not None): + if ( + hasattr(el, "material_id") + and self._project is not None + and self._project.materials + and self.on_change_element_material is not None + ): mat_cb = QComboBox() for m in self._project.materials: mat_cb.addItem(f"#{m.id} {m.name or m.type}", m.id) - idx = mat_cb.findData(el.material_id) # type: ignore[attr-defined] + idx = mat_cb.findData(el.material_id) # type: ignore[attr-defined] if idx >= 0: mat_cb.setCurrentIndex(idx) @@ -226,6 +239,7 @@ class PropertyEditorDock(QScrollArea): mid = mat_cb.currentData() if mid is not None and self.on_change_element_material is not None: self.on_change_element_material(_eid, int(mid)) + mat_cb.currentIndexChanged.connect(_on_material_changed) form.addRow("Material:", mat_cb) elif hasattr(el, "material_id"): @@ -236,9 +250,12 @@ class PropertyEditorDock(QScrollArea): area_spin = QDoubleSpinBox() area_spin.setRange(1e-12, 1e6) area_spin.setDecimals(8) - area_spin.setValue(float(el.area)) # type: ignore[attr-defined] - area_spin.setSingleStep(float(el.area) * 0.1 # type: ignore[attr-defined] - if el.area else 0.001) # type: ignore[attr-defined] + area_spin.setValue(float(el.area)) # type: ignore[attr-defined] + area_spin.setSingleStep( + float(el.area) * 0.1 # type: ignore[attr-defined] + if el.area + else 0.001 + ) # type: ignore[attr-defined] # Commit on editingFinished so we don't dispatch a command on # every keystroke (which would spam the undo stack). @@ -255,6 +272,7 @@ class PropertyEditorDock(QScrollArea): return if self.on_change_element_fields is not None: self.on_change_element_fields(_eid, {"area": new_val}) + area_spin.editingFinished.connect(_on_area_edited) form.addRow("Area:", area_spin) elif hasattr(el, "area"): @@ -268,13 +286,17 @@ class PropertyEditorDock(QScrollArea): def _show_multi(self, node_ids: frozenset[int], element_ids: frozenset[int]) -> None: self._layout.addWidget(QLabel("

Multi-selection

")) if node_ids: - self._layout.addWidget(QLabel( - f"{len(node_ids)} node(s) selected: {self._fmt_id_list(node_ids)}", - )) + self._layout.addWidget( + QLabel( + f"{len(node_ids)} node(s) selected: {self._fmt_id_list(node_ids)}", + ) + ) if element_ids: - self._layout.addWidget(QLabel( - f"{len(element_ids)} element(s) selected: {self._fmt_id_list(element_ids)}", - )) + self._layout.addWidget( + QLabel( + f"{len(element_ids)} element(s) selected: {self._fmt_id_list(element_ids)}", + ) + ) self._layout.addStretch(1) @staticmethod @@ -288,4 +310,6 @@ class PropertyEditorDock(QScrollArea): sorted_ids = sorted(ids) if len(sorted_ids) <= limit: return ", ".join(str(i) for i in sorted_ids) - return ", ".join(str(i) for i in sorted_ids[:limit]) + f", … (+{len(sorted_ids) - limit} more)" + return ( + ", ".join(str(i) for i in sorted_ids[:limit]) + f", … (+{len(sorted_ids) - limit} more)" + ) diff --git a/src/otko/views/docks/pushover_curve.py b/src/otko/views/docks/pushover_curve.py index 3dc0e11..86beff2 100644 --- a/src/otko/views/docks/pushover_curve.py +++ b/src/otko/views/docks/pushover_curve.py @@ -26,7 +26,7 @@ from PySide6.QtWidgets import ( QWidget, ) -from otko.core import UnitLabels, UnitSystem, labels_for +from otko.core import UnitSystem, labels_for from otko.services.results import PushoverResults if TYPE_CHECKING: diff --git a/src/otko/views/docks/response_spectrum.py b/src/otko/views/docks/response_spectrum.py index 56d8d6e..7664171 100644 --- a/src/otko/views/docks/response_spectrum.py +++ b/src/otko/views/docks/response_spectrum.py @@ -8,7 +8,7 @@ Two panels: from __future__ import annotations import pyqtgraph as pg -from PySide6.QtCore import Signal +from PySide6.QtCore import Qt, Signal from PySide6.QtWidgets import ( QHBoxLayout, QHeaderView, @@ -20,7 +20,6 @@ from PySide6.QtWidgets import ( QVBoxLayout, QWidget, ) -from PySide6.QtCore import Qt from otko.core import ResponseSpectrum from otko.services.results import ResponseSpectrumResults @@ -36,7 +35,8 @@ class ResponseSpectrumView(QWidget): self._build_ui() def set_results( - self, results: ResponseSpectrumResults | None, + self, + results: ResponseSpectrumResults | None, spectrum: ResponseSpectrum | None, ) -> None: self._plot.clear() @@ -59,9 +59,13 @@ class ResponseSpectrumView(QWidget): self._plot.plot(p_dense, a_dense, pen=pen, name="Sa(T)") # Original control points. self._plot.plot( - list(spectrum.periods), list(spectrum.accelerations), - pen=None, symbol="s", symbolSize=7, - symbolBrush="#1f77b4", symbolPen=None, + list(spectrum.periods), + list(spectrum.accelerations), + pen=None, + symbol="s", + symbolSize=7, + symbolBrush="#1f77b4", + symbolPen=None, name="Control pts", ) @@ -77,14 +81,19 @@ class ResponseSpectrumView(QWidget): # Slight vertical jitter for duplicate periods y_offset = count * m.sa_at_period * 0.04 self._plot.plot( - [m.period], [m.sa_at_period + y_offset], - pen=None, symbol="o", symbolSize=12, - symbolBrush="#d62728", symbolPen=pg.mkPen("#ffffff", width=1), + [m.period], + [m.sa_at_period + y_offset], + pen=None, + symbol="o", + symbolSize=12, + symbolBrush="#d62728", + symbolPen=pg.mkPen("#ffffff", width=1), name=f"Mode {m.mode_number}" if count == 0 else None, ) # Small text label right next to the marker. txt = pg.TextItem( - f" M{m.mode_number}", color="#d62728", + f" M{m.mode_number}", + color="#d62728", anchor=(0.0, 0.5), ) txt.setPos(m.period, m.sa_at_period + y_offset) @@ -133,9 +142,17 @@ class ResponseSpectrumView(QWidget): splitter.addWidget(self._plot) self._table = QTableWidget(0, 7) - self._table.setHorizontalHeaderLabels([ - "Mode", "T (s)", "f (Hz)", "Γ", "M_eff", "Mass %", "Sa(T)", - ]) + self._table.setHorizontalHeaderLabels( + [ + "Mode", + "T (s)", + "f (Hz)", + "Γ", + "M_eff", + "Mass %", + "Sa(T)", + ] + ) self._table.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeMode.Stretch, ) diff --git a/src/otko/views/docks/results_panel.py b/src/otko/views/docks/results_panel.py index 65d0866..9384e9a 100644 --- a/src/otko/views/docks/results_panel.py +++ b/src/otko/views/docks/results_panel.py @@ -20,6 +20,7 @@ from PySide6.QtWidgets import ( QWidget, ) +from otko.services.combinations import EnvelopeResults from otko.services.results import ( ModalResults, StaticResults, @@ -59,7 +60,18 @@ class ResultsPanel(QWidget): """ self._ndm, self._ndf = ndm, ndf self._tabs.clear() - if isinstance(results, StaticResults): + if isinstance(results, EnvelopeResults): + self._title.setText( + f"Combination (Envelope) — #{results.case_id} " + f"'{results.case_name}' ({results.n_steps} step(s))" + ) + self._tabs.addTab( + self._build_envelope_table(results, "disp"), "Displacements (max/min)" + ) + self._tabs.addTab( + self._build_envelope_table(results, "reaction"), "Reactions (max/min)" + ) + elif isinstance(results, StaticResults): self._title.setText( f"Static — case #{results.case_id} '{results.case_name}' " f"({results.n_steps} step(s))" @@ -67,9 +79,7 @@ class ResultsPanel(QWidget): self._tabs.addTab(self._build_static_disp_table(results), "Displacements") self._tabs.addTab(self._build_static_reaction_table(results), "Reactions") elif isinstance(results, ModalResults): - self._title.setText( - f"Modal — case #{results.case_id} '{results.case_name}'" - ) + self._title.setText(f"Modal — case #{results.case_id} '{results.case_name}'") self._tabs.addTab(self._build_modal_table(results), "Frequencies") elif isinstance(results, TransientResults): self._title.setText( @@ -109,6 +119,28 @@ class ResultsPanel(QWidget): self._set_cell(table, i, j + 1, f"{val:.6g}") return self._wrap(table, "Final-step reactions") + def _build_envelope_table(self, r: EnvelopeResults, kind: str) -> QWidget: + """Max/min table for envelope ``kind`` (``"disp"`` or ``"reaction"``).""" + max_map = r.node_disp_max if kind == "disp" else r.node_reaction_max + min_map = r.node_disp_min if kind == "disp" else r.node_reaction_min + rows = sorted(max_map.keys()) + if not rows: + return self._empty_table_widget() + ndf = max_map[rows[0]].shape[1] + dofs = dof_labels(ndf, self._ndm, self._ndf, kind="disp" if kind == "disp" else "force") + headers = ["Node", *[f"{d} max" for d in dofs], *[f"{d} min" for d in dofs]] + table = self._make_table(headers, len(rows)) + for i, nid in enumerate(rows): + self._set_cell(table, i, 0, str(nid)) + for j, val in enumerate(max_map[nid][-1]): + self._set_cell(table, i, j + 1, f"{val:.6g}") + for j, val in enumerate(min_map[nid][-1]): + self._set_cell(table, i, j + 1 + ndf, f"{val:.6g}") + caption = ( + "Final-step displacement envelope" if kind == "disp" else "Final-step reaction envelope" + ) + return self._wrap(table, caption) + def _build_modal_table(self, r: ModalResults) -> QWidget: n = len(r.eigenvalues) headers = ["Mode", "Eigenvalue (rad²/s²)", "ω (rad/s)", "f (Hz)", "T (s)"] @@ -124,15 +156,17 @@ class ResultsPanel(QWidget): def _transient_summary(self, r: TransientResults) -> QWidget: w = QWidget() layout = QVBoxLayout(w) - layout.addWidget(QLabel( - f"Steps: {r.n_steps}
" - f"dt: {r.dt:g}
" - f"Total time: {r.n_steps * r.dt:g}
" - f"HDF5 file: {r.h5_path}" - )) - layout.addWidget(QLabel( - "Time-history plots and animation will appear here in Phase 7." - )) + layout.addWidget( + QLabel( + f"Steps: {r.n_steps}
" + f"dt: {r.dt:g}
" + f"Total time: {r.n_steps * r.dt:g}
" + f"HDF5 file: {r.h5_path}" + ) + ) + layout.addWidget( + QLabel("Time-history plots and animation will appear here in Phase 7.") + ) layout.addStretch(1) return w diff --git a/src/otko/views/docks/table_dock.py b/src/otko/views/docks/table_dock.py index 23a2592..3af8383 100644 --- a/src/otko/views/docks/table_dock.py +++ b/src/otko/views/docks/table_dock.py @@ -35,6 +35,7 @@ from PySide6.QtWidgets import ( from otko.commands import ( AddAnalysisCasesCommand, + AddCombinationsCommand, AddElementLoadsCommand, AddElementsCommand, AddEqualDOFConstraintCommand, @@ -47,6 +48,7 @@ from otko.commands import ( AssignMaterialCommand, AssignSectionCommand, DeleteAnalysisCasesCommand, + DeleteCombinationsCommand, DeleteElementLoadsCommand, DeleteElementsCommand, DeleteEqualDOFConstraintCommand, @@ -59,6 +61,7 @@ from otko.commands import ( SetMassCommand, SetRestraintCommand, UpdateAnalysisCaseCommand, + UpdateCombinationCommand, UpdateElementFieldsCommand, UpdateElementLoadCommand, UpdateEqualDOFConstraintCommand, @@ -75,6 +78,8 @@ from otko.core import ( ElasticUniaxial, EqualDOFConstraint, LinearTimeSeries, + LoadCombination, + LoadCombinationItem, NodalLoad, Node, PlainLoadPattern, @@ -169,6 +174,7 @@ _ANALYSIS_ATTRS = [ "tolerance", "max_iter", ] +_COMBO_HEADERS = ["id", "name", "kind", "cases", "factors"] _TAB_ORDER = ( "Nodes", @@ -181,6 +187,7 @@ _TAB_ORDER = ( "Element Loads", "Constraints", "Analyses", + "Combinations", ) @@ -190,7 +197,7 @@ def _fmt(value: Any) -> str: return "—" if isinstance(value, float): return f"{value:g}" - if isinstance(value, (list, tuple)): + if isinstance(value, list | tuple): return ", ".join(_fmt(v) for v in value) return str(value) @@ -270,6 +277,7 @@ class TableDock(QWidget): "Element Loads": self._load_element_loads, "Constraints": self._load_constraints, "Analyses": self._load_analyses, + "Combinations": self._load_combinations, } for name in _TAB_ORDER: page = QWidget() @@ -417,6 +425,7 @@ class TableDock(QWidget): "Element Loads": self._commit_element_load, "Constraints": self._commit_constraint, "Analyses": self._commit_analysis, + "Combinations": self._commit_combination, }[tab] handler(self._tables[tab], item) except (ValueError, KeyError, IndexError) as exc: @@ -1026,7 +1035,7 @@ class TableDock(QWidget): try: key = int(pid) label = name_of.get(key, str(key)) or str(key) - parts.append(f"{label}×{float(factor):g}") # noqa: RUF001 + parts.append(f"{label}×{float(factor):g}") except (TypeError, ValueError): continue return ", ".join(parts) if parts else "—" @@ -1095,6 +1104,49 @@ class TableDock(QWidget): updated = self._dry_run(case, {attr: value}) self._dispatch(UpdateAnalysisCaseCommand(vm, updated)) + # ── Combinations ─────────────────────────────────────────────── + def _load_combinations(self, table: QTableWidget) -> None: + combos = list(self._project.combinations) if self._project is not None else [] + table.setColumnCount(len(_COMBO_HEADERS)) + table.setHorizontalHeaderLabels(_COMBO_HEADERS) + table.setRowCount(len(combos)) + for r, combo in enumerate(combos): + self._put(table, r, 0, str(combo.id), editable=False, data=combo.id) + self._put(table, r, 1, combo.name, data=combo.id) + self._put(table, r, 2, combo.kind, data=combo.id) + self._put( + table, + r, + 3, + ", ".join(str(i.case_id) for i in combo.items), + editable=False, + data=combo.id, + ) + self._put( + table, + r, + 4, + ", ".join(f"{i.factor:g}" for i in combo.items), + editable=False, + data=combo.id, + ) + + def _commit_combination(self, table: QTableWidget, item: QTableWidgetItem) -> None: + vm, project = self._bound() + row, col = item.row(), item.column() + cid = self._row_key(table, row) + combo = next(c for c in project.combinations if c.id == cid) + if col == 1: + updated = self._dry_run(combo, {"name": item.text()}) + elif col == 2: + raw = item.text().strip() + if raw not in ("Linear", "Envelope"): + raise ValueError("kind: expected 'Linear' or 'Envelope'.") + updated = self._dry_run(combo, {"kind": raw}) + else: + return + self._dispatch(UpdateCombinationCommand(vm, updated)) + # ── Add / Delete rows ────────────────────────────────────────── def _require_project(self, tab: str) -> Any | None: if self._vm is None or self._project is None: @@ -1192,6 +1244,16 @@ class TableDock(QWidget): pattern_ids=[project.load_patterns[0].id], ) self._dispatch(AddAnalysisCasesCommand(vm, [case])) + elif tab == "Combinations": + if not project.analyses: + self._say("Combinations: define an analysis case first.") + return + combo = LoadCombination( + id=project.next_combination_id(), + name="New", + items=[LoadCombinationItem(case_id=project.analyses[0].id)], + ) + self._dispatch(AddCombinationsCommand(vm, [combo])) except Exception as exc: label = tab[:-1] if tab.endswith("s") else tab self._say(f"Add {label} failed: {exc}") @@ -1264,6 +1326,8 @@ class TableDock(QWidget): self._dispatch(DeleteLoadPatternCommand(vm, ids)) elif tab == "Analyses": self._dispatch(DeleteAnalysisCasesCommand(vm, ids)) + elif tab == "Combinations": + self._dispatch(DeleteCombinationsCommand(vm, ids)) except Exception as exc: self._say(f"Delete {tab} failed: {exc}") self.refresh() diff --git a/src/otko/views/docks/time_history.py b/src/otko/views/docks/time_history.py index ba285fe..560dba0 100644 --- a/src/otko/views/docks/time_history.py +++ b/src/otko/views/docks/time_history.py @@ -10,9 +10,9 @@ runner to record additional series (planned for a later phase). from __future__ import annotations +import contextlib from typing import Any -import numpy as np import pyqtgraph as pg from PySide6.QtCore import Signal from PySide6.QtWidgets import ( @@ -208,9 +208,7 @@ class TimeHistoryView(QWidget): def _clear_traces(self) -> None: for _, _, item in self._traces: - try: + with contextlib.suppress(Exception): self._plot.removeItem(item) - except Exception: - pass self._traces.clear() self._trace_list.clear() diff --git a/src/otko/views/formatting.py b/src/otko/views/formatting.py new file mode 100644 index 0000000..1123a08 --- /dev/null +++ b/src/otko/views/formatting.py @@ -0,0 +1,97 @@ +# Ported from otko-development (MIT), (c) 2026 OTKO contributors +"""Small presentation helpers shared by docks and dialogs. + +Keeps labels, unit hints and numeric parsing in one place so every surface +names a field the same way and rejects bad input with the same message shape. +Pure helpers only — no Qt imports live here. +""" + +from __future__ import annotations + +from otko.core import UnitLabels, UnitSystem, labels_for + +__all__ = [ + "DOF_LABELS", + "MASS_LABELS", + "RESTRAINT_LABELS", + "format_number", + "parse_float", + "parse_int", + "unit_label", +] + +#: Six translational/rotational DOF labels, indexed 0-5. +DOF_LABELS: tuple[str, ...] = ("Ux", "Uy", "Uz", "Rx", "Ry", "Rz") + +#: Six mass-component labels, indexed 0-5. +MASS_LABELS: tuple[str, ...] = ("Mx", "My", "Mz", "Ixx", "Iyy", "Izz") + +#: Human-readable restraint component names (restraint + rotation). +RESTRAINT_LABELS: tuple[str, ...] = ( + "Ux", + "Uy", + "Uz", + "Rx", + "Ry", + "Rz", +) + +#: Map a public quantity name to the :class:`UnitLabels` field that holds it. +_QUANTITY_FIELDS: dict[str, str] = { + "length": "length", + "force": "force", + "moment": "moment", + "stress": "stress", + "curvature": "curvature", + "rotation": "rotation", +} + + +def format_number(value: float) -> str: + """Return a compact, round-trippable string for ``value``. + + Integral floats lose the decimal point; everything else keeps up to six + significant digits so a round-trip through a table cell is exact enough + for engineering values. + """ + if value == int(value): + return str(int(value)) + return f"{value:.6g}" + + +def parse_float(text: str, field: str, *, allow_empty: bool = False) -> float: + """Parse ``text`` as a float or raise ``ValueError`` naming ``field``. + + An empty string is accepted when ``allow_empty`` is set and yields ``0.0`` + so a blank optional cell is not treated as an error. + """ + stripped = text.strip() + if not stripped: + if allow_empty: + return 0.0 + raise ValueError(f"{field} is required") + try: + return float(stripped) + except ValueError: + raise ValueError(f"{field} must be a number, got {stripped!r}") from None + + +def parse_int(text: str, field: str, *, allow_empty: bool = False) -> int: + """Parse ``text`` as an int or raise ``ValueError`` naming ``field``.""" + value = parse_float(text, field, allow_empty=allow_empty) + if value != int(value): + raise ValueError(f"{field} must be a whole number, got {text.strip()!r}") + return int(value) + + +def unit_label(quantity: str, system: UnitSystem) -> str: + """Return the display label for ``quantity`` in ``system``. + + ``quantity`` is one of ``length``, ``force``, ``moment``, ``stress``, + ``curvature`` or ``rotation``; anything else raises ``ValueError``. + """ + field = _QUANTITY_FIELDS.get(quantity) + if field is None: + raise ValueError(f"unknown unit quantity: {quantity!r}") + labels: UnitLabels = labels_for(system) + return getattr(labels, field) diff --git a/src/otko/views/main_window.py b/src/otko/views/main_window.py index 5577d8b..958459e 100644 --- a/src/otko/views/main_window.py +++ b/src/otko/views/main_window.py @@ -15,8 +15,9 @@ from __future__ import annotations from pathlib import Path -from PySide6.QtGui import QIcon -from PySide6.QtWidgets import QMainWindow +from PySide6.QtCore import QSettings +from PySide6.QtGui import QCloseEvent, QIcon +from PySide6.QtWidgets import QMainWindow, QMessageBox from otko.viewmodels import AnalysisRunner, ProjectViewModel from otko.views.action_handlers import ActionHandlers @@ -74,6 +75,7 @@ class MainWindow( self._build_status_bar() self._wire() self._refresh_action_enablement() + self.restore_layout() # ── construction ───────────────────────────────────────────────── def _build_central_canvas(self) -> None: @@ -82,6 +84,54 @@ class MainWindow( # Diagram overlay paints onto the same plotter as the model. self._diagram_renderer = DiagramRenderer(self._canvas) + # ── layout persistence ─────────────────────────────────────────── + def save_layout(self) -> None: + """Persist window geometry and dock/toolbar state for next launch.""" + settings = QSettings("OTKO", "OTKO") + settings.setValue("geometry", self.saveGeometry()) + settings.setValue("windowState", self.saveState()) + + def restore_layout(self) -> None: + """Restore a previously saved geometry/dock layout, if any. + + On first run no values are stored, so the defaults from ``__init__`` + (``resize`` + dock construction) stay in effect. + """ + settings = QSettings("OTKO", "OTKO") + geometry = settings.value("geometry") + if geometry is not None: + self.restoreGeometry(geometry) + state = settings.value("windowState") + if state is not None: + self.restoreState(state) + + def closeEvent(self, event: QCloseEvent) -> None: # (Qt override) + """Prompt to save unsaved work, then persist the window layout. + + The prompt is shown only for a visible window: Qt also closes hidden + widgets during teardown (for example under ``pytest-qt``), where a + modal dialog would block with no user to answer it. + """ + if self._vm.is_dirty and self.isVisible(): + buttons = QMessageBox.StandardButton + choice = QMessageBox.warning( + self, + "Unsaved changes", + "Save changes to the current project before closing?", + buttons.Save | buttons.Discard | buttons.Cancel, + buttons.Save, + ) + if choice == buttons.Cancel: + event.ignore() + return + if choice == buttons.Save: + self._on_save() + if self._vm.is_dirty: + event.ignore() + return + self.save_layout() + event.accept() + # ── wiring ──────────────────────────────────────────────────────── def _wire(self) -> None: # File @@ -141,6 +191,7 @@ class MainWindow( # Analyze self._act_case_manager.triggered.connect(self._on_case_manager) + self._act_combo_manager.triggered.connect(self._on_combo_manager) self._act_run.triggered.connect(self._on_run_analysis) # Display @@ -156,7 +207,7 @@ class MainWindow( self._act_back_to_model.triggered.connect(self._on_back_to_model) # AnalysisRunner: stream log to console + show results in panel - self._runner.log.connect(self._console.appendPlainText) + self._runner.log.connect(self._console_dock.log) self._runner.finished.connect(self._on_analysis_finished) self._runner.failed.connect(self._on_analysis_failed) diff --git a/src/otko/views/menu_builder.py b/src/otko/views/menu_builder.py index 30450f9..bd8af6a 100644 --- a/src/otko/views/menu_builder.py +++ b/src/otko/views/menu_builder.py @@ -155,6 +155,7 @@ class MenuBuilder: # Analyze self._act_case_manager = QAction("&Cases…", self, shortcut="Ctrl+Shift+A") + self._act_combo_manager = QAction("&Load Combinations…", self) self._act_run = QAction("&Run…", self, shortcut="F5") # View (Iso/Top/Front/Right are an exclusive checkable group so the @@ -285,6 +286,11 @@ class MenuBuilder: "Analysis Cases (Ctrl+Shift+A)", "Manage analysis cases.", ), + "_act_combo_manager": ( + "cases", + "Load Combinations", + "Manage named case-result load combinations.", + ), "_act_show_deformed": ( "show-deformed", "Show Deformed Shape", @@ -420,6 +426,7 @@ class MenuBuilder: m_analyze = mb.addMenu("&Analyze") m_analyze.addAction(self._act_case_manager) + m_analyze.addAction(self._act_combo_manager) m_analyze.addSeparator() m_analyze.addAction(self._act_run) @@ -482,7 +489,7 @@ class MenuBuilder: tb.setMovable(True) tb.setIconSize(QSize(24, 24)) tb.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) - # Top edge; Tools stays in the left area independently (built next). + # Top edge; Tools shares the top area (built next). self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb) self._view_toolbar = tb tb.addAction(self._act_zoom_extents) @@ -528,9 +535,8 @@ class MenuBuilder: tb.setMovable(True) tb.setIconSize(QSize(24, 24)) tb.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) - # View lives on the top edge, so Tools stays left independently - # in its own left-area column. - self.addToolBar(Qt.ToolBarArea.LeftToolBarArea, tb) + # Top edge alongside the View toolbar (built just before). + self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb) self._tools_toolbar = tb tb.addAction(self._act_tool_select) tb.addAction(self._act_tool_draw_node) diff --git a/src/otko/views/render_controls.py b/src/otko/views/render_controls.py index fecf2d7..1d574c1 100644 --- a/src/otko/views/render_controls.py +++ b/src/otko/views/render_controls.py @@ -496,5 +496,5 @@ class RenderControls: self._act_clear_display.setEnabled(has_project) def _log(self, message: str) -> None: - self._console.appendPlainText(message) + self._console_dock.log(message) self.statusBar().showMessage(message, 5000) diff --git a/src/otko/views/tools/__init__.py b/src/otko/views/tools/__init__.py index 992e462..8c593d9 100644 --- a/src/otko/views/tools/__init__.py +++ b/src/otko/views/tools/__init__.py @@ -5,5 +5,11 @@ from otko.views.tools.draw_frame import DrawFrameTool from otko.views.tools.draw_node import DrawNodeTool from otko.views.tools.draw_truss import DrawTrussTool -__all__ = ["CanvasTool", "SelectTool", "ToolController", - "DrawFrameTool", "DrawNodeTool", "DrawTrussTool"] +__all__ = [ + "CanvasTool", + "SelectTool", + "ToolController", + "DrawFrameTool", + "DrawNodeTool", + "DrawTrussTool", +] diff --git a/src/otko/views/tools/base.py b/src/otko/views/tools/base.py index 2ffe593..bfb6072 100644 --- a/src/otko/views/tools/base.py +++ b/src/otko/views/tools/base.py @@ -35,8 +35,8 @@ class CanvasTool(QObject): def __init__( self, - canvas: "ModelCanvas", - vm: "ProjectViewModel", + canvas: ModelCanvas, + vm: ProjectViewModel, parent: QObject | None = None, ) -> None: super().__init__(parent) @@ -84,10 +84,11 @@ class SelectTool(CanvasTool): class ToolController(QObject): """Owns the active tool and routes canvas pick signals to it.""" - toolChanged = Signal(object) # emits the new CanvasTool (or None for default) + toolChanged = Signal(object) # emits the new CanvasTool (or None for default) - def __init__(self, canvas: "ModelCanvas", vm: "ProjectViewModel", - parent: QObject | None = None) -> None: + def __init__( + self, canvas: ModelCanvas, vm: ProjectViewModel, parent: QObject | None = None + ) -> None: super().__init__(parent) self._canvas = canvas self._vm = vm diff --git a/src/otko/views/tools/draw_frame.py b/src/otko/views/tools/draw_frame.py index d0bd47a..980716a 100644 --- a/src/otko/views/tools/draw_frame.py +++ b/src/otko/views/tools/draw_frame.py @@ -41,9 +41,9 @@ class DrawFrameTool(LinearElementDrawTool): def __init__( self, - canvas: "ModelCanvas", - vm: "ProjectViewModel", - parent: "QObject | None" = None, + canvas: ModelCanvas, + vm: ProjectViewModel, + parent: QObject | None = None, ) -> None: super().__init__(canvas, vm, parent) @@ -67,7 +67,9 @@ class DrawFrameTool(LinearElementDrawTool): section_id = self._ensure_default_section() element_id = project.next_element_id() elem = ElasticBeamColumn( - id=element_id, nodes=(n1, n2), section_id=section_id, + id=element_id, + nodes=(n1, n2), + section_id=section_id, ) self._vm.apply_command(AddElementsCommand(self._vm, [elem])) finally: diff --git a/src/otko/views/tools/draw_node.py b/src/otko/views/tools/draw_node.py index b82660a..0f286be 100644 --- a/src/otko/views/tools/draw_node.py +++ b/src/otko/views/tools/draw_node.py @@ -27,14 +27,20 @@ if TYPE_CHECKING: def _snap_to_grid( - x: float, y: float, z: float, - x_lines: list[float], y_lines: list[float], z_lines: list[float], + x: float, + y: float, + z: float, + x_lines: list[float], + y_lines: list[float], + z_lines: list[float], ) -> tuple[float, float, float]: """Snap each coordinate to its closest grid line (identity on empty axes).""" + def nearest(v: float, lines: list[float]) -> float: if not lines: return v return min(lines, key=lambda c: abs(c - v)) + return nearest(x, x_lines), nearest(y, y_lines), nearest(z, z_lines) @@ -70,16 +76,17 @@ def _snap_with_distance( if not (grid.x_lines or grid.y_lines or grid.z_lines): continue lx, ly, lz = cs.coord.world_to_local(world) - sl = _snap_to_grid(lx, ly, lz, - grid.x_lines, grid.y_lines, grid.z_lines) + sl = _snap_to_grid(lx, ly, lz, grid.x_lines, grid.y_lines, grid.z_lines) candidate = cs.coord.local_to_world(sl) - d2 = ((candidate[0] - world[0]) ** 2 - + (candidate[1] - world[1]) ** 2 - + (candidate[2] - world[2]) ** 2) + d2 = ( + (candidate[0] - world[0]) ** 2 + + (candidate[1] - world[1]) ** 2 + + (candidate[2] - world[2]) ** 2 + ) if d2 < best_d2: best = candidate best_d2 = d2 - return best, (best_d2 ** 0.5) if best is not None else float("inf") + return best, (best_d2**0.5) if best is not None else float("inf") def _min_grid_spacing(systems: list[CoordinateGridSystem]) -> float | None: @@ -108,9 +115,9 @@ class DrawNodeTool(CanvasTool): def __init__( self, - canvas: "ModelCanvas", - vm: "ProjectViewModel", - parent: "QObject | None" = None, + canvas: ModelCanvas, + vm: ProjectViewModel, + parent: QObject | None = None, ) -> None: super().__init__(canvas, vm, parent) @@ -124,13 +131,13 @@ class DrawNodeTool(CanvasTool): super().deactivate() def prompt(self) -> str: - return ("Draw Node: click a grid intersection " - "to place a node. Switch to Select tool to finish.") + return ( + "Draw Node: click a grid intersection " + "to place a node. Switch to Select tool to finish." + ) def on_node_picked(self, node_id: int) -> None: - self.statusChanged.emit( - f"Draw Node: node {node_id} already exists at that spot." - ) + self.statusChanged.emit(f"Draw Node: node {node_id} already exists at that spot.") def on_empty_clicked(self, x: float, y: float, z: float) -> None: """Called only when the canvas has already confirmed a grid snap. @@ -153,9 +160,5 @@ class DrawNodeTool(CanvasTool): nid = project.next_node_id() node = Node(id=nid, coords=(x, y, z)) - self._vm.apply_command( - AddNodesCommand(self._vm, [node], text=f"Add node {nid}") - ) - self.statusChanged.emit( - f"Draw Node: added node {nid} at ({x:g}, {y:g}, {z:g})." - ) + self._vm.apply_command(AddNodesCommand(self._vm, [node], text=f"Add node {nid}")) + self.statusChanged.emit(f"Draw Node: added node {nid} at ({x:g}, {y:g}, {z:g}).") diff --git a/src/otko/views/tools/draw_truss.py b/src/otko/views/tools/draw_truss.py index 1eeb69d..4ee8254 100644 --- a/src/otko/views/tools/draw_truss.py +++ b/src/otko/views/tools/draw_truss.py @@ -37,9 +37,9 @@ class DrawTrussTool(LinearElementDrawTool): def __init__( self, - canvas: "ModelCanvas", - vm: "ProjectViewModel", - parent: "QObject | None" = None, + canvas: ModelCanvas, + vm: ProjectViewModel, + parent: QObject | None = None, ) -> None: super().__init__(canvas, vm, parent) diff --git a/tests/conftest.py b/tests/conftest.py index 930c400..82dd86a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,22 +1,30 @@ -"""Shared pytest fixtures. +"""Shared pytest configuration. -`pytest-qt` automatically provides a `qtbot` fixture and a QApplication -instance. We add convenience fixtures here as the suite grows. +The GUI suite is headless by default: unless the caller already chose a +platform, ``QT_QPA_PLATFORM=offscreen`` is set here, before pytest-qt +builds its session ``QApplication``. An explicit caller value is respected. + +OpenseesPy is a process-global singleton, so a lazy autouse guard wipes the +domain after every test. The import is inside the teardown so that +``tests/unit`` never imports the solver. """ from __future__ import annotations +import os +from collections.abc import Iterator + import pytest +os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") + @pytest.fixture(autouse=True) -def _isolate_opensees() -> None: - """Reset OpenSees domain between tests if openseespy is importable. - - Imported lazily so that pure-core tests don't pull in the C++ runtime. - """ +def _wipe_opensees_domain() -> Iterator[None]: + """Wipe the OpenseesPy domain after a test if the solver was imported.""" + yield try: - import openseespy.opensees as ops - except ImportError: + from openseespy.opensees import wipe + except Exception: # pragma: no cover - solver not installed / import side effects return - ops.wipe() + wipe() diff --git a/tests/gui/test_analysis_commands.py b/tests/gui/test_analysis_commands.py index ca5ecf8..fed67e3 100644 --- a/tests/gui/test_analysis_commands.py +++ b/tests/gui/test_analysis_commands.py @@ -6,12 +6,12 @@ import pytest pytest.importorskip("PySide6") -from otko.commands import ( # noqa: E402 +from otko.commands import ( AddAnalysisCasesCommand, DeleteAnalysisCasesCommand, UpdateAnalysisCaseCommand, ) -from otko.core import ( # noqa: E402 +from otko.core import ( LinearTimeSeries, ModalCase, NodalLoad, @@ -20,7 +20,7 @@ from otko.core import ( # noqa: E402 StaticCase, TransientCase, ) -from otko.viewmodels import ProjectViewModel # noqa: E402 +from otko.viewmodels import ProjectViewModel def _vm_with_pattern() -> ProjectViewModel: @@ -29,8 +29,9 @@ def _vm_with_pattern() -> ProjectViewModel: vm.project.nodes.append(Node(id=1, coords=(0, 0, 0))) vm.project.time_series.append(LinearTimeSeries(id=1)) vm.project.load_patterns.append( - PlainLoadPattern(id=1, time_series_id=1, - nodal_loads=[NodalLoad(node_id=1, forces=(100, 0, 0, 0, 0, 0))]) + PlainLoadPattern( + id=1, time_series_id=1, nodal_loads=[NodalLoad(node_id=1, forces=(100, 0, 0, 0, 0, 0))] + ) ) return vm @@ -57,9 +58,14 @@ def test_add_modal_case_no_pattern_needed(qtbot) -> None: # type: ignore[no-unt def test_add_emits_modelMutated(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_with_pattern() with qtbot.waitSignal(vm.modelMutated, timeout=500): - vm.apply_command(AddAnalysisCasesCommand(vm, [ - StaticCase(id=1, pattern_ids=[1]), - ])) + vm.apply_command( + AddAnalysisCasesCommand( + vm, + [ + StaticCase(id=1, pattern_ids=[1]), + ], + ) + ) @pytest.mark.gui @@ -74,9 +80,14 @@ def test_add_duplicate_id_raises(qtbot) -> None: # type: ignore[no-untyped-def] @pytest.mark.gui def test_update_static_changes_n_steps(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_with_pattern() - vm.apply_command(AddAnalysisCasesCommand(vm, [ - StaticCase(id=1, name="A", pattern_ids=[1], n_steps=1), - ])) + vm.apply_command( + AddAnalysisCasesCommand( + vm, + [ + StaticCase(id=1, name="A", pattern_ids=[1], n_steps=1), + ], + ) + ) new = StaticCase(id=1, name="A", pattern_ids=[1], n_steps=10) vm.apply_command(UpdateAnalysisCaseCommand(vm, new)) assert vm.project.analyses[0].n_steps == 10 @@ -87,9 +98,14 @@ def test_update_static_changes_n_steps(qtbot) -> None: # type: ignore[no-untype @pytest.mark.gui def test_update_changes_case_type(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_with_pattern() - vm.apply_command(AddAnalysisCasesCommand(vm, [ - StaticCase(id=1, pattern_ids=[1]), - ])) + vm.apply_command( + AddAnalysisCasesCommand( + vm, + [ + StaticCase(id=1, pattern_ids=[1]), + ], + ) + ) swapped = ModalCase(id=1, name="Mode swap", n_modes=5) vm.apply_command(UpdateAnalysisCaseCommand(vm, swapped)) assert isinstance(vm.project.analyses[0], ModalCase) @@ -109,10 +125,15 @@ def test_update_unknown_id_raises(qtbot) -> None: # type: ignore[no-untyped-def @pytest.mark.gui def test_delete_round_trip(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_with_pattern() - vm.apply_command(AddAnalysisCasesCommand(vm, [ - StaticCase(id=1, pattern_ids=[1]), - ModalCase(id=2), - ])) + vm.apply_command( + AddAnalysisCasesCommand( + vm, + [ + StaticCase(id=1, pattern_ids=[1]), + ModalCase(id=2), + ], + ) + ) vm.apply_command(DeleteAnalysisCasesCommand(vm, {1})) assert {c.id for c in vm.project.analyses} == {2} vm.undo_stack.undo() diff --git a/tests/gui/test_analysis_runner.py b/tests/gui/test_analysis_runner.py index b2f982c..14c100a 100644 --- a/tests/gui/test_analysis_runner.py +++ b/tests/gui/test_analysis_runner.py @@ -12,21 +12,21 @@ import pytest pytest.importorskip("PySide6") pytest.importorskip("openseespy.opensees") -from otko.commands import ( # noqa: E402 +from otko.commands import ( AddAnalysisCasesCommand, AddElementsCommand, AddNodalLoadsCommand, AddNodesCommand, AddSectionsCommand, ) -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticBeamColumn, ElasticSection, Node, StaticCase, ) -from otko.services.results import StaticResults # noqa: E402 -from otko.viewmodels import AnalysisRunner, ProjectViewModel # noqa: E402 +from otko.services.results import StaticResults +from otko.viewmodels import AnalysisRunner, ProjectViewModel @pytest.fixture @@ -35,21 +35,42 @@ def cantilever_vm() -> ProjectViewModel: L = 5.0 vm = ProjectViewModel() vm.new_project(ndm=2, ndf=3) - vm.apply_command(AddNodesCommand(vm, [ - Node(id=1, coords=(0.0, 0.0, 0.0), - restraint=(True, True, False, False, False, True)), - Node(id=2, coords=(L, 0.0, 0.0)), - ])) - vm.apply_command(AddSectionsCommand(vm, [ - ElasticSection(id=1, E=200e9, A=0.01, Iz=8.333e-6), - ])) - vm.apply_command(AddElementsCommand(vm, [ - ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), - ])) + vm.apply_command( + AddNodesCommand( + vm, + [ + Node( + id=1, coords=(0.0, 0.0, 0.0), restraint=(True, True, False, False, False, True) + ), + Node(id=2, coords=(L, 0.0, 0.0)), + ], + ) + ) + vm.apply_command( + AddSectionsCommand( + vm, + [ + ElasticSection(id=1, E=200e9, A=0.01, Iz=8.333e-6), + ], + ) + ) + vm.apply_command( + AddElementsCommand( + vm, + [ + ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), + ], + ) + ) vm.apply_command(AddNodalLoadsCommand(vm, {2}, (0.0, -1000.0, 0.0, 0.0, 0.0, 0.0))) - vm.apply_command(AddAnalysisCasesCommand(vm, [ - StaticCase(id=1, name="Cantilever", pattern_ids=[1]), - ])) + vm.apply_command( + AddAnalysisCasesCommand( + vm, + [ + StaticCase(id=1, name="Cantilever", pattern_ids=[1]), + ], + ) + ) return vm diff --git a/tests/gui/test_assign_load_pattern_name.py b/tests/gui/test_assign_load_pattern_name.py index 151b1d2..b0b4fba 100644 --- a/tests/gui/test_assign_load_pattern_name.py +++ b/tests/gui/test_assign_load_pattern_name.py @@ -6,15 +6,14 @@ import pytest pytest.importorskip("PySide6") -from otko.commands import AddNodalLoadsCommand # noqa: E402 -from otko.core import ( # noqa: E402 +from otko.commands import AddNodalLoadsCommand +from otko.core import ( LinearTimeSeries, Node, PlainLoadPattern, - Project, ) -from otko.viewmodels import ProjectViewModel # noqa: E402 -from otko.views.dialogs.assign_load import AssignLoadDialog # noqa: E402 +from otko.viewmodels import ProjectViewModel +from otko.views.dialogs.assign_load import AssignLoadDialog @pytest.mark.gui @@ -48,10 +47,11 @@ def test_dialog_lists_existing_patterns(qtbot) -> None: # type: ignore[no-untyp @pytest.mark.gui def test_dialog_enables_name_field_for_new_pattern(qtbot) -> None: # type: ignore[no-untyped-def] dlg = AssignLoadDialog( - n_selected=1, existing_patterns=[(1, "Gravity")], + n_selected=1, + existing_patterns=[(1, "Gravity")], ) qtbot.addWidget(dlg) - dlg._pattern_cb.setCurrentIndex(1) # "" + dlg._pattern_cb.setCurrentIndex(1) # "" assert dlg.selected_pattern_id() is None assert dlg._new_name_edit.isEnabled() dlg._new_name_edit.setText("RefMoment") @@ -68,14 +68,23 @@ def test_command_creates_named_pattern(qtbot) -> None: # type: ignore[no-untype # Pre-existing 'Gravity' pattern — must NOT be reused when the user # asks for a new one named 'RefMoment'. vm.project.time_series.append(LinearTimeSeries(id=1, name="Gravity")) - vm.project.load_patterns.append(PlainLoadPattern( - id=1, name="Gravity", time_series_id=1, - )) + vm.project.load_patterns.append( + PlainLoadPattern( + id=1, + name="Gravity", + time_series_id=1, + ) + ) - vm.apply_command(AddNodalLoadsCommand( - vm, {1}, (0, 0, 0, 0, 0, 1.0), - pattern_id=None, new_pattern_name="RefMoment", - )) + vm.apply_command( + AddNodalLoadsCommand( + vm, + {1}, + (0, 0, 0, 0, 0, 1.0), + pattern_id=None, + new_pattern_name="RefMoment", + ) + ) # Two patterns now: Gravity (id=1) and RefMoment (id=2). names = [p.name for p in vm.project.load_patterns] assert "Gravity" in names and "RefMoment" in names @@ -90,13 +99,20 @@ def test_command_creates_constant_timeseries_when_requested(qtbot) -> None: # t the new pattern — required for axial preloads in moment-curvature runs where pseudoTime must NOT scale the axial force.""" from otko.core import ConstantTimeSeries + vm = ProjectViewModel() vm.new_project(ndm=2, ndf=3) vm.project.nodes.append(Node(id=1, coords=(0, 0, 0))) - vm.apply_command(AddNodalLoadsCommand( - vm, {1}, (-180, 0, 0, 0, 0, 0), - pattern_id=None, new_pattern_name="AxialP", new_ts_type="Constant", - )) + vm.apply_command( + AddNodalLoadsCommand( + vm, + {1}, + (-180, 0, 0, 0, 0, 0), + pattern_id=None, + new_pattern_name="AxialP", + new_ts_type="Constant", + ) + ) # Pattern + TimeSeries both created with the "AxialP" name. assert vm.project.load_patterns[0].name == "AxialP" ts = vm.project.time_series[0] @@ -111,11 +127,20 @@ def test_command_reuses_existing_pattern_by_id(qtbot) -> None: # type: ignore[n vm.new_project(ndm=2, ndf=3) vm.project.nodes.append(Node(id=1, coords=(0, 0, 0))) vm.project.time_series.append(LinearTimeSeries(id=1, name="Existing")) - vm.project.load_patterns.append(PlainLoadPattern( - id=1, name="Existing", time_series_id=1, - )) - vm.apply_command(AddNodalLoadsCommand( - vm, {1}, (0, 0, 0, 0, 0, 5.0), pattern_id=1, - )) + vm.project.load_patterns.append( + PlainLoadPattern( + id=1, + name="Existing", + time_series_id=1, + ) + ) + vm.apply_command( + AddNodalLoadsCommand( + vm, + {1}, + (0, 0, 0, 0, 0, 5.0), + pattern_id=1, + ) + ) assert len(vm.project.load_patterns) == 1 assert len(vm.project.load_patterns[0].nodal_loads) == 1 diff --git a/tests/gui/test_assign_masses.py b/tests/gui/test_assign_masses.py index 9501620..3ba0dda 100644 --- a/tests/gui/test_assign_masses.py +++ b/tests/gui/test_assign_masses.py @@ -6,7 +6,7 @@ import pytest pytest.importorskip("PySide6") -from otko.views.dialogs.assign_masses import AssignMassesDialog # noqa: E402 +from otko.views.dialogs.assign_masses import AssignMassesDialog @pytest.mark.gui @@ -23,7 +23,9 @@ def test_mass_vector_reads_spinboxes(qtbot) -> None: # type: ignore[no-untyped- dlg._mx.setValue(5000.0) dlg._my.setValue(5000.0) dlg._mz.setValue(100.0) - dlg._mxx.setValue(0.0); dlg._myy.setValue(0.0); dlg._mzz.setValue(0.0) + dlg._mxx.setValue(0.0) + dlg._myy.setValue(0.0) + dlg._mzz.setValue(0.0) assert dlg.mass_vector() == (5000.0, 5000.0, 100.0, 0.0, 0.0, 0.0) diff --git a/tests/gui/test_assign_zls.py b/tests/gui/test_assign_zls.py index 183e173..9a8cb66 100644 --- a/tests/gui/test_assign_zls.py +++ b/tests/gui/test_assign_zls.py @@ -6,27 +6,36 @@ import pytest pytest.importorskip("PySide6") -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticSection, Node, Project, ) -from otko.views.dialogs.assign_zls import ( # noqa: E402 +from otko.views.dialogs.assign_zls import ( AssignZeroLengthSectionDialog, ) def _project_with_section() -> Project: return Project( - ndm=2, ndf=3, + ndm=2, + ndf=3, nodes=[ Node(id=1, coords=(0, 0, 0)), Node(id=2, coords=(0, 0, 0)), ], - sections=[ElasticSection( - id=3, name="MK", E=30000, A=200, Iz=6667, Iy=6667, - G=12000, J=100, - )], + sections=[ + ElasticSection( + id=3, + name="MK", + E=30000, + A=200, + Iz=6667, + Iy=6667, + G=12000, + J=100, + ) + ], ) @@ -43,7 +52,8 @@ def test_dialog_lists_project_sections(qtbot) -> None: # type: ignore[no-untype @pytest.mark.gui def test_dialog_disabled_when_no_sections(qtbot) -> None: # type: ignore[no-untyped-def] p = Project( - ndm=2, ndf=3, + ndm=2, + ndf=3, nodes=[Node(id=1, coords=(0, 0, 0)), Node(id=2, coords=(0, 0, 0))], ) dlg = AssignZeroLengthSectionDialog(p, (1, 2)) diff --git a/tests/gui/test_canvas_grid_snap.py b/tests/gui/test_canvas_grid_snap.py index 81e8bd3..02ddb4b 100644 --- a/tests/gui/test_canvas_grid_snap.py +++ b/tests/gui/test_canvas_grid_snap.py @@ -12,7 +12,7 @@ import pytest pytest.importorskip("PySide6") -from otko.core import ( # noqa: E402 +from otko.core import ( CoordinateGridSystem, CoordinateSystem, GridSystem, @@ -23,7 +23,8 @@ from otko.core import ( # noqa: E402 # ────────────────────── logic helpers (no VTK) ────────────────────── def _nearest_snap( - cx: float, cy: float, + cx: float, + cy: float, world_pts: np.ndarray, screen_pts: np.ndarray, tol_px: float, @@ -33,7 +34,7 @@ def _nearest_snap( return None d2 = (screen_pts[:, 0] - cx) ** 2 + (screen_pts[:, 1] - cy) ** 2 idx = int(np.argmin(d2)) - if d2[idx] <= tol_px ** 2: + if d2[idx] <= tol_px**2: return tuple(float(v) for v in world_pts[idx]) # type: ignore[return-value] return None @@ -65,6 +66,7 @@ def test_grid_intersections_world_includes_all_visible_systems(qtbot) -> None: """ModelCanvas._grid_intersections_world combines every visible system's intersections (transformed by that system's origin/rotation).""" from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) @@ -101,15 +103,17 @@ def test_grid_intersections_world_includes_all_visible_systems(qtbot) -> None: def test_grid_intersections_world_returns_none_without_grid(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) - canvas.show_project(Project()) # default: Global system with no grid lines + canvas.show_project(Project()) # default: Global system with no grid lines assert canvas._grid_intersections_world() is None def test_hide_all_suppresses_intersections(qtbot) -> None: # type: ignore[no-untyped-def] """A system with ``hide_all=True`` must not contribute snap targets.""" from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) @@ -134,20 +138,23 @@ def test_hide_all_suppresses_intersections(qtbot) -> None: # type: ignore[no-un def test_hover_snap_marker_round_trips(qtbot) -> None: # type: ignore[no-untyped-def] """set_hover_snap(pt) creates an actor; passing None removes it.""" from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) - canvas.show_project(Project( - coord_systems=[ - CoordinateGridSystem( - name="Global", - grid=GridSystem( - x_grid_lines=make_grid_lines("X", [0.0, 3.0]), - y_grid_lines=make_grid_lines("Y", [0.0, 4.0]), - z_grid_lines=make_grid_lines("Z", [0.0]), + canvas.show_project( + Project( + coord_systems=[ + CoordinateGridSystem( + name="Global", + grid=GridSystem( + x_grid_lines=make_grid_lines("X", [0.0, 3.0]), + y_grid_lines=make_grid_lines("Y", [0.0, 4.0]), + z_grid_lines=make_grid_lines("Z", [0.0]), + ), ), - ), - ], - )) + ], + ) + ) r = canvas._renderer assert r._hover_actor is None r.set_hover_snap((3.0, 0.0, 0.0)) @@ -158,20 +165,23 @@ def test_hover_snap_marker_round_trips(qtbot) -> None: # type: ignore[no-untype def test_snap_preview_flag_clears_marker(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) - canvas.show_project(Project( - coord_systems=[ - CoordinateGridSystem( - name="Global", - grid=GridSystem( - x_grid_lines=make_grid_lines("X", [0.0]), - y_grid_lines=make_grid_lines("Y", [0.0]), - z_grid_lines=make_grid_lines("Z", [0.0]), + canvas.show_project( + Project( + coord_systems=[ + CoordinateGridSystem( + name="Global", + grid=GridSystem( + x_grid_lines=make_grid_lines("X", [0.0]), + y_grid_lines=make_grid_lines("Y", [0.0]), + z_grid_lines=make_grid_lines("Z", [0.0]), + ), ), - ), - ], - )) + ], + ) + ) canvas.set_snap_preview_enabled(True) canvas._renderer.set_hover_snap((0.0, 0.0, 0.0)) assert canvas._renderer._hover_actor is not None @@ -183,7 +193,7 @@ def test_snap_preview_flag_clears_marker(qtbot) -> None: # type: ignore[no-unty def _pt_to_segment_d2(p: np.ndarray, a: np.ndarray, b: np.ndarray) -> np.ndarray: """Vectorised point-to-segment squared distance, used to verify logic.""" ab = b - a - ab_sq = (ab ** 2).sum(axis=1) + ab_sq = (ab**2).sum(axis=1) ab_sq = np.where(ab_sq == 0, 1.0, ab_sq) pa = p - a t = (pa * ab).sum(axis=1) / ab_sq @@ -204,21 +214,21 @@ def test_point_to_segment_midpoint_hit() -> None: def test_point_to_segment_endpoint_hit() -> None: """A click near an endpoint must also hit — the bug fix this test guards.""" - p = np.array([1.0, 51.0]) # 1 px off node-a on a horizontal frame + p = np.array([1.0, 51.0]) # 1 px off node-a on a horizontal frame a = np.array([[0.0, 50.0]]) b = np.array([[100.0, 50.0]]) d2 = _pt_to_segment_d2(p, a, b) - assert d2[0] <= 2.0 ** 2 # well inside a 5-pixel test tolerance + assert d2[0] <= 2.0**2 # well inside a 5-pixel test tolerance def test_point_to_segment_orthogonal_miss() -> None: """Clicking far from a short frame must produce a large distance.""" p = np.array([50.0, 200.0]) a = np.array([[0.0, 50.0]]) - b = np.array([[10.0, 50.0]]) # short frame at (0..10, 50) + b = np.array([[10.0, 50.0]]) # short frame at (0..10, 50) d2 = _pt_to_segment_d2(p, a, b) # Closest point is (10, 50), distance ≈ sqrt(40² + 150²) ≈ 155 - assert d2[0] > 150.0 ** 2 + assert d2[0] > 150.0**2 def test_single_node_radius_uses_grid_extent(qtbot) -> None: # type: ignore[no-untyped-def] @@ -226,6 +236,7 @@ def test_single_node_radius_uses_grid_extent(qtbot) -> None: # type: ignore[no- grid bounds so the node remains visible (regression guard).""" from otko.core import Node from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) p = Project( diff --git a/tests/gui/test_case_forms.py b/tests/gui/test_case_forms.py index c911141..6672806 100644 --- a/tests/gui/test_case_forms.py +++ b/tests/gui/test_case_forms.py @@ -6,13 +6,13 @@ import pytest pytest.importorskip("PySide6") -from otko.core import ( # noqa: E402 +from otko.core import ( NodalLoad, PlainLoadPattern, StaticCase, TransientCase, ) -from otko.views.dialogs.case_forms import TransientCaseForm # noqa: E402 +from otko.views.dialogs.case_forms import TransientCaseForm def _patterns(): # type: ignore[no-untyped-def] diff --git a/tests/gui/test_commands.py b/tests/gui/test_commands.py index a02bdc0..0457390 100644 --- a/tests/gui/test_commands.py +++ b/tests/gui/test_commands.py @@ -11,15 +11,14 @@ import pytest pytest.importorskip("PySide6") -from otko.commands import ( # noqa: E402 +from otko.commands import ( AddElementsCommand, AddNodalLoadsCommand, AddNodesCommand, - DeleteElementsCommand, DeleteNodesCommand, SetRestraintCommand, ) -from otko.core import ( # noqa: E402 +from otko.core import ( LinearTimeSeries, NodalLoad, Node, @@ -27,7 +26,7 @@ from otko.core import ( # noqa: E402 Steel01, TrussElement, ) -from otko.viewmodels import ProjectViewModel # noqa: E402 +from otko.viewmodels import ProjectViewModel # ─────────────────────────── helpers ──────────────────────────────── @@ -77,17 +76,27 @@ def test_add_nodes_duplicate_id_raises(qtbot) -> None: # type: ignore[no-untype @pytest.mark.gui def test_delete_node_cascades_to_elements(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm() - vm.apply_command(AddNodesCommand(vm, [ - Node(id=1, coords=(0, 0, 0)), - Node(id=2, coords=(1, 0, 0)), - Node(id=3, coords=(2, 0, 0)), - ])) + vm.apply_command( + AddNodesCommand( + vm, + [ + Node(id=1, coords=(0, 0, 0)), + Node(id=2, coords=(1, 0, 0)), + Node(id=3, coords=(2, 0, 0)), + ], + ) + ) # Add a material so the element is valid. vm.project.materials.append(Steel01(id=1, Fy=420e6, E0=200e9, b=0.01)) - vm.apply_command(AddElementsCommand(vm, [ - TrussElement(id=1, nodes=(1, 2), area=1e-3, material_id=1), - TrussElement(id=2, nodes=(2, 3), area=1e-3, material_id=1), - ])) + vm.apply_command( + AddElementsCommand( + vm, + [ + TrussElement(id=1, nodes=(1, 2), area=1e-3, material_id=1), + TrussElement(id=2, nodes=(2, 3), area=1e-3, material_id=1), + ], + ) + ) # Delete node 2 → both elements should disappear. vm.apply_command(DeleteNodesCommand(vm, {2})) assert {n.id for n in vm.project.nodes} == {1, 3} @@ -102,10 +111,15 @@ def test_delete_node_cascades_to_elements(qtbot) -> None: # type: ignore[no-unt @pytest.mark.gui def test_set_restraint_round_trip(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm() - vm.apply_command(AddNodesCommand(vm, [ - Node(id=1, coords=(0, 0, 0)), - Node(id=2, coords=(1, 0, 0)), - ])) + vm.apply_command( + AddNodesCommand( + vm, + [ + Node(id=1, coords=(0, 0, 0)), + Node(id=2, coords=(1, 0, 0)), + ], + ) + ) fix = (True, True, True, True, True, True) vm.apply_command(SetRestraintCommand(vm, {1, 2}, fix)) assert vm.project.node(1).restraint == fix diff --git a/tests/gui/test_convert_element_type.py b/tests/gui/test_convert_element_type.py index fa14511..426c23b 100644 --- a/tests/gui/test_convert_element_type.py +++ b/tests/gui/test_convert_element_type.py @@ -6,52 +6,82 @@ import pytest pytest.importorskip("PySide6") -from otko.commands import ( # noqa: E402 +from otko.commands import ( AddElementsCommand, AddMaterialsCommand, AddNodesCommand, AddSectionsCommand, ConvertElementTypeCommand, ) -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticBeamColumn, ElasticSection, ElasticUniaxial, Node, TrussElement, ) -from otko.viewmodels import ProjectViewModel # noqa: E402 +from otko.viewmodels import ProjectViewModel def _vm_setup() -> ProjectViewModel: vm = ProjectViewModel() vm.new_project() - vm.apply_command(AddNodesCommand(vm, [ - Node(id=1, coords=(0, 0, 0)), - Node(id=2, coords=(3, 0, 0)), - ])) - vm.apply_command(AddMaterialsCommand(vm, [ - ElasticUniaxial(id=1, name="Steel", E=200e9), - ])) - vm.apply_command(AddSectionsCommand(vm, [ - ElasticSection( - id=1, name="Default", E=200e9, A=0.01, - Iz=8.33e-6, Iy=8.33e-6, G=80e9, J=1e-6, - ), - ])) + vm.apply_command( + AddNodesCommand( + vm, + [ + Node(id=1, coords=(0, 0, 0)), + Node(id=2, coords=(3, 0, 0)), + ], + ) + ) + vm.apply_command( + AddMaterialsCommand( + vm, + [ + ElasticUniaxial(id=1, name="Steel", E=200e9), + ], + ) + ) + vm.apply_command( + AddSectionsCommand( + vm, + [ + ElasticSection( + id=1, + name="Default", + E=200e9, + A=0.01, + Iz=8.33e-6, + Iy=8.33e-6, + G=80e9, + J=1e-6, + ), + ], + ) + ) return vm @pytest.mark.gui def test_frame_to_truss_conversion(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_setup() - vm.apply_command(AddElementsCommand(vm, [ - ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), - ])) - vm.apply_command(ConvertElementTypeCommand( - vm, {1}, "Truss", - defaults={"material_id": 1, "area": 0.001}, - )) + vm.apply_command( + AddElementsCommand( + vm, + [ + ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), + ], + ) + ) + vm.apply_command( + ConvertElementTypeCommand( + vm, + {1}, + "Truss", + defaults={"material_id": 1, "area": 0.001}, + ) + ) el = vm.project.element(1) assert isinstance(el, TrussElement) assert el.nodes == (1, 2) @@ -62,13 +92,22 @@ def test_frame_to_truss_conversion(qtbot) -> None: # type: ignore[no-untyped-de @pytest.mark.gui def test_truss_to_frame_conversion(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_setup() - vm.apply_command(AddElementsCommand(vm, [ - TrussElement(id=1, nodes=(1, 2), area=0.001, material_id=1), - ])) - vm.apply_command(ConvertElementTypeCommand( - vm, {1}, "ElasticBeamColumn", - defaults={"section_id": 1}, - )) + vm.apply_command( + AddElementsCommand( + vm, + [ + TrussElement(id=1, nodes=(1, 2), area=0.001, material_id=1), + ], + ) + ) + vm.apply_command( + ConvertElementTypeCommand( + vm, + {1}, + "ElasticBeamColumn", + defaults={"section_id": 1}, + ) + ) el = vm.project.element(1) assert isinstance(el, ElasticBeamColumn) assert el.nodes == (1, 2) @@ -78,12 +117,22 @@ def test_truss_to_frame_conversion(qtbot) -> None: # type: ignore[no-untyped-de @pytest.mark.gui def test_convert_preserves_id_and_is_undoable(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_setup() - vm.apply_command(AddElementsCommand(vm, [ - ElasticBeamColumn(id=42, name="A", nodes=(1, 2), section_id=1), - ])) - vm.apply_command(ConvertElementTypeCommand( - vm, {42}, "Truss", defaults={"material_id": 1, "area": 0.002}, - )) + vm.apply_command( + AddElementsCommand( + vm, + [ + ElasticBeamColumn(id=42, name="A", nodes=(1, 2), section_id=1), + ], + ) + ) + vm.apply_command( + ConvertElementTypeCommand( + vm, + {42}, + "Truss", + defaults={"material_id": 1, "area": 0.002}, + ) + ) assert isinstance(vm.project.element(42), TrussElement) assert vm.project.element(42).name == "A" vm.undo_stack.undo() @@ -93,13 +142,23 @@ def test_convert_preserves_id_and_is_undoable(qtbot) -> None: # type: ignore[no @pytest.mark.gui def test_convert_same_type_is_noop(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_setup() - vm.apply_command(AddElementsCommand(vm, [ - TrussElement(id=1, nodes=(1, 2), area=0.001, material_id=1), - ])) + vm.apply_command( + AddElementsCommand( + vm, + [ + TrussElement(id=1, nodes=(1, 2), area=0.001, material_id=1), + ], + ) + ) before = vm.project.element(1) - vm.apply_command(ConvertElementTypeCommand( - vm, {1}, "Truss", defaults={"material_id": 1, "area": 0.002}, - )) + vm.apply_command( + ConvertElementTypeCommand( + vm, + {1}, + "Truss", + defaults={"material_id": 1, "area": 0.002}, + ) + ) # Class is still Truss and the element object is unchanged. assert vm.project.element(1) is before @@ -107,10 +166,20 @@ def test_convert_same_type_is_noop(qtbot) -> None: # type: ignore[no-untyped-de @pytest.mark.gui def test_unknown_target_type_raises(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_setup() - vm.apply_command(AddElementsCommand(vm, [ - TrussElement(id=1, nodes=(1, 2), area=0.001, material_id=1), - ])) + vm.apply_command( + AddElementsCommand( + vm, + [ + TrussElement(id=1, nodes=(1, 2), area=0.001, material_id=1), + ], + ) + ) with pytest.raises(ValueError): - vm.apply_command(ConvertElementTypeCommand( - vm, {1}, "NotAType", defaults={}, - )) + vm.apply_command( + ConvertElementTypeCommand( + vm, + {1}, + "NotAType", + defaults={}, + ) + ) diff --git a/tests/gui/test_define_dialogs.py b/tests/gui/test_define_dialogs.py index c671a1e..4b6f9b3 100644 --- a/tests/gui/test_define_dialogs.py +++ b/tests/gui/test_define_dialogs.py @@ -6,9 +6,9 @@ import pytest pytest.importorskip("PySide6") -from otko.core import LinearTimeSeries, Project # noqa: E402 -from otko.views.dialogs.linear_time_series import LinearTimeSeriesDialog # noqa: E402 -from otko.views.dialogs.plain_pattern import PlainPatternDialog # noqa: E402 +from otko.core import LinearTimeSeries, Project +from otko.views.dialogs.linear_time_series import LinearTimeSeriesDialog +from otko.views.dialogs.plain_pattern import PlainPatternDialog @pytest.mark.gui @@ -27,10 +27,12 @@ def test_linear_time_series_dialog_builds_entity(qtbot) -> None: # type: ignore @pytest.mark.gui def test_plain_pattern_dialog_uses_selected_time_series(qtbot) -> None: # type: ignore[no-untyped-def] - proj = Project(time_series=[ - LinearTimeSeries(id=1, name="GravityTS"), - LinearTimeSeries(id=2, name="RampTS"), - ]) + proj = Project( + time_series=[ + LinearTimeSeries(id=1, name="GravityTS"), + LinearTimeSeries(id=2, name="RampTS"), + ] + ) dlg = PlainPatternDialog(project=proj, next_pattern_id=4) qtbot.addWidget(dlg) diff --git a/tests/gui/test_draw_frame_tool.py b/tests/gui/test_draw_frame_tool.py index b98bb40..ff6370c 100644 --- a/tests/gui/test_draw_frame_tool.py +++ b/tests/gui/test_draw_frame_tool.py @@ -11,11 +11,11 @@ import pytest pytest.importorskip("PySide6") -from otko.commands import AddNodesCommand # noqa: E402 -from otko.core import ElasticBeamColumn, ElasticSection, Node # noqa: E402 -from otko.viewmodels import ProjectViewModel # noqa: E402 -from otko.views.canvas3d import SelectionState # noqa: E402 -from otko.views.tools.draw_frame import DrawFrameTool # noqa: E402 +from otko.commands import AddNodesCommand +from otko.core import ElasticBeamColumn, ElasticSection, Node +from otko.viewmodels import ProjectViewModel +from otko.views.canvas3d import SelectionState +from otko.views.tools.draw_frame import DrawFrameTool class _CanvasStub: @@ -35,10 +35,15 @@ class _CanvasStub: def _vm_with_two_nodes() -> ProjectViewModel: vm = ProjectViewModel() vm.new_project() - vm.apply_command(AddNodesCommand(vm, [ - Node(id=1, coords=(0, 0, 0)), - Node(id=2, coords=(3, 0, 0)), - ])) + vm.apply_command( + AddNodesCommand( + vm, + [ + Node(id=1, coords=(0, 0, 0)), + Node(id=2, coords=(3, 0, 0)), + ], + ) + ) return vm @@ -86,7 +91,7 @@ def test_self_pick_is_ignored(qtbot) -> None: # type: ignore[no-untyped-def] tool.activate() tool.on_node_picked(1) - tool.on_node_picked(1) # same node — should NOT create an element + tool.on_node_picked(1) # same node — should NOT create an element assert vm.project.elements == [] # First-node state preserved so user can finish the gesture. assert tool._first_node_id == 1 @@ -150,6 +155,7 @@ def test_reset_clears_first_pick(qtbot) -> None: # type: ignore[no-untyped-def] def test_empty_clicks_snap_and_create_frame(qtbot) -> None: # type: ignore[no-untyped-def] """Two clicks on empty grid intersections → 2 nodes + 1 frame.""" from otko.core import GridSystem + vm = ProjectViewModel() vm.new_project() vm.project.grid_system = GridSystem( # type: ignore[union-attr] @@ -165,9 +171,9 @@ def test_empty_clicks_snap_and_create_frame(qtbot) -> None: # type: ignore[no-u tool.on_empty_clicked(0.0, 0.0, 0.0) tool.on_empty_clicked(3.0, 4.0, 0.0) - assert len(vm.project.nodes) == 2 # type: ignore[union-attr] - assert len(vm.project.elements) == 1 # type: ignore[union-attr] - elem = vm.project.elements[0] # type: ignore[union-attr] + assert len(vm.project.nodes) == 2 # type: ignore[union-attr] + assert len(vm.project.elements) == 1 # type: ignore[union-attr] + elem = vm.project.elements[0] # type: ignore[union-attr] assert isinstance(elem, ElasticBeamColumn) n1 = next(n for n in vm.project.nodes if n.id == elem.nodes[0]) # type: ignore[union-attr] n2 = next(n for n in vm.project.nodes if n.id == elem.nodes[1]) # type: ignore[union-attr] @@ -179,18 +185,21 @@ def test_empty_clicks_snap_and_create_frame(qtbot) -> None: # type: ignore[no-u def test_empty_click_reuses_coincident_node(qtbot) -> None: # type: ignore[no-untyped-def] """An empty click at an existing node's location must not duplicate it.""" from otko.core import GridSystem - vm = _vm_with_two_nodes() # nodes 1, 2 at (0,0,0) and (3,0,0) + + vm = _vm_with_two_nodes() # nodes 1, 2 at (0,0,0) and (3,0,0) vm.project.grid_system = GridSystem( # type: ignore[union-attr] - x_lines=[0.0, 3.0], y_lines=[0.0], z_lines=[0.0], + x_lines=[0.0, 3.0], + y_lines=[0.0], + z_lines=[0.0], ) tool = DrawFrameTool(_CanvasStub(), vm) # type: ignore[arg-type] tool.activate() - tool.on_empty_clicked(0.0, 0.0, 0.0) # existing node 1 at (0,0,0) - tool.on_empty_clicked(3.0, 0.0, 0.0) # existing node 2 at (3,0,0) + tool.on_empty_clicked(0.0, 0.0, 0.0) # existing node 1 at (0,0,0) + tool.on_empty_clicked(3.0, 0.0, 0.0) # existing node 2 at (3,0,0) - assert len(vm.project.nodes) == 2 # type: ignore[union-attr] (no new nodes) - elem = vm.project.elements[0] # type: ignore[union-attr] + assert len(vm.project.nodes) == 2 # type: ignore[union-attr] (no new nodes) + elem = vm.project.elements[0] # type: ignore[union-attr] assert set(elem.nodes) == {1, 2} @@ -198,19 +207,22 @@ def test_empty_click_reuses_coincident_node(qtbot) -> None: # type: ignore[no-u def test_mixed_node_pick_then_empty_click(qtbot) -> None: # type: ignore[no-untyped-def] """First click picks existing node; second click creates new node + frame.""" from otko.core import GridSystem + vm = _vm_with_two_nodes() vm.project.grid_system = GridSystem( # type: ignore[union-attr] - x_lines=[0.0, 3.0, 6.0], y_lines=[0.0], z_lines=[0.0], + x_lines=[0.0, 3.0, 6.0], + y_lines=[0.0], + z_lines=[0.0], ) tool = DrawFrameTool(_CanvasStub(), vm) # type: ignore[arg-type] tool.activate() - tool.on_node_picked(1) # start at node 1 = (0,0,0) + tool.on_node_picked(1) # start at node 1 = (0,0,0) tool.on_empty_clicked(6.0, 0.0, 0.0) # canvas emits exact snap - assert len(vm.project.nodes) == 3 # type: ignore[union-attr] (new node added) + assert len(vm.project.nodes) == 3 # type: ignore[union-attr] (new node added) assert len(vm.project.elements) == 1 - new_node = vm.project.nodes[-1] # type: ignore[union-attr] + new_node = vm.project.nodes[-1] # type: ignore[union-attr] assert new_node.coords == (6.0, 0.0, 0.0) elem = vm.project.elements[0] assert set(elem.nodes) == {1, new_node.id} diff --git a/tests/gui/test_draw_node_tool.py b/tests/gui/test_draw_node_tool.py index 26a9edb..aab389d 100644 --- a/tests/gui/test_draw_node_tool.py +++ b/tests/gui/test_draw_node_tool.py @@ -10,10 +10,10 @@ import pytest pytest.importorskip("PySide6") -from otko.core import GridSystem, Node, Project # noqa: E402 -from otko.viewmodels import ProjectViewModel # noqa: E402 -from otko.views.canvas3d.selection import SelectionState # noqa: E402 -from otko.views.tools.draw_node import ( # noqa: E402 +from otko.core import GridSystem, Node +from otko.viewmodels import ProjectViewModel +from otko.views.canvas3d.selection import SelectionState +from otko.views.tools.draw_node import ( DrawNodeTool, _snap_to_grid, ) @@ -46,8 +46,12 @@ def _vm_with_grid() -> ProjectViewModel: # ────────────────────────── snap helper ───────────────────────────── def test_snap_to_grid_picks_nearest_lines() -> None: x, y, z = _snap_to_grid( - 2.2, 3.7, 0.6, - x_lines=[0.0, 3.0], y_lines=[0.0, 4.0], z_lines=[0.0, 3.0], + 2.2, + 3.7, + 0.6, + x_lines=[0.0, 3.0], + y_lines=[0.0, 4.0], + z_lines=[0.0, 3.0], ) assert (x, y, z) == (3.0, 4.0, 0.0) @@ -99,10 +103,11 @@ def test_grid_less_canvas_never_emits_to_tool(qtbot) -> None: # type: ignore[no target. For a grid-less project, the canvas never emits, so the tool is never invoked. We therefore don't test rejection here. """ - vm = ProjectViewModel(); vm.new_project() + vm = ProjectViewModel() + vm.new_project() tool = DrawNodeTool(_CanvasStub(), vm) # type: ignore[arg-type] tool.activate() - assert vm.project.nodes == [] # type: ignore[union-attr] + assert vm.project.nodes == [] # type: ignore[union-attr] @pytest.mark.gui diff --git a/tests/gui/test_draw_truss_tool.py b/tests/gui/test_draw_truss_tool.py index 3cc5d52..bcc2879 100644 --- a/tests/gui/test_draw_truss_tool.py +++ b/tests/gui/test_draw_truss_tool.py @@ -6,18 +6,17 @@ import pytest pytest.importorskip("PySide6") -from otko.core import ( # noqa: E402 +from otko.core import ( CoordinateGridSystem, - CoordinateSystem, ElasticUniaxial, GridSystem, Node, TrussElement, make_grid_lines, ) -from otko.viewmodels import ProjectViewModel # noqa: E402 -from otko.views.canvas3d.selection import SelectionState # noqa: E402 -from otko.views.tools.draw_truss import DrawTrussTool # noqa: E402 +from otko.viewmodels import ProjectViewModel +from otko.views.canvas3d.selection import SelectionState +from otko.views.tools.draw_truss import DrawTrussTool class _CanvasStub: @@ -34,10 +33,12 @@ class _CanvasStub: def _vm_with_grid_and_nodes() -> ProjectViewModel: vm = ProjectViewModel() vm.new_project() - vm.project.nodes.extend([ - Node(id=1, coords=(0, 0, 0)), - Node(id=2, coords=(3, 0, 0)), - ]) + vm.project.nodes.extend( + [ + Node(id=1, coords=(0, 0, 0)), + Node(id=2, coords=(3, 0, 0)), + ] + ) vm.project.coord_systems = [ CoordinateGridSystem( name="Global", @@ -60,13 +61,13 @@ def test_two_clicks_create_truss(qtbot) -> None: # type: ignore[no-untyped-def] tool.on_node_picked(1) tool.on_node_picked(2) - assert len(vm.project.elements) == 1 # type: ignore[union-attr] - el = vm.project.elements[0] # type: ignore[union-attr] + assert len(vm.project.elements) == 1 # type: ignore[union-attr] + el = vm.project.elements[0] # type: ignore[union-attr] assert isinstance(el, TrussElement) assert el.nodes == (1, 2) assert el.area > 0 # A default ElasticUniaxial must have been created for the material. - assert len(vm.project.materials) == 1 # type: ignore[union-attr] + assert len(vm.project.materials) == 1 # type: ignore[union-attr] assert isinstance(vm.project.materials[0], ElasticUniaxial) @@ -78,9 +79,9 @@ def test_draw_truss_reuses_existing_material(qtbot) -> None: # type: ignore[no- tool.activate() tool.on_node_picked(1) tool.on_node_picked(2) - el = vm.project.elements[0] # type: ignore[union-attr] + el = vm.project.elements[0] # type: ignore[union-attr] assert el.material_id == 1 - assert len(vm.project.materials) == 1 # type: ignore[union-attr] (no new material) + assert len(vm.project.materials) == 1 # type: ignore[union-attr] (no new material) @pytest.mark.gui @@ -104,8 +105,8 @@ def test_empty_click_creates_node_then_truss(qtbot) -> None: # type: ignore[no- tool.on_empty_clicked(3.0, 0.0, 0.0) # Two nodes created + one truss element. - assert len(vm.project.nodes) == 2 # type: ignore[union-attr] - assert len(vm.project.elements) == 1 # type: ignore[union-attr] + assert len(vm.project.nodes) == 2 # type: ignore[union-attr] + assert len(vm.project.elements) == 1 # type: ignore[union-attr] assert isinstance(vm.project.elements[0], TrussElement) @@ -115,5 +116,5 @@ def test_self_pick_is_ignored(qtbot) -> None: # type: ignore[no-untyped-def] tool = DrawTrussTool(_CanvasStub(), vm) # type: ignore[arg-type] tool.activate() tool.on_node_picked(1) - tool.on_node_picked(1) # same node — must not create a zero-length truss - assert vm.project.elements == [] # type: ignore[union-attr] + tool.on_node_picked(1) # same node — must not create a zero-length truss + assert vm.project.elements == [] # type: ignore[union-attr] diff --git a/tests/gui/test_grid_dialog.py b/tests/gui/test_grid_dialog.py index 489d64b..0013ad4 100644 --- a/tests/gui/test_grid_dialog.py +++ b/tests/gui/test_grid_dialog.py @@ -6,7 +6,7 @@ import pytest pytest.importorskip("PySide6") -from otko.core import ( # noqa: E402 +from otko.core import ( CoordinateGridSystem, CoordinateSystem, GridLine, @@ -19,11 +19,18 @@ from otko.core import ( # noqa: E402 @pytest.mark.gui def test_quick_grid_lines_produces_ordinates(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.dialogs.quick_grid_lines import QuickGridLinesDialog + dlg = QuickGridLinesDialog() qtbot.addWidget(dlg) - dlg._x_n.setValue(4); dlg._x_s.setValue(3.0); dlg._x_f.setValue(0.0) - dlg._y_n.setValue(2); dlg._y_s.setValue(4.0); dlg._y_f.setValue(0.0) - dlg._z_n.setValue(3); dlg._z_s.setValue(3.0); dlg._z_f.setValue(-3.0) + dlg._x_n.setValue(4) + dlg._x_s.setValue(3.0) + dlg._x_f.setValue(0.0) + dlg._y_n.setValue(2) + dlg._y_s.setValue(4.0) + dlg._y_f.setValue(0.0) + dlg._z_n.setValue(3) + dlg._z_s.setValue(3.0) + dlg._z_f.setValue(-3.0) xs, ys, zs = dlg.ordinates() assert xs == [0.0, 3.0, 6.0, 9.0] assert ys == [0.0, 4.0] @@ -33,6 +40,7 @@ def test_quick_grid_lines_produces_ordinates(qtbot) -> None: # type: ignore[no- @pytest.mark.gui def test_quick_grid_zero_lines_produces_empty(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.dialogs.quick_grid_lines import QuickGridLinesDialog + dlg = QuickGridLinesDialog() qtbot.addWidget(dlg) dlg._x_n.setValue(0) @@ -48,6 +56,7 @@ def test_locate_origin_round_trip(qtbot) -> None: # type: ignore[no-untyped-def from otko.views.dialogs.locate_origin import ( CoordSystemLocationOrientationDialog, ) + dlg = CoordSystemLocationOrientationDialog( origin=(1.5, -2.0, 3.0), rotation_deg=(0.0, 0.0, 45.0), @@ -65,6 +74,7 @@ def test_define_grid_data_round_trip(qtbot) -> None: # type: ignore[no-untyped- from otko.views.dialogs.define_grid_data import ( DefineGridSystemDataDialog, ) + cs = CoordinateGridSystem( name="Floor2", coord=CoordinateSystem(origin=(0, 0, 3.5)), @@ -96,6 +106,7 @@ def test_define_grid_data_spacing_mode_conversion(qtbot) -> None: # type: ignor from otko.views.dialogs.define_grid_data import ( DefineGridSystemDataDialog, ) + cs = CoordinateGridSystem( name="Test", grid=GridSystem(x_grid_lines=make_grid_lines("X", [0.0, 3.0, 6.0, 10.0])), @@ -117,6 +128,7 @@ def test_define_grid_data_global_name_locked(qtbot) -> None: # type: ignore[no- from otko.views.dialogs.define_grid_data import ( DefineGridSystemDataDialog, ) + dlg = DefineGridSystemDataDialog(is_global=True) qtbot.addWidget(dlg) assert dlg._name_edit.text() == "Global" @@ -130,6 +142,7 @@ def test_define_grid_data_add_and_delete_row(qtbot) -> None: # type: ignore[no- from otko.views.dialogs.define_grid_data import ( DefineGridSystemDataDialog, ) + dlg = DefineGridSystemDataDialog() qtbot.addWidget(dlg) # Start with empty X table. diff --git a/tests/gui/test_main_window.py b/tests/gui/test_main_window.py index 8ce4d0b..a57a00f 100644 --- a/tests/gui/test_main_window.py +++ b/tests/gui/test_main_window.py @@ -20,3 +20,22 @@ def test_main_window_opens(qtbot) -> None: # type: ignore[no-untyped-def] window.show() assert window.isVisible() assert window.windowTitle() == "OTKO" + + +@pytest.mark.gui +def test_close_dirty_hidden_window_does_not_block(qtbot) -> None: # type: ignore[no-untyped-def] + """Closing a dirty but hidden window must not raise a modal prompt. + + Qt closes hidden widgets during teardown (as ``pytest-qt`` does); a + modal "unsaved changes" dialog there would block forever with no user + to answer it. The prompt is therefore gated on visibility. + """ + from otko.views.main_window import MainWindow + + window = MainWindow() + qtbot.addWidget(window) + window._vm.new_project() + window._vm.mark_dirty() + assert not window.isVisible() + assert window.close() is True + assert window._vm.is_dirty # unchanged: the prompt was skipped diff --git a/tests/gui/test_material_dialog_concrete04.py b/tests/gui/test_material_dialog_concrete04.py index 4671343..23523bc 100644 --- a/tests/gui/test_material_dialog_concrete04.py +++ b/tests/gui/test_material_dialog_concrete04.py @@ -6,10 +6,10 @@ import pytest pytest.importorskip("PySide6") -from otko.core import Concrete04 # noqa: E402 -from otko.views.dialogs.material_forms import ( # noqa: E402 - Concrete04Form, +from otko.core import Concrete04 +from otko.views.dialogs.material_forms import ( FORM_REGISTRY, + Concrete04Form, ) @@ -37,7 +37,7 @@ def test_concrete04_read_no_tension(qtbot) -> None: # type: ignore[no-untyped-d form._epsc0.setValue(-0.002) form._epscu.setValue(-0.005) form._Ec.setValue(30e9) - form._fct.setValue(0.0) # 0 → no tension + form._fct.setValue(0.0) # 0 → no tension form._et.setValue(0.0) form._material_id = 1 @@ -77,9 +77,14 @@ def test_concrete04_read_with_tension(qtbot) -> None: # type: ignore[no-untyped def test_concrete04_populate_round_trip(qtbot) -> None: # type: ignore[no-untyped-def] """populate(mat) + read() reproduces the original model object.""" original = Concrete04( - id=3, name="C40-Tension", - fpc=-40e6, epsc0=-0.0022, epscu=-0.006, Ec=32e9, - fct=2.5e6, et=8e-5, + id=3, + name="C40-Tension", + fpc=-40e6, + epsc0=-0.0022, + epscu=-0.006, + Ec=32e9, + fct=2.5e6, + et=8e-5, ) form = Concrete04Form() @@ -102,8 +107,8 @@ def test_concrete04_accepts_ksi_values(qtbot) -> None: # type: ignore[no-untype """Form must not clamp US-customary (ksi) values for fpc.""" form = Concrete04Form() qtbot.addWidget(form) - form._fpc.setValue(-6.0) # ksi - form._Ec.setValue(3600.0) # ksi + form._fpc.setValue(-6.0) # ksi + form._Ec.setValue(3600.0) # ksi assert form._fpc.value() == pytest.approx(-6.0) assert form._Ec.value() == pytest.approx(3600.0) diff --git a/tests/gui/test_material_form_ranges.py b/tests/gui/test_material_form_ranges.py index 13a6eb3..54c1899 100644 --- a/tests/gui/test_material_form_ranges.py +++ b/tests/gui/test_material_form_ranges.py @@ -10,7 +10,7 @@ import pytest pytest.importorskip("PySide6") -from otko.views.dialogs.material_forms import ( # noqa: E402 +from otko.views.dialogs.material_forms import ( Concrete01Form, Concrete02Form, Steel01Form, @@ -50,7 +50,7 @@ def test_concrete02_accepts_ksi_values(qtbot) -> None: # type: ignore[no-untype qtbot.addWidget(form) form._fpc.setValue(-6.0) form._fpcu.setValue(-5.0) - form._ft.setValue(0.6) # ksi tensile strength + form._ft.setValue(0.6) # ksi tensile strength assert form._fpc.value() == pytest.approx(-6.0) assert form._fpcu.value() == pytest.approx(-5.0) assert form._ft.value() == pytest.approx(0.6) @@ -60,8 +60,8 @@ def test_concrete02_accepts_ksi_values(qtbot) -> None: # type: ignore[no-untype def test_steel01_accepts_ksi_values(qtbot) -> None: # type: ignore[no-untyped-def] form = Steel01Form() qtbot.addWidget(form) - form._fy.setValue(60.0) # ksi - form._e0.setValue(30000.0) # ksi + form._fy.setValue(60.0) # ksi + form._e0.setValue(30000.0) # ksi form._b.setValue(0.01) assert form._fy.value() == pytest.approx(60.0) assert form._e0.value() == pytest.approx(30000.0) @@ -83,6 +83,7 @@ def test_spinbox_uses_c_locale_for_decimal_separator(qtbot) -> None: # type: ig the OS locale. On tr_TR / de_DE Windows the default QLocale expects ',' and silently rejects Tcl-style '-0.004' inputs.""" from PySide6.QtCore import QLocale + form = Concrete01Form() qtbot.addWidget(form) assert form._epsc0.locale().decimalPoint() == "." diff --git a/tests/gui/test_path_ts_dialogs.py b/tests/gui/test_path_ts_dialogs.py index 1571fc3..5f1e132 100644 --- a/tests/gui/test_path_ts_dialogs.py +++ b/tests/gui/test_path_ts_dialogs.py @@ -8,17 +8,16 @@ import pytest pytest.importorskip("PySide6") -from otko.commands import ( # noqa: E402 +from otko.commands import ( AddLoadPatternCommand, AddTimeSeriesCommand, ) -from otko.core import ( # noqa: E402 - Node, +from otko.core import ( PathTimeSeries, Project, UniformExcitationPattern, ) -from otko.viewmodels import ProjectViewModel # noqa: E402 +from otko.viewmodels import ProjectViewModel # ─────────────── AddTimeSeriesCommand / AddLoadPatternCommand ────────────── @@ -28,9 +27,9 @@ def test_add_time_series_command_is_undoable(qtbot) -> None: # type: ignore[no- vm.new_project() ts = PathTimeSeries(id=1, name="GM", dt=0.01, factor=386.4, values=[0.1, 0.2]) vm.apply_command(AddTimeSeriesCommand(vm, ts)) - assert len(vm.project.time_series) == 1 # type: ignore[union-attr] + assert len(vm.project.time_series) == 1 # type: ignore[union-attr] vm.undo_stack.undo() - assert vm.project.time_series == [] # type: ignore[union-attr] + assert vm.project.time_series == [] # type: ignore[union-attr] vm.undo_stack.redo() assert vm.project.time_series[0].name == "GM" # type: ignore[union-attr] @@ -39,9 +38,14 @@ def test_add_time_series_command_is_undoable(qtbot) -> None: # type: ignore[no- def test_add_time_series_rejects_duplicate_id(qtbot) -> None: # type: ignore[no-untyped-def] vm = ProjectViewModel() vm.new_project() - vm.project.time_series.append(PathTimeSeries( # type: ignore[union-attr] - id=1, name="Existing", dt=0.01, values=[0.0], - )) + vm.project.time_series.append( + PathTimeSeries( # type: ignore[union-attr] + id=1, + name="Existing", + dt=0.01, + values=[0.0], + ) + ) ts2 = PathTimeSeries(id=1, name="Duplicate", dt=0.01, values=[0.0]) with pytest.raises(ValueError): vm.apply_command(AddTimeSeriesCommand(vm, ts2)) @@ -51,22 +55,31 @@ def test_add_time_series_rejects_duplicate_id(qtbot) -> None: # type: ignore[no def test_add_load_pattern_command_is_undoable(qtbot) -> None: # type: ignore[no-untyped-def] vm = ProjectViewModel() vm.new_project() - vm.project.time_series.append(PathTimeSeries( # type: ignore[union-attr] - id=1, name="GM", dt=0.01, values=[0.1, 0.2], - )) + vm.project.time_series.append( + PathTimeSeries( # type: ignore[union-attr] + id=1, + name="GM", + dt=0.01, + values=[0.1, 0.2], + ) + ) pat = UniformExcitationPattern( - id=1, name="EQ", direction=1, accel_series_id=1, + id=1, + name="EQ", + direction=1, + accel_series_id=1, ) vm.apply_command(AddLoadPatternCommand(vm, pat)) - assert len(vm.project.load_patterns) == 1 # type: ignore[union-attr] + assert len(vm.project.load_patterns) == 1 # type: ignore[union-attr] vm.undo_stack.undo() - assert vm.project.load_patterns == [] # type: ignore[union-attr] + assert vm.project.load_patterns == [] # type: ignore[union-attr] # ─────────────── PathTimeSeriesDialog ────────────── @pytest.mark.gui def test_path_ts_dialog_manual_entry(qtbot, tmp_path) -> None: # type: ignore[no-untyped-def] from otko.views.dialogs.path_time_series import PathTimeSeriesDialog + dlg = PathTimeSeriesDialog(next_ts_id=1) qtbot.addWidget(dlg) # Simulate a plain-values import by directly seeding the values @@ -87,20 +100,24 @@ def test_path_ts_dialog_manual_entry(qtbot, tmp_path) -> None: # type: ignore[n def test_path_ts_dialog_imports_peer(qtbot, tmp_path) -> None: # type: ignore[no-untyped-def] """_on_import_peer populates dt / npts from the header + value list.""" from otko.views.dialogs.path_time_series import PathTimeSeriesDialog + rec = tmp_path / "test.at2" - rec.write_text(textwrap.dedent("""\ + rec.write_text( + textwrap.dedent("""\ PEER PACIFIC EL CENTRO 1940 ACCELERATION IN G 3 0.025 NPTS, DT 0.01 -0.02 0.03 - """)) + """) + ) dlg = PathTimeSeriesDialog(next_ts_id=1) qtbot.addWidget(dlg) # Feed the file path directly through the parser + state-setter # logic. Simulating QFileDialog in a headless test is fragile; # calling _on_import_peer's internals is the stable path. from otko.services.peer_record import parse_peer_record + dt, npts, vals = parse_peer_record(rec) dlg._values = vals dlg._dt_spin.setValue(dt) @@ -114,6 +131,7 @@ def test_path_ts_dialog_imports_peer(qtbot, tmp_path) -> None: # type: ignore[n def test_uniform_excitation_dialog_requires_time_series(qtbot) -> None: # type: ignore[no-untyped-def] """With no TimeSeries defined, the dialog's picker is disabled.""" from otko.views.dialogs.uniform_excitation import UniformExcitationDialog + proj = Project() dlg = UniformExcitationDialog(project=proj, next_pattern_id=1) qtbot.addWidget(dlg) @@ -123,9 +141,12 @@ def test_uniform_excitation_dialog_requires_time_series(qtbot) -> None: # type: @pytest.mark.gui def test_uniform_excitation_dialog_builds_pattern(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.dialogs.uniform_excitation import UniformExcitationDialog - proj = Project(time_series=[ - PathTimeSeries(id=7, name="GM", dt=0.01, values=[0.0, 0.1]), - ]) + + proj = Project( + time_series=[ + PathTimeSeries(id=7, name="GM", dt=0.01, values=[0.0, 0.1]), + ] + ) dlg = UniformExcitationDialog(project=proj, next_pattern_id=2) qtbot.addWidget(dlg) # Pick direction = 1 (X) — already default. diff --git a/tests/gui/test_property_commands.py b/tests/gui/test_property_commands.py index 117c445..8a49e26 100644 --- a/tests/gui/test_property_commands.py +++ b/tests/gui/test_property_commands.py @@ -6,7 +6,7 @@ import pytest pytest.importorskip("PySide6") -from otko.commands import ( # noqa: E402 +from otko.commands import ( AddElementsCommand, AddMaterialsCommand, AddNodesCommand, @@ -16,7 +16,7 @@ from otko.commands import ( # noqa: E402 UpdateMaterialCommand, UpdateSectionCommand, ) -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticBeamColumn, ElasticSection, Node, @@ -24,24 +24,40 @@ from otko.core import ( # noqa: E402 Steel02, TrussElement, ) -from otko.viewmodels import ProjectViewModel # noqa: E402 +from otko.viewmodels import ProjectViewModel def _vm_with_steel() -> ProjectViewModel: vm = ProjectViewModel() vm.new_project() - vm.apply_command(AddNodesCommand(vm, [ - Node(id=1, coords=(0, 0, 0)), - Node(id=2, coords=(1, 0, 0)), - Node(id=3, coords=(2, 0, 0)), - ])) - vm.apply_command(AddMaterialsCommand(vm, [ - Steel01(id=1, name="S420", Fy=420e6, E0=200e9, b=0.01), - ])) - vm.apply_command(AddSectionsCommand(vm, [ - ElasticSection(id=1, name="Default", E=200e9, A=0.01, - Iz=8.33e-6, Iy=8.33e-6, G=80e9, J=1e-6), - ])) + vm.apply_command( + AddNodesCommand( + vm, + [ + Node(id=1, coords=(0, 0, 0)), + Node(id=2, coords=(1, 0, 0)), + Node(id=3, coords=(2, 0, 0)), + ], + ) + ) + vm.apply_command( + AddMaterialsCommand( + vm, + [ + Steel01(id=1, name="S420", Fy=420e6, E0=200e9, b=0.01), + ], + ) + ) + vm.apply_command( + AddSectionsCommand( + vm, + [ + ElasticSection( + id=1, name="Default", E=200e9, A=0.01, Iz=8.33e-6, Iy=8.33e-6, G=80e9, J=1e-6 + ), + ], + ) + ) return vm @@ -91,8 +107,7 @@ def test_update_unknown_id_raises(qtbot) -> None: # type: ignore[no-untyped-def @pytest.mark.gui def test_update_section_changes_inertia(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_with_steel() - new = ElasticSection(id=1, name="Updated", E=200e9, A=0.02, - Iz=2e-5, Iy=2e-5, G=80e9, J=2e-6) + new = ElasticSection(id=1, name="Updated", E=200e9, A=0.02, Iz=2e-5, Iy=2e-5, G=80e9, J=2e-6) vm.apply_command(UpdateSectionCommand(vm, new)) s = vm.project.section(1) assert s.A == 0.02 @@ -106,14 +121,23 @@ def test_update_section_changes_inertia(qtbot) -> None: # type: ignore[no-untyp def test_assign_section_to_frames(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_with_steel() # Add a second section and two frames. - vm.apply_command(AddSectionsCommand(vm, [ - ElasticSection(id=2, name="Big", E=200e9, A=0.05, - Iz=4e-5, Iy=4e-5, G=80e9, J=2e-6), - ])) - vm.apply_command(AddElementsCommand(vm, [ - ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), - ElasticBeamColumn(id=2, nodes=(2, 3), section_id=1), - ])) + vm.apply_command( + AddSectionsCommand( + vm, + [ + ElasticSection(id=2, name="Big", E=200e9, A=0.05, Iz=4e-5, Iy=4e-5, G=80e9, J=2e-6), + ], + ) + ) + vm.apply_command( + AddElementsCommand( + vm, + [ + ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), + ElasticBeamColumn(id=2, nodes=(2, 3), section_id=1), + ], + ) + ) vm.apply_command(AssignSectionCommand(vm, {1, 2}, section_id=2)) assert vm.project.element(1).section_id == 2 assert vm.project.element(2).section_id == 2 @@ -126,10 +150,15 @@ def test_assign_section_to_frames(qtbot) -> None: # type: ignore[no-untyped-def def test_assign_section_skips_truss_elements(qtbot) -> None: # type: ignore[no-untyped-def] """Truss has no section_id — command should silently skip it.""" vm = _vm_with_steel() - vm.apply_command(AddElementsCommand(vm, [ - TrussElement(id=1, nodes=(1, 2), area=1e-3, material_id=1), - ElasticBeamColumn(id=2, nodes=(2, 3), section_id=1), - ])) + vm.apply_command( + AddElementsCommand( + vm, + [ + TrussElement(id=1, nodes=(1, 2), area=1e-3, material_id=1), + ElasticBeamColumn(id=2, nodes=(2, 3), section_id=1), + ], + ) + ) vm.apply_command(AssignSectionCommand(vm, {1, 2}, section_id=1)) # Frame ok, truss unchanged (still material-based). assert vm.project.element(2).section_id == 1 @@ -140,13 +169,23 @@ def test_assign_section_skips_truss_elements(qtbot) -> None: # type: ignore[no- @pytest.mark.gui def test_assign_material_to_truss(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_with_steel() - vm.apply_command(AddMaterialsCommand(vm, [ - Steel02(id=2, name="S355", Fy=355e6, E0=210e9, b=0.005), - ])) - vm.apply_command(AddElementsCommand(vm, [ - TrussElement(id=1, nodes=(1, 2), area=1e-3, material_id=1), - TrussElement(id=2, nodes=(2, 3), area=1e-3, material_id=1), - ])) + vm.apply_command( + AddMaterialsCommand( + vm, + [ + Steel02(id=2, name="S355", Fy=355e6, E0=210e9, b=0.005), + ], + ) + ) + vm.apply_command( + AddElementsCommand( + vm, + [ + TrussElement(id=1, nodes=(1, 2), area=1e-3, material_id=1), + TrussElement(id=2, nodes=(2, 3), area=1e-3, material_id=1), + ], + ) + ) vm.apply_command(AssignMaterialCommand(vm, {1, 2}, material_id=2)) assert vm.project.element(1).material_id == 2 assert vm.project.element(2).material_id == 2 @@ -157,10 +196,15 @@ def test_assign_material_to_truss(qtbot) -> None: # type: ignore[no-untyped-def @pytest.mark.gui def test_assign_material_skips_frame_elements(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_with_steel() - vm.apply_command(AddElementsCommand(vm, [ - ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), - TrussElement(id=2, nodes=(2, 3), area=1e-3, material_id=1), - ])) + vm.apply_command( + AddElementsCommand( + vm, + [ + ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), + TrussElement(id=2, nodes=(2, 3), area=1e-3, material_id=1), + ], + ) + ) vm.apply_command(AssignMaterialCommand(vm, {1, 2}, material_id=1)) # Frame doesn't have material_id; only the truss is affected. assert vm.project.element(2).material_id == 1 diff --git a/tests/gui/test_property_forms.py b/tests/gui/test_property_forms.py index 7a5d74c..aed5ece 100644 --- a/tests/gui/test_property_forms.py +++ b/tests/gui/test_property_forms.py @@ -10,13 +10,10 @@ import pytest pytest.importorskip("PySide6") -from otko.core import ( # noqa: E402 - Concrete01, +from otko.core import ( Concrete02, ElasticIsotropic, - ElasticPP, ElasticSection, - ElasticUniaxial, Steel01, Steel02, ) @@ -37,8 +34,7 @@ def test_steel01_form_round_trip(qtbot) -> None: # type: ignore[no-untyped-def] def test_steel02_form_round_trip(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.dialogs.material_forms import Steel02Form - original = Steel02(id=3, name="S355", Fy=355e6, E0=210e9, b=0.005, - R0=18.0, cR1=0.925, cR2=0.15) + original = Steel02(id=3, name="S355", Fy=355e6, E0=210e9, b=0.005, R0=18.0, cR1=0.925, cR2=0.15) form = Steel02Form() form.populate(original) restored = form.read() @@ -50,8 +46,15 @@ def test_concrete02_form_round_trip(qtbot) -> None: # type: ignore[no-untyped-d from otko.views.dialogs.material_forms import Concrete02Form original = Concrete02( - id=2, name="C30", fpc=-30e6, epsc0=-0.002, fpcu=-15e6, epsU=-0.005, - ft=3e6, Ets=2e9, **{"lambda": 0.1}, + id=2, + name="C30", + fpc=-30e6, + epsc0=-0.002, + fpcu=-15e6, + epsU=-0.005, + ft=3e6, + Ets=2e9, + **{"lambda": 0.1}, ) form = Concrete02Form() form.populate(original) @@ -75,8 +78,14 @@ def test_elastic_section_form_round_trip(qtbot) -> None: # type: ignore[no-unty from otko.views.dialogs.section_forms import ElasticSectionForm original = ElasticSection( - id=11, name="W14x90", - E=200e9, A=0.017, Iz=4.16e-4, Iy=1.29e-4, G=80e9, J=2.04e-6, + id=11, + name="W14x90", + E=200e9, + A=0.017, + Iz=4.16e-4, + Iy=1.29e-4, + G=80e9, + J=2.04e-6, ) form = ElasticSectionForm() form.populate(original) @@ -88,10 +97,14 @@ def test_elastic_section_form_round_trip(qtbot) -> None: # type: ignore[no-unty def test_form_for_dispatches_by_type(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.dialogs.material_forms import ( Steel01Form, + ) + from otko.views.dialogs.material_forms import ( form_for as material_form_for, ) from otko.views.dialogs.section_forms import ( ElasticSectionForm, + ) + from otko.views.dialogs.section_forms import ( form_for as section_form_for, ) diff --git a/tests/gui/test_pushover_curve_units.py b/tests/gui/test_pushover_curve_units.py index 09c03b6..9aee331 100644 --- a/tests/gui/test_pushover_curve_units.py +++ b/tests/gui/test_pushover_curve_units.py @@ -7,9 +7,9 @@ import pytest pytest.importorskip("PySide6") -from otko.core import UnitSystem # noqa: E402 -from otko.services.results import PushoverResults # noqa: E402 -from otko.views.docks.pushover_curve import ( # noqa: E402 +from otko.core import UnitSystem +from otko.services.results import PushoverResults +from otko.views.docks.pushover_curve import ( PushoverCurveView, _is_rotation_dof, ) @@ -17,8 +17,11 @@ from otko.views.docks.pushover_curve import ( # noqa: E402 def _pushover(control_dof: int = 1) -> PushoverResults: return PushoverResults( - case_id=1, case_name="tst", n_steps=5, - control_node=2, control_dof=control_dof, + case_id=1, + case_name="tst", + n_steps=5, + control_node=2, + control_dof=control_dof, control_disp=np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0]), base_shear=np.array([0.0, 100.0, 200.0, 300.0, 400.0, 500.0]), ) @@ -34,10 +37,10 @@ def test_is_rotation_dof_ndf3() -> None: def test_is_rotation_dof_ndf6() -> None: """In a 3D/ndf=6 model, DOFs 4, 5, 6 are rotational.""" assert _is_rotation_dof(1, ndf=6) is False - assert _is_rotation_dof(3, ndf=6) is False # Uz in 3D - assert _is_rotation_dof(4, ndf=6) is True # Rx - assert _is_rotation_dof(5, ndf=6) is True # Ry - assert _is_rotation_dof(6, ndf=6) is True # Rz + assert _is_rotation_dof(3, ndf=6) is False # Uz in 3D + assert _is_rotation_dof(4, ndf=6) is True # Rx + assert _is_rotation_dof(5, ndf=6) is True # Ry + assert _is_rotation_dof(6, ndf=6) is True # Rz @pytest.mark.gui @@ -96,8 +99,7 @@ def test_no_auto_scaling_applied_to_values(qtbot) -> None: # type: ignore[no-un v.set_results(r) # Take the single line item that was added; the item's data # should match the input arrays point-for-point. - items = [it for it in v._plot.listDataItems() - if hasattr(it, "getData")] + items = [it for it in v._plot.listDataItems() if hasattr(it, "getData")] assert items, "Pushover curve has no plot items" xs, ys = items[0].getData() # The first line item is the actual data (reference line is second). diff --git a/tests/gui/test_run_analysis.py b/tests/gui/test_run_analysis.py index 0e40752..5281fd8 100644 --- a/tests/gui/test_run_analysis.py +++ b/tests/gui/test_run_analysis.py @@ -8,11 +8,11 @@ import pytest pytest.importorskip("PySide6") -from PySide6.QtCore import QObject, Signal # noqa: E402 +from PySide6.QtCore import QObject, Signal -from otko.core import LinearTimeSeries, PlainLoadPattern, Project, TransientCase # noqa: E402 -from otko.viewmodels import ProjectViewModel # noqa: E402 -from otko.views.dialogs.run_analysis import RunAnalysisDialog # noqa: E402 +from otko.core import LinearTimeSeries, PlainLoadPattern, TransientCase +from otko.viewmodels import ProjectViewModel +from otko.views.dialogs.run_analysis import RunAnalysisDialog class _FakeRunner(QObject): @@ -41,16 +41,18 @@ def test_run_dialog_applies_transient_damping_overrides(qtbot, tmp_path) -> None vm.new_project() vm.project.time_series.append(LinearTimeSeries(id=1, name="Ramp")) # type: ignore[union-attr] vm.project.load_patterns.append(PlainLoadPattern(id=1, name="P1", time_series_id=1)) # type: ignore[union-attr] - vm.project.analyses.append(TransientCase( # type: ignore[union-attr] - id=1, - name="EQ", - pattern_ids=[1], - dt=0.01, - n_steps=10, - rayleigh_alpha_m=0.1, - rayleigh_beta_k=0.002, - rayleigh_mode1_damping=0.02, - )) + vm.project.analyses.append( + TransientCase( # type: ignore[union-attr] + id=1, + name="EQ", + pattern_ids=[1], + dt=0.01, + n_steps=10, + rayleigh_alpha_m=0.1, + rayleigh_beta_k=0.002, + rayleigh_mode1_damping=0.02, + ) + ) vm._path = Path(tmp_path) / "demo.osmodel" # type: ignore[attr-defined] runner = _FakeRunner() diff --git a/tests/gui/test_section_extrusion.py b/tests/gui/test_section_extrusion.py index 449b24e..3f66eb4 100644 --- a/tests/gui/test_section_extrusion.py +++ b/tests/gui/test_section_extrusion.py @@ -6,7 +6,7 @@ import pytest pytest.importorskip("PySide6") -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticBeamColumn, ElasticSection, Node, @@ -18,18 +18,25 @@ def _frame_project() -> Project: # Rectangular section 0.3×0.5 m. b, h = 0.30, 0.50 A = b * h - Iz = b * h ** 3 / 12.0 - Iy = h * b ** 3 / 12.0 + Iz = b * h**3 / 12.0 + Iy = h * b**3 / 12.0 return Project( nodes=[ - Node(id=1, coords=(0.0, 0.0, 0.0), - restraint=(True, True, True, True, True, True)), + Node(id=1, coords=(0.0, 0.0, 0.0), restraint=(True, True, True, True, True, True)), Node(id=2, coords=(6.0, 0.0, 0.0)), ], - sections=[ElasticSection( - id=1, name="Rect", E=200e9, A=A, - Iz=Iz, Iy=Iy, G=80e9, J=1e-6, - )], + sections=[ + ElasticSection( + id=1, + name="Rect", + E=200e9, + A=A, + Iz=Iz, + Iy=Iy, + G=80e9, + J=1e-6, + ) + ], elements=[ ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), ], @@ -39,6 +46,7 @@ def _frame_project() -> Project: @pytest.mark.gui def test_toggle_creates_and_removes_extrusion_actor(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) canvas.show_project(_frame_project()) @@ -58,6 +66,7 @@ def test_extrusion_skips_elements_without_section(qtbot) -> None: # type: ignor """A truss element has no section — it must be skipped silently.""" from otko.core import ElasticUniaxial, TrussElement from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) p = Project( diff --git a/tests/gui/test_section_forms.py b/tests/gui/test_section_forms.py index 9e68a75..4747cec 100644 --- a/tests/gui/test_section_forms.py +++ b/tests/gui/test_section_forms.py @@ -7,14 +7,14 @@ import pytest pytest.importorskip("PySide6") -from otko.core import ( # noqa: E402 +from otko.core import ( AggregatorDOF, ElasticSection, FiberSection, RectangularPatch, SectionAggregator, ) -from otko.views.dialogs.section_forms import ( # noqa: E402 +from otko.views.dialogs.section_forms import ( FORM_REGISTRY, form_for, ) @@ -31,7 +31,14 @@ def test_section_aggregator_has_registered_form() -> None: @pytest.mark.gui def test_form_for_elastic_section_returns_editable_form(qtbot) -> None: # type: ignore[no-untyped-def] s = ElasticSection( - id=1, name="Rect", E=200e9, A=0.01, Iz=1e-5, Iy=1e-5, G=80e9, J=1e-6, + id=1, + name="Rect", + E=200e9, + A=0.01, + Iz=1e-5, + Iy=1e-5, + G=80e9, + J=1e-6, ) f = form_for(s) qtbot.addWidget(f) @@ -41,11 +48,19 @@ def test_form_for_elastic_section_returns_editable_form(qtbot) -> None: # type: @pytest.mark.gui def test_form_for_fiber_section_returns_summary(qtbot) -> None: # type: ignore[no-untyped-def] s = FiberSection( - id=1, name="RC", - patches=[RectangularPatch( - material_id=1, n_fib_y=4, n_fib_z=4, - y_i=-0.1, z_i=-0.15, y_j=0.1, z_j=0.15, - )], + id=1, + name="RC", + patches=[ + RectangularPatch( + material_id=1, + n_fib_y=4, + n_fib_z=4, + y_i=-0.1, + z_i=-0.15, + y_j=0.1, + z_j=0.15, + ) + ], ) f = form_for(s) qtbot.addWidget(f) @@ -59,7 +74,9 @@ def test_form_for_fiber_section_returns_summary(qtbot) -> None: # type: ignore[ @pytest.mark.gui def test_form_for_section_aggregator_returns_summary(qtbot) -> None: # type: ignore[no-untyped-def] s = SectionAggregator( - id=2, name="Agg", section_id=1, + id=2, + name="Agg", + section_id=1, pairings=[AggregatorDOF(material_id=3, dof="T")], ) f = form_for(s) @@ -71,10 +88,12 @@ def test_form_for_section_aggregator_returns_summary(qtbot) -> None: # type: ig def test_form_for_unknown_type_falls_back(qtbot) -> None: # type: ignore[no-untyped-def] """An unknown section type must NOT raise — the dialog gets a placeholder form so the whole UI doesn't go down.""" + class _Mystery: type = "NotRegistered" id = 99 name = "???" + f = form_for(_Mystery()) qtbot.addWidget(f) # Placeholder form exists; no exception raised. diff --git a/tests/gui/test_selection.py b/tests/gui/test_selection.py index c795493..365c03c 100644 --- a/tests/gui/test_selection.py +++ b/tests/gui/test_selection.py @@ -6,7 +6,7 @@ import pytest pytest.importorskip("PySide6") -from otko.views.canvas3d import SelectionState # noqa: E402 +from otko.views.canvas3d import SelectionState @pytest.mark.gui diff --git a/tests/gui/test_transforms.py b/tests/gui/test_transforms.py index 120cc64..fe13b70 100644 --- a/tests/gui/test_transforms.py +++ b/tests/gui/test_transforms.py @@ -6,34 +6,44 @@ import pytest pytest.importorskip("PySide6") -from otko.commands import ( # noqa: E402 +from otko.commands import ( AddElementsCommand, AddNodesCommand, MirrorCommand, MoveNodesCommand, ReplicateCommand, ) -from otko.core import Node, Steel01, TrussElement # noqa: E402 -from otko.viewmodels import ProjectViewModel # noqa: E402 +from otko.core import Node, Steel01, TrussElement +from otko.viewmodels import ProjectViewModel def _populated_vm() -> ProjectViewModel: """A small VM with 4 corner nodes + 4 truss elements forming a square.""" vm = ProjectViewModel() vm.new_project() - vm.apply_command(AddNodesCommand(vm, [ - Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), - Node(id=2, coords=(1, 0, 0)), - Node(id=3, coords=(1, 1, 0)), - Node(id=4, coords=(0, 1, 0)), - ])) + vm.apply_command( + AddNodesCommand( + vm, + [ + Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), + Node(id=2, coords=(1, 0, 0)), + Node(id=3, coords=(1, 1, 0)), + Node(id=4, coords=(0, 1, 0)), + ], + ) + ) vm.project.materials.append(Steel01(id=1, Fy=420e6, E0=200e9, b=0.01)) - vm.apply_command(AddElementsCommand(vm, [ - TrussElement(id=1, nodes=(1, 2), area=1e-3, material_id=1), - TrussElement(id=2, nodes=(2, 3), area=1e-3, material_id=1), - TrussElement(id=3, nodes=(3, 4), area=1e-3, material_id=1), - TrussElement(id=4, nodes=(4, 1), area=1e-3, material_id=1), - ])) + vm.apply_command( + AddElementsCommand( + vm, + [ + TrussElement(id=1, nodes=(1, 2), area=1e-3, material_id=1), + TrussElement(id=2, nodes=(2, 3), area=1e-3, material_id=1), + TrussElement(id=3, nodes=(3, 4), area=1e-3, material_id=1), + TrussElement(id=4, nodes=(4, 1), area=1e-3, material_id=1), + ], + ) + ) return vm @@ -44,7 +54,7 @@ def test_move_translates_in_place(qtbot) -> None: # type: ignore[no-untyped-def vm.apply_command(MoveNodesCommand(vm, {1, 2, 3, 4}, (10.0, 0.0, 0.0))) assert vm.project.node(1).coords == (10.0, 0.0, 0.0) assert vm.project.node(2).coords == (11.0, 0.0, 0.0) - assert len(vm.project.nodes) == 4 # no copies created + assert len(vm.project.nodes) == 4 # no copies created @pytest.mark.gui @@ -68,8 +78,9 @@ def test_move_preserves_restraint(qtbot) -> None: # type: ignore[no-untyped-def @pytest.mark.gui def test_replicate_creates_n_copies(qtbot) -> None: # type: ignore[no-untyped-def] vm = _populated_vm() - vm.apply_command(ReplicateCommand(vm, {1, 2, 3, 4}, {1, 2, 3, 4}, - offset=(0, 0, 3.0), n_copies=2)) + vm.apply_command( + ReplicateCommand(vm, {1, 2, 3, 4}, {1, 2, 3, 4}, offset=(0, 0, 3.0), n_copies=2) + ) # Original 4 + 2*4 copies = 12 nodes; original 4 + 2*4 elements = 12 elements assert len(vm.project.nodes) == 12 assert len(vm.project.elements) == 12 @@ -81,8 +92,9 @@ def test_replicate_creates_n_copies(qtbot) -> None: # type: ignore[no-untyped-d @pytest.mark.gui def test_replicate_undo_removes_only_copies(qtbot) -> None: # type: ignore[no-untyped-def] vm = _populated_vm() - vm.apply_command(ReplicateCommand(vm, {1, 2, 3, 4}, {1, 2, 3, 4}, - offset=(0, 0, 3.0), n_copies=2)) + vm.apply_command( + ReplicateCommand(vm, {1, 2, 3, 4}, {1, 2, 3, 4}, offset=(0, 0, 3.0), n_copies=2) + ) vm.undo_stack.undo() assert len(vm.project.nodes) == 4 assert len(vm.project.elements) == 4 @@ -94,8 +106,7 @@ def test_replicate_undo_removes_only_copies(qtbot) -> None: # type: ignore[no-u def test_replicate_skips_elements_with_unselected_endpoints(qtbot) -> None: # type: ignore[no-untyped-def] vm = _populated_vm() # Select only node 1 and 2; elements 1 (1↔2) is fully covered, others aren't. - vm.apply_command(ReplicateCommand(vm, {1, 2}, {1, 2, 3, 4}, - offset=(0, 0, 3.0), n_copies=1)) + vm.apply_command(ReplicateCommand(vm, {1, 2}, {1, 2, 3, 4}, offset=(0, 0, 3.0), n_copies=1)) # 2 new nodes + 1 new element (only element 1 was fully bracketed). assert len(vm.project.nodes) == 6 assert len(vm.project.elements) == 5 diff --git a/tests/gui/test_tree_multi_select.py b/tests/gui/test_tree_multi_select.py index 9b5e288..235ee7e 100644 --- a/tests/gui/test_tree_multi_select.py +++ b/tests/gui/test_tree_multi_select.py @@ -6,16 +6,16 @@ import pytest pytest.importorskip("PySide6") -from PySide6.QtCore import Qt # noqa: E402 -from PySide6.QtWidgets import QTreeWidget # noqa: E402 +from PySide6.QtWidgets import QTreeWidget -from otko.core import Node # noqa: E402 +from otko.core import Node @pytest.mark.gui def test_tree_is_in_extended_selection_mode(qtbot) -> None: # type: ignore[no-untyped-def] """Default QTreeWidget is single-select; we need ExtendedSelection.""" from otko.views.main_window import MainWindow + mw = MainWindow() qtbot.addWidget(mw) assert mw._tree.selectionMode() == QTreeWidget.SelectionMode.ExtendedSelection @@ -27,14 +27,17 @@ def test_tree_multi_select_syncs_canvas(qtbot) -> None: # type: ignore[no-untyp selection with both ids — the precondition for the Zero-Length Section dialog (exactly 2 joints).""" from otko.views.main_window import MainWindow + mw = MainWindow() qtbot.addWidget(mw) mw._vm.new_project(ndm=2, ndf=3) - mw._vm.project.nodes.extend([ - Node(id=1, coords=(0, 0, 0)), - Node(id=2, coords=(0, 0, 0)), - Node(id=3, coords=(1, 0, 0)), - ]) + mw._vm.project.nodes.extend( + [ + Node(id=1, coords=(0, 0, 0)), + Node(id=2, coords=(0, 0, 0)), + Node(id=3, coords=(1, 0, 0)), + ] + ) mw._refresh_tree(mw._vm.project) nodes_cat = mw._tree_categories["Nodes"] diff --git a/tests/gui/test_units_status_bar.py b/tests/gui/test_units_status_bar.py index b3c27e7..4eaaae1 100644 --- a/tests/gui/test_units_status_bar.py +++ b/tests/gui/test_units_status_bar.py @@ -6,12 +6,13 @@ import pytest pytest.importorskip("PySide6") -from otko.core import UnitSystem # noqa: E402 +from otko.core import UnitSystem @pytest.mark.gui def test_status_bar_combo_lists_all_unit_systems(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.main_window import MainWindow + mw = MainWindow() qtbot.addWidget(mw) assert mw._units_combo.count() == len(list(UnitSystem)) @@ -20,6 +21,7 @@ def test_status_bar_combo_lists_all_unit_systems(qtbot) -> None: # type: ignore @pytest.mark.gui def test_status_bar_combo_reflects_project_units(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.main_window import MainWindow + mw = MainWindow() qtbot.addWidget(mw) mw._vm.new_project() @@ -32,6 +34,7 @@ def test_status_bar_combo_reflects_project_units(qtbot) -> None: # type: ignore def test_status_bar_combo_write_updates_project(qtbot) -> None: # type: ignore[no-untyped-def] """Changing the combo writes through to project.meta.units.""" from otko.views.main_window import MainWindow + mw = MainWindow() qtbot.addWidget(mw) mw._vm.new_project() @@ -46,6 +49,7 @@ def test_status_bar_combo_write_updates_project(qtbot) -> None: # type: ignore[ def test_status_bar_combo_no_project_noop(qtbot) -> None: # type: ignore[no-untyped-def] """Before a project is loaded, changing the combo is a no-op.""" from otko.views.main_window import MainWindow + mw = MainWindow() qtbot.addWidget(mw) # No crash even with no project. diff --git a/tests/gui/test_update_element_fields.py b/tests/gui/test_update_element_fields.py index e684e5a..f83cd67 100644 --- a/tests/gui/test_update_element_fields.py +++ b/tests/gui/test_update_element_fields.py @@ -6,33 +6,48 @@ import pytest pytest.importorskip("PySide6") -from otko.commands import ( # noqa: E402 +from otko.commands import ( AddElementsCommand, AddMaterialsCommand, AddNodesCommand, UpdateElementFieldsCommand, ) -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticUniaxial, Node, TrussElement, ) -from otko.viewmodels import ProjectViewModel # noqa: E402 +from otko.viewmodels import ProjectViewModel def _vm_with_truss() -> ProjectViewModel: vm = ProjectViewModel() vm.new_project() - vm.apply_command(AddNodesCommand(vm, [ - Node(id=1, coords=(0, 0, 0)), - Node(id=2, coords=(3, 0, 0)), - ])) - vm.apply_command(AddMaterialsCommand(vm, [ - ElasticUniaxial(id=1, name="Steel", E=200e9), - ])) - vm.apply_command(AddElementsCommand(vm, [ - TrussElement(id=1, nodes=(1, 2), area=0.001, material_id=1), - ])) + vm.apply_command( + AddNodesCommand( + vm, + [ + Node(id=1, coords=(0, 0, 0)), + Node(id=2, coords=(3, 0, 0)), + ], + ) + ) + vm.apply_command( + AddMaterialsCommand( + vm, + [ + ElasticUniaxial(id=1, name="Steel", E=200e9), + ], + ) + ) + vm.apply_command( + AddElementsCommand( + vm, + [ + TrussElement(id=1, nodes=(1, 2), area=0.001, material_id=1), + ], + ) + ) return vm @@ -57,9 +72,13 @@ def test_update_area_is_undoable(qtbot) -> None: # type: ignore[no-untyped-def] def test_unknown_field_is_silently_ignored(qtbot) -> None: # type: ignore[no-untyped-def] """Fields not in the element's model are filtered out — no exception.""" vm = _vm_with_truss() - vm.apply_command(UpdateElementFieldsCommand( - vm, 1, {"section_id": 42}, # TrussElement has no section_id - )) + vm.apply_command( + UpdateElementFieldsCommand( + vm, + 1, + {"section_id": 42}, # TrussElement has no section_id + ) + ) # The element is unchanged. assert vm.project.element(1).area == pytest.approx(0.001) assert vm.project.element(1).material_id == 1 @@ -68,9 +87,13 @@ def test_unknown_field_is_silently_ignored(qtbot) -> None: # type: ignore[no-un @pytest.mark.gui def test_update_multiple_fields_at_once(qtbot) -> None: # type: ignore[no-untyped-def] vm = _vm_with_truss() - vm.apply_command(UpdateElementFieldsCommand( - vm, 1, {"area": 0.003, "rho": 7850.0}, - )) + vm.apply_command( + UpdateElementFieldsCommand( + vm, + 1, + {"area": 0.003, "rho": 7850.0}, + ) + ) el = vm.project.element(1) assert el.area == pytest.approx(0.003) assert el.rho == pytest.approx(7850.0) diff --git a/tests/gui/test_working_plane.py b/tests/gui/test_working_plane.py index b0fc2c8..012c1c0 100644 --- a/tests/gui/test_working_plane.py +++ b/tests/gui/test_working_plane.py @@ -7,7 +7,7 @@ import pytest pytest.importorskip("PySide6") -from otko.core import ( # noqa: E402 +from otko.core import ( CoordinateGridSystem, GridSystem, Project, @@ -33,6 +33,7 @@ def _multi_z_project() -> Project: @pytest.mark.gui def test_no_working_plane_returns_all_intersections(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) canvas.show_project(_multi_z_project()) @@ -45,6 +46,7 @@ def test_no_working_plane_returns_all_intersections(qtbot) -> None: # type: ign @pytest.mark.gui def test_xy_plane_filters_to_single_z_level(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) canvas.show_project(_multi_z_project()) @@ -61,6 +63,7 @@ def test_xy_plane_filters_to_single_z_level(qtbot) -> None: # type: ignore[no-u @pytest.mark.gui def test_xz_plane_filters_to_single_y_level(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) canvas.show_project(_multi_z_project()) @@ -74,6 +77,7 @@ def test_xz_plane_filters_to_single_y_level(qtbot) -> None: # type: ignore[no-u @pytest.mark.gui def test_yz_plane_filters_to_single_x_level(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) canvas.show_project(_multi_z_project()) @@ -87,6 +91,7 @@ def test_yz_plane_filters_to_single_x_level(qtbot) -> None: # type: ignore[no-u @pytest.mark.gui def test_clear_working_plane_restores_all(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) canvas.show_project(_multi_z_project()) @@ -100,6 +105,7 @@ def test_clear_working_plane_restores_all(qtbot) -> None: # type: ignore[no-unt @pytest.mark.gui def test_invalid_plane_raises(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) with pytest.raises(ValueError): @@ -112,10 +118,11 @@ def test_offset_with_no_matching_grid_returns_none(qtbot) -> None: # type: igno (e.g. typed a custom value off the grid), the snap list is empty so draw-click does nothing.""" from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) canvas.show_project(_multi_z_project()) - canvas.set_working_plane("XY", 1.234) # not in [0, 3, 6] + canvas.set_working_plane("XY", 1.234) # not in [0, 3, 6] assert canvas._grid_intersections_world() is None @@ -126,6 +133,7 @@ def test_switching_levels_rebuilds_grid_actors(qtbot) -> None: # type: ignore[n renders — ensuring the screen actually changes when the user switches Z=0 → Z=3.""" from otko.views.canvas3d.model_canvas import ModelCanvas + canvas = ModelCanvas() qtbot.addWidget(canvas) canvas.show_project(_multi_z_project()) @@ -133,7 +141,7 @@ def test_switching_levels_rebuilds_grid_actors(qtbot) -> None: # type: ignore[n # Without a working plane the renderer builds grid + vertical # connectors across all 3 Z-levels. Count aux actors as a proxy. base_count = len(canvas._renderer._aux_actors) - assert base_count >= 2 # at least one grid-lines + one dots actor + assert base_count >= 2 # at least one grid-lines + one dots actor # Pick an XY plane at Z=3 → render rebuilt. canvas.set_working_plane("XY", 3.0) @@ -162,6 +170,7 @@ def test_switching_levels_rebuilds_grid_actors(qtbot) -> None: # type: ignore[n @pytest.mark.gui def test_top_button_populates_level_combo_with_z_ordinates(qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.main_window import MainWindow + mw = MainWindow() qtbot.addWidget(mw) mw._vm.new_project() diff --git a/tests/integration/test_basic_truss.py b/tests/integration/test_basic_truss.py index db4b471..20be03c 100644 --- a/tests/integration/test_basic_truss.py +++ b/tests/integration/test_basic_truss.py @@ -11,7 +11,9 @@ import pytest pytest.importorskip("openseespy") -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def test_basic_truss_matches_opensees_tcl_reference() -> None: @@ -23,7 +25,7 @@ def test_basic_truss_matches_opensees_tcl_reference() -> None: a linear rescale of the Tcl model — any deviation would flag a real solver/translation issue. """ - from examples.basic_truss import build_basic_truss, IN_TO_M + from examples.basic_truss import IN_TO_M, build_basic_truss proj = build_basic_truss() runner = OpenSeesRunner(proj) @@ -43,6 +45,7 @@ def test_basic_truss_matches_opensees_tcl_reference() -> None: def test_basic_truss_round_trips(tmp_path) -> None: # type: ignore[no-untyped-def] """The example project must survive save/load without any information loss.""" from examples.basic_truss import build_basic_truss + from otko.services import load_project, save_project p = build_basic_truss() diff --git a/tests/integration/test_beam_quad_2d.py b/tests/integration/test_beam_quad_2d.py index 6ca31a5..4dc4a60 100644 --- a/tests/integration/test_beam_quad_2d.py +++ b/tests/integration/test_beam_quad_2d.py @@ -9,8 +9,10 @@ import pytest pytest.importorskip("openseespy") -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def test_beam_quad_2d_midspan_deflection(tmp_path) -> None: # type: ignore[no-untyped-def] @@ -23,6 +25,7 @@ def test_beam_quad_2d_midspan_deflection(tmp_path) -> None: # type: ignore[no-u _mid_top_node_id, build_beam_quad_2d, ) + proj = build_beam_quad_2d() proj.validate_references() @@ -49,10 +52,10 @@ def test_beam_quad_2d_midspan_deflection(tmp_path) -> None: # type: ignore[no-u # By symmetry, left and right support reactions must balance the two # 10-kip midspan loads (total -20 kip vertical). - assert reloaded.nodes[0].id == 1 # left pin + assert reloaded.nodes[0].id == 1 # left pin reaction_sum_fy = sum( result.node_reaction[nid][-1, 1] - for nid in (1, 17) # node 17 = (L, 0) = roller + for nid in (1, 17) # node 17 = (L, 0) = roller ) assert reaction_sum_fy == pytest.approx(20.0, abs=1e-6) @@ -69,6 +72,7 @@ def test_beam_quad_2d_free_vibration_chain(tmp_path) -> None: # type: ignore[no _mid_bottom_node_id, build_beam_quad_2d, ) + proj = build_beam_quad_2d() proj.validate_references() @@ -81,6 +85,7 @@ def test_beam_quad_2d_free_vibration_chain(tmp_path) -> None: # type: ignore[no r = OpenSeesRunner(reloaded).run(reloaded.analyses[1], results_dir=results_dir) import h5py + with h5py.File(r.h5_path) as f: t = f["time"][:] u = f[f"nodes/{_mid_bottom_node_id()}/disp"][:, 1] diff --git a/tests/integration/test_combinations.py b/tests/integration/test_combinations.py new file mode 100644 index 0000000..8ba5ca7 --- /dev/null +++ b/tests/integration/test_combinations.py @@ -0,0 +1,90 @@ +"""Load-combination integration: 1.2×D + 1.6×L by superposition == one combined run.""" + +from __future__ import annotations + +import math + +import pytest + +ops = pytest.importorskip("openseespy.opensees") # skip if OpenSeesPy not installed + +from otko.core import ( + ElasticBeamColumn, + ElasticSection, + LinearTimeSeries, + LoadCombination, + LoadCombinationItem, + NodalLoad, + Node, + PlainLoadPattern, + Project, + StaticCase, +) +from otko.services import OpenSeesRunner, evaluate_combination +from otko.services.results import StaticResults + +pytestmark = pytest.mark.slow + + +def _two_pattern_cantilever() -> Project: + elastic_mod = 200e9 + area = 0.01 + inertia = 8.333e-6 + return Project( + ndm=2, + ndf=3, + nodes=[ + Node( + id=1, + coords=(0.0, 0.0, 0.0), + restraint=(True, True, False, False, False, True), + ), + Node(id=2, coords=(5.0, 0.0, 0.0)), + ], + sections=[ElasticSection(id=1, E=elastic_mod, A=area, Iz=inertia)], + elements=[ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1)], + time_series=[LinearTimeSeries(id=1), LinearTimeSeries(id=2)], + load_patterns=[ + PlainLoadPattern( + id=1, + name="Dead", + time_series_id=1, + nodal_loads=[NodalLoad(node_id=2, forces=(0.0, -1000.0, 0.0, 0.0, 0.0, 0.0))], + ), + PlainLoadPattern( + id=2, + name="Live", + time_series_id=2, + nodal_loads=[NodalLoad(node_id=2, forces=(0.0, -500.0, 0.0, 0.0, 0.0, 0.0))], + ), + ], + analyses=[ + StaticCase(id=1, name="Dead", pattern_ids=[1]), + StaticCase(id=2, name="Live", pattern_ids=[2]), + ], + ) + + +def test_superposition_matches_combined_run() -> None: + """1.2*Dead + 1.6*Live via evaluate_combination == single StaticCase run.""" + proj = _two_pattern_cantilever() + dead = OpenSeesRunner(proj).run(proj.analyses[0]) + live = OpenSeesRunner(proj).run(proj.analyses[1]) + combo = LoadCombination( + id=1, + name="1.2D+1.6L", + kind="Linear", + items=[ + LoadCombinationItem(case_id=1, factor=1.2), + LoadCombinationItem(case_id=2, factor=1.6), + ], + ) + combined = evaluate_combination({1: dead, 2: live}, combo) + assert isinstance(combined, StaticResults) + # Single run with both patterns factored identically. + direct = OpenSeesRunner(proj).run( + StaticCase(id=3, name="direct", pattern_ids=[1, 2], pattern_factors={1: 1.2, 2: 1.6}) + ) + assert math.isclose( + combined.disp(node_id=2, dof=2), direct.disp(node_id=2, dof=2), rel_tol=1e-9 + ) diff --git a/tests/integration/test_concrete04_runner.py b/tests/integration/test_concrete04_runner.py index a3aa8e1..4293dea 100644 --- a/tests/integration/test_concrete04_runner.py +++ b/tests/integration/test_concrete04_runner.py @@ -18,7 +18,7 @@ import pytest pytest.importorskip("openseespy") -from otko.core import ( # noqa: E402 +from otko.core import ( Concrete04, FiberSection, ForceBeamColumn, @@ -32,24 +32,27 @@ from otko.core import ( # noqa: E402 StaticCase, UnitSystem, ) -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow + # ── Model constants ──────────────────────────────────────────────────────────── -L = 1.0 # column height [m] -B = H = 0.3 # cross-section dimensions [m] -A = B * H # section area [m²] +L = 1.0 # column height [m] +B = H = 0.3 # cross-section dimensions [m] +A = B * H # section area [m²] -FC = -30e6 # peak compressive strength [Pa] (negative) -EPSC0 = -0.002 # strain at peak strength (negative) -EPSCU = -0.005 # ultimate compressive strain (negative) -EC = 30e9 # initial tangent modulus [Pa] +FC = -30e6 # peak compressive strength [Pa] (negative) +EPSC0 = -0.002 # strain at peak strength (negative) +EPSCU = -0.005 # ultimate compressive strain (negative) +EC = 30e9 # initial tangent modulus [Pa] # Applied axial load: small enough (< 1 % of capacity) that the Popovics # curve is indistinguishable from its linear tangent at origin. -P_AXIAL = -1200.0 # N (downward → compressive) +P_AXIAL = -1200.0 # N (downward → compressive) # Analytical axial shortening: P * L / (Ec * A) -EXPECTED_UY = P_AXIAL * L / (EC * A) # ≈ -1.333e-7 m +EXPECTED_UY = P_AXIAL * L / (EC * A) # ≈ -1.333e-7 m def _build_project() -> Project: @@ -61,32 +64,45 @@ def _build_project() -> Project: ndm=2, ndf=3, nodes=[ - Node(id=1, name="Base", coords=(0.0, 0.0, 0.0), - restraint=(True, True, True, False, False, False)), - Node(id=2, name="Top", coords=(0.0, L, 0.0)), + Node( + id=1, + name="Base", + coords=(0.0, 0.0, 0.0), + restraint=(True, True, True, False, False, False), + ), + Node(id=2, name="Top", coords=(0.0, L, 0.0)), ], materials=[ Concrete04( - id=1, name="C30-Popovics", - fpc=FC, epsc0=EPSC0, epscu=EPSCU, Ec=EC, + id=1, + name="C30-Popovics", + fpc=FC, + epsc0=EPSC0, + epscu=EPSCU, + Ec=EC, ), ], sections=[ FiberSection( - id=1, name="RC-Fiber", + id=1, + name="RC-Fiber", patches=[ RectangularPatch( material_id=1, - n_fib_y=4, n_fib_z=4, - y_i=-H / 2, z_i=-B / 2, - y_j= H / 2, z_j= B / 2, + n_fib_y=4, + n_fib_z=4, + y_i=-H / 2, + z_i=-B / 2, + y_j=H / 2, + z_j=B / 2, ), ], ), ], elements=[ ForceBeamColumn( - id=1, name="Column", + id=1, + name="Column", nodes=(1, 2), section_id=1, integration_points=3, @@ -96,7 +112,9 @@ def _build_project() -> Project: time_series=[LinearTimeSeries(id=1, name="Ramp")], load_patterns=[ PlainLoadPattern( - id=1, name="Gravity", time_series_id=1, + id=1, + name="Gravity", + time_series_id=1, nodal_loads=[ NodalLoad(node_id=2, forces=(0.0, P_AXIAL, 0.0, 0.0, 0.0, 0.0)), ], @@ -104,7 +122,8 @@ def _build_project() -> Project: ], analyses=[ StaticCase( - id=1, name="Gravity", + id=1, + name="Gravity", pattern_ids=[1], n_steps=1, load_factor_increment=1.0, @@ -139,9 +158,14 @@ def test_concrete04_with_tension_does_not_raise() -> None: proj = _build_project() # Replace material with tensile-branch variant. proj.materials[0] = Concrete04( - id=1, name="C30-WithTension", - fpc=FC, epsc0=EPSC0, epscu=EPSCU, Ec=EC, - fct=3.0e6, et=1e-4, + id=1, + name="C30-WithTension", + fpc=FC, + epsc0=EPSC0, + epscu=EPSCU, + Ec=EC, + fct=3.0e6, + et=1e-4, ) case = proj.analyses[0] result = OpenSeesRunner(proj).run(case) diff --git a/tests/integration/test_dof_coverage.py b/tests/integration/test_dof_coverage.py index 087b461..7e9e37e 100644 --- a/tests/integration/test_dof_coverage.py +++ b/tests/integration/test_dof_coverage.py @@ -11,7 +11,7 @@ import pytest pytest.importorskip("openseespy") -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticUniaxial, LinearTimeSeries, NodalLoad, @@ -21,17 +21,18 @@ from otko.core import ( # noqa: E402 StaticCase, TrussElement, ) -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def _make_truss_project(ndf: int) -> Project: return Project( - ndm=2, ndf=ndf, + ndm=2, + ndf=ndf, nodes=[ - Node(id=1, coords=(0, 0, 0), - restraint=(True, True, False, False, False, False)), - Node(id=2, coords=(3, 0, 0), - restraint=(True, True, False, False, False, False)), + Node(id=1, coords=(0, 0, 0), restraint=(True, True, False, False, False, False)), + Node(id=2, coords=(3, 0, 0), restraint=(True, True, False, False, False, False)), Node(id=3, coords=(1.5, 2, 0)), ], materials=[ElasticUniaxial(id=1, E=200e9)], @@ -40,10 +41,13 @@ def _make_truss_project(ndf: int) -> Project: TrussElement(id=2, nodes=(2, 3), area=1e-3, material_id=1), ], time_series=[LinearTimeSeries(id=1, name="R")], - load_patterns=[PlainLoadPattern( - id=1, time_series_id=1, - nodal_loads=[NodalLoad(node_id=3, forces=(1e3, -5e3, 0, 0, 0, 0))], - )], + load_patterns=[ + PlainLoadPattern( + id=1, + time_series_id=1, + nodal_loads=[NodalLoad(node_id=3, forces=(1e3, -5e3, 0, 0, 0, 0))], + ) + ], analyses=[StaticCase(id=1, name="Static", pattern_ids=[1], n_steps=1)], ) diff --git a/tests/integration/test_eigen_two_storey_one_bay_frame.py b/tests/integration/test_eigen_two_storey_one_bay_frame.py index 5f76282..83cd67e 100644 --- a/tests/integration/test_eigen_two_storey_one_bay_frame.py +++ b/tests/integration/test_eigen_two_storey_one_bay_frame.py @@ -8,9 +8,11 @@ import pytest pytest.importorskip("openseespy") -from otko.core import ModalCase # noqa: E402 -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.core import ModalCase +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def _reload(proj, tmp_path): # type: ignore[no-untyped-def] diff --git a/tests/integration/test_eigen_two_storey_shear_frame.py b/tests/integration/test_eigen_two_storey_shear_frame.py index c0cc4db..793f98d 100644 --- a/tests/integration/test_eigen_two_storey_shear_frame.py +++ b/tests/integration/test_eigen_two_storey_shear_frame.py @@ -8,9 +8,11 @@ import pytest pytest.importorskip("openseespy") -from otko.core import ModalCase # noqa: E402 -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.core import ModalCase +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def _reload(proj, tmp_path): # type: ignore[no-untyped-def] diff --git a/tests/integration/test_elastic_frame.py b/tests/integration/test_elastic_frame.py index 54edec4..6f04163 100644 --- a/tests/integration/test_elastic_frame.py +++ b/tests/integration/test_elastic_frame.py @@ -8,9 +8,11 @@ import pytest pytest.importorskip("openseespy") -from otko.core import ModalCase, StaticCase # noqa: E402 -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.core import ModalCase, StaticCase +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow BASE_NODES = (1, 2, 3, 4) @@ -27,13 +29,17 @@ def _reload(proj, tmp_path): # type: ignore[no-untyped-def] def test_elastic_frame_gravity_reactions(tmp_path) -> None: # type: ignore[no-untyped-def] """ΣFy at base = total applied gravity (distributed w × beam × floors).""" from examples.elastic_frame import ( - BAY, LOAD_F1, LOAD_F2, LOAD_F3, N_BAYS, build_elastic_frame, + LOAD_F1, + LOAD_F2, + LOAD_F3, + N_BAYS, + build_elastic_frame, ) + proj = _reload(build_elastic_frame(), tmp_path) gravity_case = next( - c for c in proj.analyses - if isinstance(c, StaticCase) and c.name == "Gravity" + c for c in proj.analyses if isinstance(c, StaticCase) and c.name == "Gravity" ) r = OpenSeesRunner(proj).run(gravity_case) @@ -51,24 +57,32 @@ def test_elastic_frame_gravity_reactions(tmp_path) -> None: # type: ignore[no-u # By symmetry exterior-column reactions pair up, as do interior. assert r.node_reaction[1][-1, 1] == pytest.approx( - r.node_reaction[4][-1, 1], abs=1e-6, + r.node_reaction[4][-1, 1], + abs=1e-6, ) assert r.node_reaction[2][-1, 1] == pytest.approx( - r.node_reaction[3][-1, 1], abs=1e-6, + r.node_reaction[3][-1, 1], + abs=1e-6, ) def test_elastic_frame_gravity_plus_lateral_reactions(tmp_path) -> None: # type: ignore[no-untyped-def] """ΣFx at base must equal -(lateral applied) within PDelta tolerance.""" from examples.elastic_frame import ( - BAY, LOAD_F1, LOAD_F2, LOAD_F3, - N_BAYS, P_F1, P_F2, P_F3, build_elastic_frame, + LOAD_F1, + LOAD_F2, + LOAD_F3, + N_BAYS, + P_F1, + P_F2, + P_F3, + build_elastic_frame, ) + proj = _reload(build_elastic_frame(), tmp_path) combined = next( - c for c in proj.analyses - if isinstance(c, StaticCase) and c.name == "Gravity+Lateral" + c for c in proj.analyses if isinstance(c, StaticCase) and c.name == "Gravity+Lateral" ) r = OpenSeesRunner(proj).run(combined) @@ -87,6 +101,7 @@ def test_elastic_frame_gravity_plus_lateral_reactions(tmp_path) -> None: # type def test_elastic_frame_modal_periods(tmp_path) -> None: # type: ignore[no-untyped-def] """5-mode eigen analysis: first periods match the Tcl reference values.""" from examples.elastic_frame import build_elastic_frame + proj = _reload(build_elastic_frame(), tmp_path) modal = next(c for c in proj.analyses if isinstance(c, ModalCase)) @@ -103,7 +118,5 @@ def test_elastic_frame_modal_periods(tmp_path) -> None: # type: ignore[no-untyp # 0.1930, 0.1562, 0.130 s. Our solve nails these within 1.5%. expected = [1.040, 0.3526, 0.1930, 0.1562, 0.130] periods = [2.0 * math.pi / math.sqrt(v) for v in r.eigenvalues] - for i, (T, T_ref) in enumerate(zip(periods, expected), start=1): - assert T == pytest.approx(T_ref, rel=0.02), ( - f"T{i} = {T:.4f} s, reference {T_ref:.4f} s" - ) + for i, (T, T_ref) in enumerate(zip(periods, expected, strict=False), start=1): + assert pytest.approx(T_ref, rel=0.02) == T, f"T{i} = {T:.4f} s, reference {T_ref:.4f} s" diff --git a/tests/integration/test_ex1a_canti2d.py b/tests/integration/test_ex1a_canti2d.py index 660810a..a20d589 100644 --- a/tests/integration/test_ex1a_canti2d.py +++ b/tests/integration/test_ex1a_canti2d.py @@ -9,9 +9,11 @@ import pytest pytest.importorskip("openseespy") -from otko.core import PushoverCase, TransientCase # noqa: E402 -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.core import PushoverCase, TransientCase +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def _reload(proj, tmp_path): # type: ignore[no-untyped-def] diff --git a/tests/integration/test_ex1a_canti2d_eq.py b/tests/integration/test_ex1a_canti2d_eq.py index 246af57..264d30c 100644 --- a/tests/integration/test_ex1a_canti2d_eq.py +++ b/tests/integration/test_ex1a_canti2d_eq.py @@ -9,8 +9,10 @@ import pytest pytest.importorskip("openseespy") -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def test_ex1a_canti2d_eq_runs_and_oscillates(tmp_path) -> None: # type: ignore[no-untyped-def] diff --git a/tests/integration/test_ex1b_portal2d.py b/tests/integration/test_ex1b_portal2d.py index 8e3f2a4..f47bdd2 100644 --- a/tests/integration/test_ex1b_portal2d.py +++ b/tests/integration/test_ex1b_portal2d.py @@ -9,9 +9,11 @@ import pytest pytest.importorskip("openseespy") -from otko.core import PushoverCase, TransientCase # noqa: E402 -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.core import PushoverCase, TransientCase +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def _reload(proj, tmp_path): # type: ignore[no-untyped-def] diff --git a/tests/integration/test_ex2a_canti2d_elastic_element.py b/tests/integration/test_ex2a_canti2d_elastic_element.py index 5a79a65..71bfb6d 100644 --- a/tests/integration/test_ex2a_canti2d_elastic_element.py +++ b/tests/integration/test_ex2a_canti2d_elastic_element.py @@ -9,9 +9,11 @@ import pytest pytest.importorskip("openseespy") -from otko.core import PushoverCase, TransientCase # noqa: E402 -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.core import PushoverCase, TransientCase +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def _reload(proj, tmp_path): # type: ignore[no-untyped-def] diff --git a/tests/integration/test_ex2b_canti2d_inelastic_section.py b/tests/integration/test_ex2b_canti2d_inelastic_section.py index 4afae36..3766581 100644 --- a/tests/integration/test_ex2b_canti2d_inelastic_section.py +++ b/tests/integration/test_ex2b_canti2d_inelastic_section.py @@ -9,9 +9,11 @@ import pytest pytest.importorskip("openseespy") -from otko.core import PushoverCase, TransientCase # noqa: E402 -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.core import PushoverCase, TransientCase +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def _reload(proj, tmp_path): # type: ignore[no-untyped-def] diff --git a/tests/integration/test_ex2c_canti2d_inelastic_fiber_section.py b/tests/integration/test_ex2c_canti2d_inelastic_fiber_section.py index a9caf9c..1cae060 100644 --- a/tests/integration/test_ex2c_canti2d_inelastic_fiber_section.py +++ b/tests/integration/test_ex2c_canti2d_inelastic_fiber_section.py @@ -9,9 +9,11 @@ import pytest pytest.importorskip("openseespy") -from otko.core import PushoverCase, TransientCase # noqa: E402 -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.core import PushoverCase, TransientCase +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def _reload(proj, tmp_path): # type: ignore[no-untyped-def] diff --git a/tests/integration/test_ex3_canti2d_variants.py b/tests/integration/test_ex3_canti2d_variants.py index 273610d..2486519 100644 --- a/tests/integration/test_ex3_canti2d_variants.py +++ b/tests/integration/test_ex3_canti2d_variants.py @@ -9,9 +9,11 @@ import pytest pytest.importorskip("openseespy") -from otko.core import PushoverCase, TransientCase # noqa: E402 -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.core import PushoverCase, TransientCase +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def _reload(proj, tmp_path, stem: str): # type: ignore[no-untyped-def] @@ -27,10 +29,16 @@ def _reload(proj, tmp_path, stem: str): # type: ignore[no-untyped-def] [ ("build_ex3_canti2d_elastic_element", "examples.ex3_canti2d_elastic_element", False), ("build_ex3_canti2d_inelastic_section", "examples.ex3_canti2d_inelastic_section", True), - ("build_ex3_canti2d_inelastic_fiber_section", "examples.ex3_canti2d_inelastic_fiber_section", True), + ( + "build_ex3_canti2d_inelastic_fiber_section", + "examples.ex3_canti2d_inelastic_fiber_section", + True, + ), ], ) -def test_ex3_variant_pushover_runs(tmp_path, builder_name: str, module_name: str, nonlinear: bool) -> None: # type: ignore[no-untyped-def] +def test_ex3_variant_pushover_runs( + tmp_path, builder_name: str, module_name: str, nonlinear: bool +) -> None: # type: ignore[no-untyped-def] mod = __import__(module_name, fromlist=[builder_name, "PUSH_STEP", "PUSH_TARGET"]) proj = _reload(getattr(mod, builder_name)(), tmp_path, builder_name) push_case = next(c for c in proj.analyses if isinstance(c, PushoverCase)) @@ -41,8 +49,12 @@ def test_ex3_variant_pushover_runs(tmp_path, builder_name: str, module_name: str assert result.control_disp[-1] == pytest.approx(mod.PUSH_TARGET, rel=1e-6) assert max(result.base_shear) > 0.0 - early = (result.base_shear[5] - result.base_shear[0]) / (result.control_disp[5] - result.control_disp[0]) - late = (result.base_shear[-1] - result.base_shear[-6]) / (result.control_disp[-1] - result.control_disp[-6]) + early = (result.base_shear[5] - result.base_shear[0]) / ( + result.control_disp[5] - result.control_disp[0] + ) + late = (result.base_shear[-1] - result.base_shear[-6]) / ( + result.control_disp[-1] - result.control_disp[-6] + ) if nonlinear: assert early > 2.0 * late else: @@ -54,7 +66,10 @@ def test_ex3_variant_pushover_runs(tmp_path, builder_name: str, module_name: str [ ("build_ex3_canti2d_elastic_element", "examples.ex3_canti2d_elastic_element"), ("build_ex3_canti2d_inelastic_section", "examples.ex3_canti2d_inelastic_section"), - ("build_ex3_canti2d_inelastic_fiber_section", "examples.ex3_canti2d_inelastic_fiber_section"), + ( + "build_ex3_canti2d_inelastic_fiber_section", + "examples.ex3_canti2d_inelastic_fiber_section", + ), ], ) def test_ex3_variant_earthquake_runs(tmp_path, builder_name: str, module_name: str) -> None: # type: ignore[no-untyped-def] diff --git a/tests/integration/test_ex4_portal2d_variants.py b/tests/integration/test_ex4_portal2d_variants.py index 875942f..d4943cb 100644 --- a/tests/integration/test_ex4_portal2d_variants.py +++ b/tests/integration/test_ex4_portal2d_variants.py @@ -9,9 +9,11 @@ import pytest pytest.importorskip("openseespy") -from otko.core import PushoverCase, TransientCase # noqa: E402 -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.core import PushoverCase, TransientCase +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def _reload(proj, tmp_path, stem: str): # type: ignore[no-untyped-def] @@ -27,10 +29,16 @@ def _reload(proj, tmp_path, stem: str): # type: ignore[no-untyped-def] [ ("build_ex4_portal2d_elastic_element", "examples.ex4_portal2d_elastic_element", False), ("build_ex4_portal2d_inelastic_section", "examples.ex4_portal2d_inelastic_section", True), - ("build_ex4_portal2d_inelastic_fiber_section", "examples.ex4_portal2d_inelastic_fiber_section", True), + ( + "build_ex4_portal2d_inelastic_fiber_section", + "examples.ex4_portal2d_inelastic_fiber_section", + True, + ), ], ) -def test_ex4_variant_pushover_runs(tmp_path, builder_name: str, module_name: str, nonlinear: bool) -> None: # type: ignore[no-untyped-def] +def test_ex4_variant_pushover_runs( + tmp_path, builder_name: str, module_name: str, nonlinear: bool +) -> None: # type: ignore[no-untyped-def] mod = __import__(module_name, fromlist=[builder_name, "PUSH_STEP", "PUSH_TARGET"]) proj = _reload(getattr(mod, builder_name)(), tmp_path, builder_name) push_case = next(c for c in proj.analyses if isinstance(c, PushoverCase)) @@ -45,8 +53,12 @@ def test_ex4_variant_pushover_runs(tmp_path, builder_name: str, module_name: str assert result.control_disp[-1] == pytest.approx(mod.PUSH_TARGET, abs=5e-3) assert max(result.base_shear) > 0.0 - early = (result.base_shear[5] - result.base_shear[0]) / (result.control_disp[5] - result.control_disp[0]) - late = (result.base_shear[-1] - result.base_shear[-6]) / (result.control_disp[-1] - result.control_disp[-6]) + early = (result.base_shear[5] - result.base_shear[0]) / ( + result.control_disp[5] - result.control_disp[0] + ) + late = (result.base_shear[-1] - result.base_shear[-6]) / ( + result.control_disp[-1] - result.control_disp[-6] + ) if nonlinear: assert early > 1.25 * late else: @@ -58,7 +70,10 @@ def test_ex4_variant_pushover_runs(tmp_path, builder_name: str, module_name: str [ ("build_ex4_portal2d_elastic_element", "examples.ex4_portal2d_elastic_element"), ("build_ex4_portal2d_inelastic_section", "examples.ex4_portal2d_inelastic_section"), - ("build_ex4_portal2d_inelastic_fiber_section", "examples.ex4_portal2d_inelastic_fiber_section"), + ( + "build_ex4_portal2d_inelastic_fiber_section", + "examples.ex4_portal2d_inelastic_fiber_section", + ), ], ) def test_ex4_variant_sine_runs(tmp_path, builder_name: str, module_name: str) -> None: # type: ignore[no-untyped-def] diff --git a/tests/integration/test_material_tester.py b/tests/integration/test_material_tester.py index 0711df4..6463b21 100644 --- a/tests/integration/test_material_tester.py +++ b/tests/integration/test_material_tester.py @@ -34,6 +34,9 @@ from otko.services.material_tester import ( test_uniaxial_material, ) +pytestmark = pytest.mark.slow + + # ---- helpers --------------------------------------------------------------- @@ -45,7 +48,8 @@ def _simple_cantilever() -> Project: ndf=3, nodes=[ Node( - id=1, name="Base", + id=1, + name="Base", coords=(0.0, 0.0, 0.0), # 2D-frame DOF mapping: (Ux, Uy, Uz, Rx, Ry, Rz) -> runner uses (0,1,5). # Fixed base: Ux=True, Uy=True, Rz=True (index 5). @@ -61,17 +65,25 @@ def _simple_cantilever() -> Project: time_series=[LinearTimeSeries(id=1)], load_patterns=[ PlainLoadPattern( - id=1, time_series_id=1, + id=1, + time_series_id=1, # Downward tip load (Uy direction). nodal_loads=[NodalLoad(node_id=2, forces=(0.0, -1.0e4, 0.0, 0.0, 0.0, 0.0))], ), ], analyses=[ StaticCase( - id=1, pattern_ids=[1], n_steps=1, load_factor_increment=1.0, - system="BandGeneral", constraints="Plain", - integrator="LoadControl", algorithm="Newton", - test="NormDispIncr", tolerance=1e-8, max_iter=10, + id=1, + pattern_ids=[1], + n_steps=1, + load_factor_increment=1.0, + system="BandGeneral", + constraints="Plain", + integrator="LoadControl", + algorithm="Newton", + test="NormDispIncr", + tolerance=1e-8, + max_iter=10, ), ], ) @@ -110,8 +122,8 @@ def test_elastic_uniaxial_monotonic_stress_strain() -> None: def test_elastic_pp_compressive_plateau() -> None: """ElasticPP: stress is exactly -Fy for all strains past compressive yield.""" e_mod = 200e9 - epsy = 1.25e-3 # yield strain in tension - fy = e_mod * epsy # implied yield stress = 250 MPa + epsy = 1.25e-3 # yield strain in tension + fy = e_mod * epsy # implied yield stress = 250 MPa mat = ElasticPP(id=1, E=e_mod, epsy_pos=epsy) protocol = LoadProtocol( @@ -129,9 +141,9 @@ def test_elastic_pp_compressive_plateau() -> None: assert len(past_yield) > 0, "no post-yield data points found" for s, sig in past_yield: - assert sig == pytest.approx(-fy, rel=1e-6), ( - f"plateau broken at strain={s:.4g}: got {sig:.4g}, expected {-fy:.4g}" - ) + assert sig == pytest.approx( + -fy, rel=1e-6 + ), f"plateau broken at strain={s:.4g}: got {sig:.4g}, expected {-fy:.4g}" # ---- Steel01 cyclic energy ------------------------------------------------- @@ -147,9 +159,9 @@ def test_steel01_cyclic_hysteresis_energy() -> None: fy = 250e6 e0 = 200e9 b = 0.0 - ey = fy / e0 # = 1.25e-3 - ea = 5.0 * ey # = 6.25e-3 - n = 100 # steps per branch + ey = fy / e0 # = 1.25e-3 + ea = 5.0 * ey # = 6.25e-3 + n = 100 # steps per branch mat = Steel01(id=1, Fy=fy, E0=e0, b=b) protocol = LoadProtocol( @@ -162,30 +174,28 @@ def test_steel01_cyclic_hysteresis_energy() -> None: result = test_uniaxial_material(mat, protocol) # Theoretical energy per stable cycle (EPP closed-form) - e_ref = 4.0 * fy * (ea - ey) # = 5 000 000 J/m^3 + e_ref = 4.0 * fy * (ea - ey) # = 5 000 000 J/m^3 - pts_per_cycle = 3 * n # = 300 (three branches per cycle) + pts_per_cycle = 3 * n # = 300 (three branches per cycle) total_pts = len(result.strain) assert total_pts == 3 * pts_per_cycle, f"expected 900 points, got {total_pts}" - for i_cycle in [1, 2]: # stable cycles 1 and 2 (0-indexed); closed loops + for i_cycle in [1, 2]: # stable cycles 1 and 2 (0-indexed); closed loops # Include the last point of the preceding cycle as the opening vertex # so the integration path is a closed loop. lo = i_cycle * pts_per_cycle - 1 - hi = (i_cycle + 1) * pts_per_cycle # Python slice: exclusive upper bound + hi = (i_cycle + 1) * pts_per_cycle # Python slice: exclusive upper bound strain_loop = result.strain[lo:hi] stress_loop = result.stress[lo:hi] assert len(strain_loop) == pts_per_cycle + 1 # 301 points # Trapezoidal area of closed stress-strain loop = dissipated energy. e_num = sum( - 0.5 * (stress_loop[j] + stress_loop[j + 1]) - * (strain_loop[j + 1] - strain_loop[j]) + 0.5 * (stress_loop[j] + stress_loop[j + 1]) * (strain_loop[j + 1] - strain_loop[j]) for j in range(len(strain_loop) - 1) ) assert abs(e_num) == pytest.approx(e_ref, rel=0.01), ( - f"cycle {i_cycle + 1}: numerical energy {abs(e_num):.4g} " - f"vs reference {e_ref:.4g}" + f"cycle {i_cycle + 1}: numerical energy {abs(e_num):.4g} " f"vs reference {e_ref:.4g}" ) @@ -207,7 +217,7 @@ def test_concrete04_monotonic_popovics_envelope() -> None: epsc0 = -0.002 epscu = -0.005 ec = 30e9 - n_steps = 200 # enough resolution to detect a kink clearly + n_steps = 200 # enough resolution to detect a kink clearly mat = Concrete04(id=1, fpc=fpc, epsc0=epsc0, epscu=epscu, Ec=ec) protocol = LoadProtocol( @@ -243,23 +253,21 @@ def test_concrete04_monotonic_popovics_envelope() -> None: ) # C1 continuity at peak: tangent slope ~ 0 from both sides. - d_eps = strain[peak_idx] - strain[peak_idx - 1] # negative step size + d_eps = strain[peak_idx] - strain[peak_idx - 1] # negative step size slope_before = (stress[peak_idx] - stress[peak_idx - 1]) / d_eps slope_after = (stress[peak_idx + 1] - stress[peak_idx]) / ( strain[peak_idx + 1] - strain[peak_idx] ) # Both slopes must be near zero (Popovics curve is C1 at the peak). - assert abs(slope_before) / ec < 0.05, ( - f"slope before peak too large: {slope_before / ec:.4f} x Ec" - ) - assert abs(slope_after) / ec < 0.05, ( - f"slope after peak too large: {slope_after / ec:.4f} x Ec" - ) + assert ( + abs(slope_before) / ec < 0.05 + ), f"slope before peak too large: {slope_before / ec:.4f} x Ec" + assert abs(slope_after) / ec < 0.05, f"slope after peak too large: {slope_after / ec:.4f} x Ec" # No kink: slope change at the peak must be smooth (< 5% of Ec). - assert abs(slope_before - slope_after) / ec < 0.05, ( - f"kink detected at peak: delta_slope = {abs(slope_before - slope_after) / ec:.4f} x Ec" - ) + assert ( + abs(slope_before - slope_after) / ec < 0.05 + ), f"kink detected at peak: delta_slope = {abs(slope_before - slope_after) / ec:.4f} x Ec" # ---- state-cleanup proof --------------------------------------------------- @@ -281,12 +289,8 @@ def test_state_cleanup_ten_consecutive_calls() -> None: ref_strain = results[0].strain ref_stress = results[0].stress for i, r in enumerate(results[1:], start=1): - assert r.strain == pytest.approx(ref_strain, rel=1e-9), ( - f"strain diverged on call {i + 1}" - ) - assert r.stress == pytest.approx(ref_stress, rel=1e-9), ( - f"stress diverged on call {i + 1}" - ) + assert r.strain == pytest.approx(ref_strain, rel=1e-9), f"strain diverged on call {i + 1}" + assert r.stress == pytest.approx(ref_stress, rel=1e-9), f"stress diverged on call {i + 1}" # ---- interleave test ------------------------------------------------------- @@ -323,6 +327,6 @@ def test_interleave_with_runner_analysis() -> None: # Uy at node 2 (DOF 1 in 0-indexed = DOF 2 in 1-indexed) must be identical. uy1 = float(result1.node_disp[2][0, 1]) uy2 = float(result2.node_disp[2][0, 1]) - assert uy1 == pytest.approx(uy2, rel=1e-9), ( - f"runner Uy changed after interleaved material test: {uy1} vs {uy2}" - ) + assert uy1 == pytest.approx( + uy2, rel=1e-9 + ), f"runner Uy changed after interleaved material test: {uy1} vs {uy2}" diff --git a/tests/integration/test_moment_curvature.py b/tests/integration/test_moment_curvature.py index 8d17046..12439d4 100644 --- a/tests/integration/test_moment_curvature.py +++ b/tests/integration/test_moment_curvature.py @@ -8,13 +8,11 @@ behaviour is exercised by the Phase 9 pushover tests. from __future__ import annotations -import math - import pytest pytest.importorskip("openseespy") -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticUniaxial, FiberSection, LinearTimeSeries, @@ -26,7 +24,9 @@ from otko.core import ( # noqa: E402 StaticCase, ZeroLengthSectionElement, ) -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def _moment_curvature_project(moment: float) -> Project: @@ -36,32 +36,44 @@ def _moment_curvature_project(moment: float) -> Project: at node 2's DOF 3 (Rz). With a linear-elastic fibre material the curvature should be ``moment / (E·I)``. """ - E = 30000.0 # Elastic modulus - b, h = 10.0, 20.0 # width × depth (in) + E = 30000.0 # Elastic modulus + b, h = 10.0, 20.0 # width × depth (in) return Project( - ndm=2, ndf=3, + ndm=2, + ndf=3, nodes=[ - Node(id=1, coords=(0, 0, 0), - restraint=(True, True, False, False, False, True)), - Node(id=2, coords=(0, 0, 0), - restraint=(False, True, False, False, False, False)), + Node(id=1, coords=(0, 0, 0), restraint=(True, True, False, False, False, True)), + Node(id=2, coords=(0, 0, 0), restraint=(False, True, False, False, False, False)), ], materials=[ElasticUniaxial(id=1, name="Elastic", E=E)], - sections=[FiberSection( - id=1, name="Rect", patches=[RectangularPatch( - material_id=1, n_fib_y=20, n_fib_z=1, - y_i=-h / 2, z_i=-b / 2, y_j=h / 2, z_j=b / 2, - )], - )], + sections=[ + FiberSection( + id=1, + name="Rect", + patches=[ + RectangularPatch( + material_id=1, + n_fib_y=20, + n_fib_z=1, + y_i=-h / 2, + z_i=-b / 2, + y_j=h / 2, + z_j=b / 2, + ) + ], + ) + ], elements=[ZeroLengthSectionElement(id=1, nodes=(1, 2), section_id=1)], time_series=[LinearTimeSeries(id=1, name="R")], - load_patterns=[PlainLoadPattern( - id=1, time_series_id=1, - # NodalLoad.forces = (Fx, Fy, Fz, Mx, My, Mz). Moment around - # z (= curvature driver in 2D) goes into index 5, not 2. - nodal_loads=[NodalLoad(node_id=2, - forces=(0, 0, 0, 0, 0, moment))], - )], + load_patterns=[ + PlainLoadPattern( + id=1, + time_series_id=1, + # NodalLoad.forces = (Fx, Fy, Fz, Mx, My, Mz). Moment around + # z (= curvature driver in 2D) goes into index 5, not 2. + nodal_loads=[NodalLoad(node_id=2, forces=(0, 0, 0, 0, 0, moment))], + ) + ], analyses=[StaticCase(id=1, name="MK", pattern_ids=[1], n_steps=1)], ) @@ -72,16 +84,16 @@ def test_zero_length_section_elastic_curvature_matches_closed_form() -> None: M = 500.0 E = 30000.0 b, h = 10.0, 20.0 - I = b * h ** 3 / 12.0 + I = b * h**3 / 12.0 expected_kappa = M / (E * I) proj = _moment_curvature_project(moment=M) result = OpenSeesRunner(proj).run(proj.analyses[0]) # Rz at node 2 IS the curvature for a zero-length section. ux, uy, rz = result.node_disp[2][-1] - assert rz == pytest.approx(expected_kappa, rel=5e-3), ( - f"κ = {rz:.6e}, expected {expected_kappa:.6e}" - ) + assert rz == pytest.approx( + expected_kappa, rel=5e-3 + ), f"κ = {rz:.6e}, expected {expected_kappa:.6e}" def test_pushover_drives_rotation_for_moment_curvature() -> None: @@ -94,34 +106,39 @@ def test_pushover_drives_rotation_for_moment_curvature() -> None: straight line through the origin with slope E·I. """ from otko.core import PushoverCase + E = 30000.0 b, h = 10.0, 20.0 - I = b * h ** 3 / 12.0 + I = b * h**3 / 12.0 target_kappa = 1e-5 steps = 20 # PushoverCase with DisplacementControl scales the load pattern — # needs a *non-zero* reference moment at the control DOF. proj = _moment_curvature_project(moment=1.0) - proj.analyses = [PushoverCase( - id=1, name="MK-push", - pattern_ids=[1], - control_node=2, control_dof=3, # DOF 3 = Rz - target_disp=target_kappa, # "displacement" == curvature here - step_size=target_kappa / steps, - base_nodes=[1], - )] + proj.analyses = [ + PushoverCase( + id=1, + name="MK-push", + pattern_ids=[1], + control_node=2, + control_dof=3, # DOF 3 = Rz + target_disp=target_kappa, # "displacement" == curvature here + step_size=target_kappa / steps, + base_nodes=[1], + ) + ] result = OpenSeesRunner(proj).run(proj.analyses[0]) # Every (κ, M) point must satisfy M = E·I·κ (1 % tolerance allows # for the ~20-fibre discretisation of the rectangular section). - for kappa, moment in zip(result.control_disp, result.base_shear): + for kappa, moment in zip(result.control_disp, result.base_shear, strict=False): if abs(kappa) < 1e-12: continue expected_M = E * I * kappa - assert moment == pytest.approx(expected_M, rel=1e-2), ( - f"at κ={kappa:.3e}: M={moment:.3e}, expected {expected_M:.3e}" - ) + assert moment == pytest.approx( + expected_M, rel=1e-2 + ), f"at κ={kappa:.3e}: M={moment:.3e}, expected {expected_M:.3e}" # Terminal curvature must reach the target. assert result.control_disp[-1] == pytest.approx(target_kappa, rel=1e-3) @@ -145,6 +162,7 @@ def test_moment_curvature_with_constant_axial_preload() -> None: Steel01, StraightLayer, ) + colWidth = 15.0 colDepth = 24.0 cover = 1.5 @@ -153,58 +171,104 @@ def test_moment_curvature_with_constant_axial_preload() -> None: z1 = colWidth / 2 proj = Project( - ndm=2, ndf=3, + ndm=2, + ndf=3, nodes=[ - Node(id=1, coords=(0, 0, 0), - restraint=(True, True, False, False, False, True)), - Node(id=2, coords=(0, 0, 0), - restraint=(False, True, False, False, False, False)), + Node(id=1, coords=(0, 0, 0), restraint=(True, True, False, False, False, True)), + Node(id=2, coords=(0, 0, 0), restraint=(False, True, False, False, False, False)), ], materials=[ - Concrete01(id=1, name="Core", - fpc=-6.0, epsc0=-0.004, - fpcu=-5.0, epsU=-0.014), - Concrete01(id=2, name="Cover", - fpc=-5.0, epsc0=-0.002, - fpcu=0.0, epsU=-0.006), + Concrete01(id=1, name="Core", fpc=-6.0, epsc0=-0.004, fpcu=-5.0, epsU=-0.014), + Concrete01(id=2, name="Cover", fpc=-5.0, epsc0=-0.002, fpcu=0.0, epsU=-0.006), Steel01(id=3, name="Steel", Fy=60.0, E0=30000.0, b=0.01), ], - sections=[FiberSection( - id=1, name="RC", - patches=[ - # Core (confined) - RectangularPatch(material_id=1, n_fib_y=10, n_fib_z=1, - y_i=cover - y1, z_i=cover - z1, - y_j=y1 - cover, z_j=z1 - cover), - # Top cover - RectangularPatch(material_id=2, n_fib_y=10, n_fib_z=1, - y_i=-y1, z_i=z1 - cover, - y_j=y1, z_j=z1), - # Bottom cover - RectangularPatch(material_id=2, n_fib_y=10, n_fib_z=1, - y_i=-y1, z_i=-z1, - y_j=y1, z_j=cover - z1), - # Left cover - RectangularPatch(material_id=2, n_fib_y=2, n_fib_z=1, - y_i=-y1, z_i=cover - z1, - y_j=cover - y1, z_j=z1 - cover), - # Right cover - RectangularPatch(material_id=2, n_fib_y=2, n_fib_z=1, - y_i=y1 - cover, z_i=cover - z1, - y_j=y1, z_j=z1 - cover), - ], - layers=[ - StraightLayer(material_id=3, n_bars=3, bar_area=As, - y_start=y1 - cover, z_start=z1 - cover, - y_end=y1 - cover, z_end=cover - z1), - StraightLayer(material_id=3, n_bars=2, bar_area=As, - y_start=0.0, z_start=z1 - cover, - y_end=0.0, z_end=cover - z1), - StraightLayer(material_id=3, n_bars=3, bar_area=As, - y_start=cover - y1, z_start=z1 - cover, - y_end=cover - y1, z_end=cover - z1), - ], - )], + sections=[ + FiberSection( + id=1, + name="RC", + patches=[ + # Core (confined) + RectangularPatch( + material_id=1, + n_fib_y=10, + n_fib_z=1, + y_i=cover - y1, + z_i=cover - z1, + y_j=y1 - cover, + z_j=z1 - cover, + ), + # Top cover + RectangularPatch( + material_id=2, + n_fib_y=10, + n_fib_z=1, + y_i=-y1, + z_i=z1 - cover, + y_j=y1, + z_j=z1, + ), + # Bottom cover + RectangularPatch( + material_id=2, + n_fib_y=10, + n_fib_z=1, + y_i=-y1, + z_i=-z1, + y_j=y1, + z_j=cover - z1, + ), + # Left cover + RectangularPatch( + material_id=2, + n_fib_y=2, + n_fib_z=1, + y_i=-y1, + z_i=cover - z1, + y_j=cover - y1, + z_j=z1 - cover, + ), + # Right cover + RectangularPatch( + material_id=2, + n_fib_y=2, + n_fib_z=1, + y_i=y1 - cover, + z_i=cover - z1, + y_j=y1, + z_j=z1 - cover, + ), + ], + layers=[ + StraightLayer( + material_id=3, + n_bars=3, + bar_area=As, + y_start=y1 - cover, + z_start=z1 - cover, + y_end=y1 - cover, + z_end=cover - z1, + ), + StraightLayer( + material_id=3, + n_bars=2, + bar_area=As, + y_start=0.0, + z_start=z1 - cover, + y_end=0.0, + z_end=cover - z1, + ), + StraightLayer( + material_id=3, + n_bars=3, + bar_area=As, + y_start=cover - y1, + z_start=z1 - cover, + y_end=cover - y1, + z_end=cover - z1, + ), + ], + ) + ], elements=[ZeroLengthSectionElement(id=1, nodes=(1, 2), section_id=1)], time_series=[ ConstantTimeSeries(id=1, name="AxialP"), @@ -212,14 +276,16 @@ def test_moment_curvature_with_constant_axial_preload() -> None: ], load_patterns=[ PlainLoadPattern( - id=1, name="AxialP", time_series_id=1, - nodal_loads=[NodalLoad(node_id=2, - forces=(-180.0, 0, 0, 0, 0, 0))], + id=1, + name="AxialP", + time_series_id=1, + nodal_loads=[NodalLoad(node_id=2, forces=(-180.0, 0, 0, 0, 0, 0))], ), PlainLoadPattern( - id=2, name="RefMoment", time_series_id=2, - nodal_loads=[NodalLoad(node_id=2, - forces=(0, 0, 0, 0, 0, 1.0))], + id=2, + name="RefMoment", + time_series_id=2, + nodal_loads=[NodalLoad(node_id=2, forces=(0, 0, 0, 0, 0, 1.0))], ), ], analyses=[], @@ -227,25 +293,29 @@ def test_moment_curvature_with_constant_axial_preload() -> None: # Yield curvature estimate from the Tcl example. d = colDepth - cover Ky = 60.0 / 30000.0 / (0.7 * d) - target = Ky * 15 # μ = 15 - proj.analyses = [PushoverCase( - id=1, name="MK", - pattern_ids=[1, 2], - control_node=2, control_dof=3, - target_disp=target, - step_size=target / 100, - base_nodes=[1], - test="NormUnbalance", - tolerance=1e-9, max_iter=25, - )] + target = Ky * 15 # μ = 15 + proj.analyses = [ + PushoverCase( + id=1, + name="MK", + pattern_ids=[1, 2], + control_node=2, + control_dof=3, + target_disp=target, + step_size=target / 100, + base_nodes=[1], + test="NormUnbalance", + tolerance=1e-9, + max_iter=25, + ) + ] result = OpenSeesRunner(proj).run(proj.analyses[0]) # Analysis must actually converge past yield (not collapse at # step 1 like it did before the two-stage preload fix). assert len(result.control_disp) > 50, ( - f"Converged for only {len(result.control_disp)} of 100 steps — " - "preload stage broken?" + f"Converged for only {len(result.control_disp)} of 100 steps — " "preload stage broken?" ) # Curvature reached or passed yield. kappa_max = float(max(abs(k) for k in result.control_disp)) diff --git a/tests/integration/test_moment_curvature_example.py b/tests/integration/test_moment_curvature_example.py index 1c27358..7648e51 100644 --- a/tests/integration/test_moment_curvature_example.py +++ b/tests/integration/test_moment_curvature_example.py @@ -6,26 +6,30 @@ import pytest pytest.importorskip("openseespy") -from otko.services import load_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.services import load_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def test_moment_curvature_example_round_trips_and_converges(tmp_path) -> None: # type: ignore[no-untyped-def] """build_moment_curvature() → save → load → run → expected shape.""" from examples.moment_curvature import ( - build_moment_curvature, COL_DEPTH, COVER, E_STEEL, FY, MU, NUM_INCR, + build_moment_curvature, ) + proj = build_moment_curvature() proj.validate_references() # Save + reload — catches schema drift. from otko.services import save_project + path = tmp_path / "mk.osmodel" save_project(proj, path) reloaded = load_project(path) @@ -39,15 +43,14 @@ def test_moment_curvature_example_round_trips_and_converges(tmp_path) -> None: ky = (FY / E_STEEL) / (0.7 * d) # At least half of the NUM_INCR pushover steps converged. - assert len(result.control_disp) > NUM_INCR * 0.5, ( - "Pushover bailed out prematurely — check Concrete01 softening" - ) + assert ( + len(result.control_disp) > NUM_INCR * 0.5 + ), "Pushover bailed out prematurely — check Concrete01 softening" # Reached the mu * Ky target. assert result.control_disp[-1] == pytest.approx(MU * ky, rel=1e-2) # Moment at yield curvature is plausible: > 3 kip·in and < 10 kip·in per rebar # → for 8 bars total, the section moment capacity is roughly O(3000-6000) kip·in. peak_moment = max(abs(m) for m in result.base_shear) assert 2000 < peak_moment < 10000, ( - f"Peak moment {peak_moment:.1f} kip·in is outside the " - "expected RC section range" + f"Peak moment {peak_moment:.1f} kip·in is outside the " "expected RC section range" ) diff --git a/tests/integration/test_pattern_factors.py b/tests/integration/test_pattern_factors.py index f67c645..3c05c3e 100644 --- a/tests/integration/test_pattern_factors.py +++ b/tests/integration/test_pattern_factors.py @@ -8,7 +8,7 @@ import pytest ops = pytest.importorskip("openseespy.opensees") # skip if OpenSeesPy not installed -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticBeamColumn, ElasticSection, LinearTimeSeries, @@ -18,7 +18,9 @@ from otko.core import ( # noqa: E402 Project, StaticCase, ) -from otko.services import OpenSeesRunner # noqa: E402 +from otko.services import OpenSeesRunner + +pytestmark = pytest.mark.slow def _cantilever() -> Project: diff --git a/tests/integration/test_rc_frame_earthquake.py b/tests/integration/test_rc_frame_earthquake.py index 8f2a7a8..132535f 100644 --- a/tests/integration/test_rc_frame_earthquake.py +++ b/tests/integration/test_rc_frame_earthquake.py @@ -9,17 +9,19 @@ import pytest pytest.importorskip("openseespy") -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def test_rc_frame_earthquake_runs_and_has_oscillatory_response(tmp_path) -> None: # type: ignore[no-untyped-def] """Synthetic ground motion produces bounded, oscillatory response.""" from examples.rc_frame_earthquake import ( - build_rc_frame_earthquake, - DT, N_PTS, + build_rc_frame_earthquake, ) + proj = build_rc_frame_earthquake() proj.validate_references() @@ -33,9 +35,9 @@ def test_rc_frame_earthquake_runs_and_has_oscillatory_response(tmp_path) -> None # Simulation covers most of the 4-second record (ModifiedNewton # fallback may trim a few steps at stiffness jumps; we allow that). - assert result.n_steps >= int(0.9 * N_PTS), ( - f"Only {result.n_steps}/{N_PTS} steps — fallback didn't recover" - ) + assert result.n_steps >= int( + 0.9 * N_PTS + ), f"Only {result.n_steps}/{N_PTS} steps — fallback didn't recover" # Node 3 Ux history: bounded, non-trivial, some positive AND some # negative (oscillation confirms the base excitation actually @@ -50,4 +52,4 @@ def test_rc_frame_earthquake_runs_and_has_oscillatory_response(tmp_path) -> None # Uy on the top nodes — small compared to Ux (gravity holds, base # excitation is horizontal). uy = h3[:, 1] - assert max(abs(uy)) < 1.0, f"max |Uy| = {max(abs(uy)):.4f} in too large" \ No newline at end of file + assert max(abs(uy)) < 1.0, f"max |Uy| = {max(abs(uy)):.4f} in too large" diff --git a/tests/integration/test_rc_frame_gravity.py b/tests/integration/test_rc_frame_gravity.py index a213681..0c91190 100644 --- a/tests/integration/test_rc_frame_gravity.py +++ b/tests/integration/test_rc_frame_gravity.py @@ -6,8 +6,10 @@ import pytest pytest.importorskip("openseespy") -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def test_rc_frame_gravity_matches_opensees_reference(tmp_path) -> None: # type: ignore[no-untyped-def] @@ -20,7 +22,8 @@ def test_rc_frame_gravity_matches_opensees_reference(tmp_path) -> None: # type: The column axial force is 180 kip compression (from the 180 kip load stepped onto each top node). """ - from examples.rc_frame_gravity import build_rc_frame_gravity, P_LOAD + from examples.rc_frame_gravity import P_LOAD, build_rc_frame_gravity + proj = build_rc_frame_gravity() proj.validate_references() @@ -54,7 +57,7 @@ def test_rc_frame_gravity_matches_opensees_reference(tmp_path) -> None: # type: # OpenSees equilibrium sign, i.e. the first component equals the # applied vertical load on end i. col1 = result.element_forces[1][-1] - assert abs(col1[0]) == pytest.approx(P_LOAD, abs=1.0), ( - f"Column 1 axial {col1[0]:.2f} ≠ ±{P_LOAD} kip" - ) + assert abs(col1[0]) == pytest.approx( + P_LOAD, abs=1.0 + ), f"Column 1 axial {col1[0]:.2f} ≠ ±{P_LOAD} kip" assert abs(col1[3]) == pytest.approx(P_LOAD, abs=1.0) diff --git a/tests/integration/test_rc_frame_pushover.py b/tests/integration/test_rc_frame_pushover.py index d7d6df5..5bd9ed0 100644 --- a/tests/integration/test_rc_frame_pushover.py +++ b/tests/integration/test_rc_frame_pushover.py @@ -6,8 +6,10 @@ import pytest pytest.importorskip("openseespy") -from otko.services import load_project, save_project # noqa: E402 -from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 +from otko.services import load_project, save_project +from otko.services.opensees_runner import OpenSeesRunner + +pytestmark = pytest.mark.slow def test_rc_frame_pushover_reaches_target_with_fallback(tmp_path) -> None: # type: ignore[no-untyped-def] @@ -17,11 +19,13 @@ def test_rc_frame_pushover_reaches_target_with_fallback(tmp_path) -> None: # ty AND shows expected nonlinear shape. """ from examples.rc_frame_pushover import ( - build_rc_frame_pushover, D_STEP, D_TARGET, + build_rc_frame_pushover, ) + from otko.core import PushoverCase + proj = build_rc_frame_pushover() proj.validate_references() @@ -35,7 +39,7 @@ def test_rc_frame_pushover_reaches_target_with_fallback(tmp_path) -> None: # ty push_case = next(c for c in reloaded.analyses if isinstance(c, PushoverCase)) result = OpenSeesRunner(reloaded).run(push_case) - expected_pts = int(D_TARGET / D_STEP) + 1 # 151 including step 0 + expected_pts = int(D_TARGET / D_STEP) + 1 # 151 including step 0 assert len(result.control_disp) == expected_pts, ( f"Got {len(result.control_disp)} points, expected {expected_pts} — " "ModifiedNewton fallback probably didn't kick in." @@ -64,6 +68,6 @@ def test_rc_frame_pushover_reaches_target_with_fallback(tmp_path) -> None: # ty # of the pushover (step 1) should be close to P = 180 kip. step1_col1 = result.element_forces[1][1] axial_step1 = abs(step1_col1[0]) - assert 100.0 < axial_step1 < 260.0, ( - f"Col 1 axial at step 1 = {axial_step1:.1f} kip — gravity preload lost?" - ) + assert ( + 100.0 < axial_step1 < 260.0 + ), f"Col 1 axial at step 1 = {axial_step1:.1f} kip — gravity preload lost?" diff --git a/tests/integration/test_runner_imposed_motion.py b/tests/integration/test_runner_imposed_motion.py index 3565fb7..33c352b 100644 --- a/tests/integration/test_runner_imposed_motion.py +++ b/tests/integration/test_runner_imposed_motion.py @@ -23,7 +23,7 @@ import pytest ops = pytest.importorskip("openseespy.opensees") h5py = pytest.importorskip("h5py") -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticUniaxial, ImposedSupportMotionPattern, Node, @@ -33,7 +33,10 @@ from otko.core import ( # noqa: E402 UniformExcitationPattern, ZeroLengthElement, ) -from otko.services import OpenSeesRunner # noqa: E402 +from otko.services import OpenSeesRunner + +pytestmark = pytest.mark.slow + W = 1.57 # drive (rad/s) DT_SERIES = 0.01 diff --git a/tests/integration/test_runner_modal.py b/tests/integration/test_runner_modal.py index 753210b..bf2a843 100644 --- a/tests/integration/test_runner_modal.py +++ b/tests/integration/test_runner_modal.py @@ -15,16 +15,18 @@ import math import pytest -ops = pytest.importorskip("openseespy.opensees") # noqa: F401 +ops = pytest.importorskip("openseespy.opensees") -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticBeamColumn, ElasticSection, ModalCase, Node, Project, ) -from otko.services import OpenSeesRunner # noqa: E402 +from otko.services import OpenSeesRunner + +pytestmark = pytest.mark.slow def test_sdof_pole_first_frequency_matches_kspring_over_m() -> None: @@ -36,11 +38,11 @@ def test_sdof_pole_first_frequency_matches_kspring_over_m() -> None: m_tip = 1000.0 project = Project( - ndm=2, ndf=3, + ndm=2, + ndf=3, nodes=[ Node(id=1, coords=(0.0, 0.0, 0.0), restraint=(True, True, False, False, False, True)), - Node(id=2, coords=(0.0, L, 0.0), - mass=(m_tip, m_tip, 0.0, 0.0, 0.0, 0.0)), + Node(id=2, coords=(0.0, L, 0.0), mass=(m_tip, m_tip, 0.0, 0.0, 0.0, 0.0)), ], sections=[ElasticSection(id=1, E=E, A=A, Iz=I)], elements=[ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1)], @@ -54,9 +56,9 @@ def test_sdof_pole_first_frequency_matches_kspring_over_m() -> None: omega_expected = math.sqrt(k / m_tip) omega_actual = float(results.angular_frequencies[0]) - assert math.isclose(omega_actual, omega_expected, rel_tol=5e-3), ( - f"ω₁ mismatch: expected {omega_expected:.4f} rad/s, got {omega_actual:.4f} rad/s" - ) + assert math.isclose( + omega_actual, omega_expected, rel_tol=5e-3 + ), f"ω₁ mismatch: expected {omega_expected:.4f} rad/s, got {omega_actual:.4f} rad/s" def test_runner_falls_back_to_lapack_for_small_models() -> None: @@ -64,11 +66,11 @@ def test_runner_falls_back_to_lapack_for_small_models() -> None: from unittest.mock import MagicMock project = Project( - ndm=2, ndf=3, + ndm=2, + ndf=3, nodes=[ Node(id=1, coords=(0.0, 0.0, 0.0), restraint=(True, True, False, False, False, True)), - Node(id=2, coords=(0.0, 3.0, 0.0), - mass=(1000.0, 1000.0, 0.0, 0.0, 0.0, 0.0)), + Node(id=2, coords=(0.0, 3.0, 0.0), mass=(1000.0, 1000.0, 0.0, 0.0, 0.0, 0.0)), ], sections=[ElasticSection(id=1, E=200e9, A=0.01, Iz=8.333e-6)], elements=[ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1)], diff --git a/tests/integration/test_runner_static.py b/tests/integration/test_runner_static.py index da5aa9d..bd9cac1 100644 --- a/tests/integration/test_runner_static.py +++ b/tests/integration/test_runner_static.py @@ -12,7 +12,7 @@ import pytest ops = pytest.importorskip("openseespy.opensees") # skip if OpenSeesPy not installed -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticBeamColumn, ElasticSection, LinearTimeSeries, @@ -22,7 +22,9 @@ from otko.core import ( # noqa: E402 Project, StaticCase, ) -from otko.services import OpenSeesRunner # noqa: E402 +from otko.services import OpenSeesRunner + +pytestmark = pytest.mark.slow def test_cantilever_tip_deflection_matches_closed_form() -> None: @@ -34,7 +36,8 @@ def test_cantilever_tip_deflection_matches_closed_form() -> None: I = 8.333e-6 project = Project( - ndm=2, ndf=3, + ndm=2, + ndf=3, nodes=[ Node(id=1, coords=(0.0, 0.0, 0.0), restraint=(True, True, False, False, False, True)), Node(id=2, coords=(L, 0.0, 0.0)), @@ -44,7 +47,8 @@ def test_cantilever_tip_deflection_matches_closed_form() -> None: time_series=[LinearTimeSeries(id=1)], load_patterns=[ PlainLoadPattern( - id=1, time_series_id=1, + id=1, + time_series_id=1, # Fy at the tip (downward); Rz of node 1 is restrained, others free. nodal_loads=[NodalLoad(node_id=2, forces=(0.0, -P, 0.0, 0.0, 0.0, 0.0))], ) @@ -54,12 +58,12 @@ def test_cantilever_tip_deflection_matches_closed_form() -> None: case = StaticCase(id=1, name="Cantilever", pattern_ids=[1]) results = OpenSeesRunner(project).run(case) - delta_expected = -P * L**3 / (3.0 * E * I) # negative (downward) - delta_actual = results.disp(node_id=2, dof=2) # Uy at node 2 + delta_expected = -P * L**3 / (3.0 * E * I) # negative (downward) + delta_actual = results.disp(node_id=2, dof=2) # Uy at node 2 - assert math.isclose(delta_actual, delta_expected, rel_tol=1e-3), ( - f"Tip deflection mismatch: expected {delta_expected:.6e}, got {delta_actual:.6e}" - ) + assert math.isclose( + delta_actual, delta_expected, rel_tol=1e-3 + ), f"Tip deflection mismatch: expected {delta_expected:.6e}, got {delta_actual:.6e}" # Reaction at the support equals the applied load. Fy_reaction = results.node_reaction[1][0, 1] diff --git a/tests/integration/test_runner_transient.py b/tests/integration/test_runner_transient.py index 69c5f3b..a2be365 100644 --- a/tests/integration/test_runner_transient.py +++ b/tests/integration/test_runner_transient.py @@ -12,10 +12,10 @@ import math import numpy as np import pytest -ops = pytest.importorskip("openseespy.opensees") # noqa: F401 -h5py = pytest.importorskip("h5py") # noqa: F401 +ops = pytest.importorskip("openseespy.opensees") +h5py = pytest.importorskip("h5py") -from otko.core import ( # noqa: E402 +from otko.core import ( ConstantTimeSeries, ElasticBeamColumn, ElasticSection, @@ -27,7 +27,9 @@ from otko.core import ( # noqa: E402 StaticCase, TransientCase, ) -from otko.services import OpenSeesRunner # noqa: E402 +from otko.services import OpenSeesRunner + +pytestmark = pytest.mark.slow def test_sdof_free_vibration_matches_cosine(tmp_path) -> None: # type: ignore[no-untyped-def] @@ -49,28 +51,29 @@ def test_sdof_free_vibration_matches_cosine(tmp_path) -> None: # type: ignore[n # (already done), then verify dt-step Newmark integration of free # vibration starting from a static IC. F0 = 100.0 - u0 = F0 / k project = Project( - ndm=2, ndf=3, + ndm=2, + ndf=3, nodes=[ Node(id=1, coords=(0.0, 0.0, 0.0), restraint=(True, True, False, False, False, True)), - Node(id=2, coords=(0.0, L, 0.0), - mass=(m_tip, m_tip, 0.0, 0.0, 0.0, 0.0)), + Node(id=2, coords=(0.0, L, 0.0), mass=(m_tip, m_tip, 0.0, 0.0, 0.0, 0.0)), ], sections=[ElasticSection(id=1, E=E, A=A, Iz=I)], elements=[ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1)], time_series=[ ConstantTimeSeries(id=1, factor=1.0), # static initial - LinearTimeSeries(id=2), # transient (zero load) + LinearTimeSeries(id=2), # transient (zero load) ], load_patterns=[ PlainLoadPattern( - id=1, time_series_id=1, + id=1, + time_series_id=1, nodal_loads=[NodalLoad(node_id=2, forces=(F0, 0.0, 0.0, 0.0, 0.0, 0.0))], ), PlainLoadPattern( - id=2, time_series_id=2, + id=2, + time_series_id=2, nodal_loads=[NodalLoad(node_id=2, forces=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0))], ), ], @@ -85,8 +88,11 @@ def test_sdof_free_vibration_matches_cosine(tmp_path) -> None: # type: ignore[n n_steps = 200 dt = T / 50.0 case = TransientCase( - id=2, name="FreeVib", pattern_ids=[2], - dt=dt, n_steps=n_steps, + id=2, + name="FreeVib", + pattern_ids=[2], + dt=dt, + n_steps=n_steps, # Average-acceleration Newmark is unconditionally stable. integrator_params=(0.5, 0.25), ) @@ -111,7 +117,8 @@ def test_sdof_free_vibration_matches_cosine(tmp_path) -> None: # type: ignore[n def test_transient_writes_hdf5_with_time_dataset(tmp_path) -> None: # type: ignore[no-untyped-def] """Transient run must produce an HDF5 with a /time dataset of length n_steps.""" project = Project( - ndm=2, ndf=3, + ndm=2, + ndf=3, nodes=[ Node(id=1, coords=(0.0, 0.0, 0.0), restraint=(True, True, False, False, False, True)), Node(id=2, coords=(0.0, 3.0, 0.0), mass=(1000.0,) * 3 + (0.0,) * 3), @@ -121,7 +128,8 @@ def test_transient_writes_hdf5_with_time_dataset(tmp_path) -> None: # type: ign time_series=[LinearTimeSeries(id=1)], load_patterns=[ PlainLoadPattern( - id=1, time_series_id=1, + id=1, + time_series_id=1, nodal_loads=[NodalLoad(node_id=2, forces=(10.0, 0, 0, 0, 0, 0))], ) ], diff --git a/tests/services/__init__.py b/tests/services/__init__.py new file mode 100644 index 0000000..c495081 --- /dev/null +++ b/tests/services/__init__.py @@ -0,0 +1 @@ +"""Service-layer tests: runner, persistence, export, results I/O.""" diff --git a/tests/unit/test_element_forces.py b/tests/services/test_element_forces.py similarity index 90% rename from tests/unit/test_element_forces.py rename to tests/services/test_element_forces.py index 3fc8dd8..2f57387 100644 --- a/tests/unit/test_element_forces.py +++ b/tests/services/test_element_forces.py @@ -19,11 +19,11 @@ from otko.services.element_forces import ( ) from otko.services.results import StaticResults - # ────────────────────────────────────────────────────────────────────── # Fixtures: a tiny 3D project with 2 elements + canned force results. # ────────────────────────────────────────────────────────────────────── + @pytest.fixture def project_3d() -> Project: return Project( @@ -44,12 +44,12 @@ def project_3d() -> Project: def static_3d_results() -> StaticResults: """One step of analysis. Element 10 carries [N=100, …]; element 20 carries [N=-50, …] (compression). 12 components, single time step.""" - forces_10 = np.array([[100.0, 5.0, 7.0, 1.0, 8.0, 9.0, - -100.0, -5.0, -7.0, -1.0, -8.0, -9.0]]) - forces_20 = np.array([[-50.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 50.0, 0.0, 0.0, 0.0, 0.0, 0.0]]) + forces_10 = np.array([[100.0, 5.0, 7.0, 1.0, 8.0, 9.0, -100.0, -5.0, -7.0, -1.0, -8.0, -9.0]]) + forces_20 = np.array([[-50.0, 0.0, 0.0, 0.0, 0.0, 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0]]) return StaticResults( - case_id=1, case_name="tst", n_steps=1, + case_id=1, + case_name="tst", + n_steps=1, element_forces={10: forces_10, 20: forces_20}, ) @@ -58,8 +58,10 @@ def static_3d_results() -> StaticResults: # extract_diagram_data # ────────────────────────────────────────────────────────────────────── + def test_extract_axial_yields_correct_per_end_values( - project_3d: Project, static_3d_results: StaticResults, + project_3d: Project, + static_3d_results: StaticResults, ) -> None: data = extract_diagram_data(project_3d, static_3d_results, ForceComponent.N) assert data.component is ForceComponent.N @@ -86,22 +88,20 @@ def test_extract_handles_2d_force_vectors(project_3d: Project) -> None: """2D OpenSees beams return 6 components: [N, Vy, Mz] × 2 ends. Asking for V3 / M2 in this case should yield no rows for that element.""" forces_10 = np.array([[100.0, 5.0, 9.0, -100.0, -5.0, -9.0]]) - results = StaticResults(case_id=1, case_name="2d", n_steps=1, - element_forces={10: forces_10}) + results = StaticResults(case_id=1, case_name="2d", n_steps=1, element_forces={10: forces_10}) n_data = extract_diagram_data(project_3d, results, ForceComponent.N) np.testing.assert_array_equal(n_data.values_i, [100.0]) v3_data = extract_diagram_data(project_3d, results, ForceComponent.V3) - assert v3_data.element_ids.size == 0 # V3 not available in 2D output + assert v3_data.element_ids.size == 0 # V3 not available in 2D output def test_extract_uses_specified_step(project_3d: Project) -> None: """Multi-step pushover: pick the first step explicitly.""" f = np.zeros((3, 12)) - f[0, 0] = 10.0 # step 0 axial @ node i + f[0, 0] = 10.0 # step 0 axial @ node i f[1, 0] = 20.0 f[2, 0] = 30.0 - results = StaticResults(case_id=1, case_name="push", n_steps=3, - element_forces={10: f}) + results = StaticResults(case_id=1, case_name="push", n_steps=3, element_forces={10: f}) data = extract_diagram_data(project_3d, results, ForceComponent.N, step=0) np.testing.assert_array_equal(data.values_i, [10.0]) data = extract_diagram_data(project_3d, results, ForceComponent.N, step=2) @@ -112,8 +112,10 @@ def test_extract_uses_specified_step(project_3d: Project) -> None: # auto_scale # ────────────────────────────────────────────────────────────────────── + def test_auto_scale_scales_to_target_fraction( - project_3d: Project, static_3d_results: StaticResults, + project_3d: Project, + static_3d_results: StaticResults, ) -> None: data = extract_diagram_data(project_3d, static_3d_results, ForceComponent.N) # Bounding-box diagonal of the 3-node line is 6.0 along X. @@ -127,7 +129,9 @@ def test_auto_scale_zero_force_returns_unity(project_3d: Project) -> None: empty = DiagramData( component=ForceComponent.N, element_ids=np.empty(0, dtype=int), - values_i=np.empty(0), values_j=np.empty(0), abs_max=0.0, + values_i=np.empty(0), + values_j=np.empty(0), + abs_max=0.0, ) assert auto_scale(project_3d, empty) == 1.0 @@ -138,8 +142,10 @@ def test_auto_scale_zero_force_returns_unity(project_3d: Project) -> None: @pytest.fixture def truss_project_2d() -> Project: from otko.core import ElasticUniaxial, TrussElement + return Project( - ndm=2, ndf=2, + ndm=2, + ndf=2, nodes=[ Node(id=1, coords=(0.0, 0.0, 0.0)), Node(id=2, coords=(3.0, 0.0, 0.0)), @@ -163,7 +169,9 @@ def test_truss_axial_uses_correct_index_map(truss_project_2d: Project) -> None: # (equilibrium signs: end-i points out, end-j points in). forces = np.array([[-1500.0, 0.0, 1500.0, 0.0]]) results = StaticResults( - case_id=1, case_name="tst", n_steps=1, + case_id=1, + case_name="tst", + n_steps=1, element_forces={1: forces}, ) data = extract_diagram_data(truss_project_2d, results, ForceComponent.N) @@ -178,7 +186,9 @@ def test_truss_has_no_shear_or_moment_components(truss_project_2d: Project) -> N """Requesting V2 / V3 / M3 on a truss returns an empty diagram.""" forces = np.array([[100.0, 0.0, -100.0, 0.0]]) results = StaticResults( - case_id=1, case_name="tst", n_steps=1, + case_id=1, + case_name="tst", + n_steps=1, element_forces={1: forces}, ) for comp in (ForceComponent.V2, ForceComponent.V3, ForceComponent.M3): diff --git a/tests/unit/test_export.py b/tests/services/test_export.py similarity index 100% rename from tests/unit/test_export.py rename to tests/services/test_export.py diff --git a/tests/unit/test_imposed_support_motion.py b/tests/services/test_imposed_support_motion.py similarity index 100% rename from tests/unit/test_imposed_support_motion.py rename to tests/services/test_imposed_support_motion.py diff --git a/tests/unit/test_peer_record.py b/tests/services/test_peer_record.py similarity index 100% rename from tests/unit/test_peer_record.py rename to tests/services/test_peer_record.py diff --git a/tests/unit/test_persistence.py b/tests/services/test_persistence.py similarity index 90% rename from tests/unit/test_persistence.py rename to tests/services/test_persistence.py index 7416697..93319ff 100644 --- a/tests/unit/test_persistence.py +++ b/tests/services/test_persistence.py @@ -27,7 +27,8 @@ from otko.services import PROJECT_FILE_SUFFIX, load_project, save_project def _sample_project() -> Project: return Project( meta=ProjectMeta(name="Sample", author="Ozan", units=UnitSystem.SI_M_N), - ndm=3, ndf=6, + ndm=3, + ndf=6, nodes=[ Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), Node(id=2, coords=(0, 0, 3.0)), @@ -35,8 +36,14 @@ def _sample_project() -> Project: materials=[ Steel01(id=1, Fy=420e6, E0=200e9, b=0.01), Concrete02( - id=2, fpc=-30e6, epsc0=-0.002, fpcu=-15e6, epsU=-0.005, - ft=3e6, Ets=2e9, **{"lambda": 0.1}, + id=2, + fpc=-30e6, + epsc0=-0.002, + fpcu=-15e6, + epsU=-0.005, + ft=3e6, + Ets=2e9, + **{"lambda": 0.1}, ), ], sections=[ElasticSection(id=1, E=200e9, A=0.01, Iz=8.33e-6, Iy=8.33e-6, G=80e9, J=1e-6)], @@ -47,7 +54,8 @@ def _sample_project() -> Project: time_series=[LinearTimeSeries(id=1, factor=1.0)], load_patterns=[ PlainLoadPattern( - id=1, time_series_id=1, + id=1, + time_series_id=1, nodal_loads=[NodalLoad(node_id=2, forces=(0, 0, -10e3, 0, 0, 0))], ) ], diff --git a/tests/unit/test_runner_translation.py b/tests/services/test_runner_translation.py similarity index 89% rename from tests/unit/test_runner_translation.py rename to tests/services/test_runner_translation.py index c4d4e15..1903183 100644 --- a/tests/unit/test_runner_translation.py +++ b/tests/services/test_runner_translation.py @@ -23,7 +23,6 @@ from otko.core import ( Node, PlainLoadPattern, Project, - StaticCase, Steel01, TrussElement, ZeroLengthElement, @@ -34,7 +33,8 @@ from otko.services.opensees_runner import OpenSeesRunner, _dof_indices # ───────────────────────── helpers ───────────────────────── def _truss_2d() -> Project: return Project( - ndm=2, ndf=2, + ndm=2, + ndf=2, nodes=[ Node(id=1, coords=(0, 0, 0), restraint=(True, True, False, False, False, False)), Node(id=2, coords=(4, 0, 0), restraint=(False, True, False, False, False, False)), @@ -48,7 +48,8 @@ def _truss_2d() -> Project: time_series=[LinearTimeSeries(id=1)], load_patterns=[ PlainLoadPattern( - id=1, time_series_id=1, + id=1, + time_series_id=1, nodal_loads=[NodalLoad(node_id=3, forces=(0, -1000, 0, 0, 0, 0))], ) ], @@ -57,7 +58,8 @@ def _truss_2d() -> Project: def _portal_3d() -> Project: return Project( - ndm=3, ndf=6, + ndm=3, + ndf=6, nodes=[ Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), Node(id=2, coords=(0, 0, 3)), @@ -72,27 +74,35 @@ def _isolator_3d() -> Project: """A grounded HystereticSM isolator (zeroLength) under a -cMass frame — the wire-rope benchmark's building block in miniature.""" return Project( - ndm=3, ndf=6, + ndm=3, + ndf=6, nodes=[ Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), # grounded - Node(id=2, coords=(0, 0, 0)), # coincident + Node(id=2, coords=(0, 0, 0)), # coincident Node(id=3, coords=(0, 0, 3)), ], materials=[ HystereticSM( - id=1, name="axial", + id=1, + name="axial", pos_env=[(1.57, 0.00207), (69.1, 0.0399)], neg_env=[(-1.4, -0.00057), (-15.31, -0.0483)], ), HystereticSM(id=2, name="shear", pos_env=[(0.12, 0.00067), (9.21, 0.0804)]), ], sections=[ - ElasticSection(id=1, E=2.1e8, A=9.13e-4, Iz=7.373e-7, Iy=7.373e-7, G=8.08e7, J=2.494e-8), + ElasticSection( + id=1, E=2.1e8, A=9.13e-4, Iz=7.373e-7, Iy=7.373e-7, G=8.08e7, J=2.494e-8 + ), ], elements=[ ZeroLengthElement(id=1, nodes=(1, 2), material_ids=(1, 2), dofs=(3, 1)), ElasticBeamColumn( - id=2, nodes=(2, 3), section_id=1, rho=0.00717, consistent_mass=True, + id=2, + nodes=(2, 3), + section_id=1, + rho=0.00717, + consistent_mass=True, ), ], ) @@ -100,7 +110,8 @@ def _isolator_3d() -> Project: def _shear_frame_2d() -> Project: return Project( - ndm=2, ndf=3, + ndm=2, + ndf=3, nodes=[ Node(id=1, coords=(0, 0, 0), restraint=(True, True, False, False, False, True)), Node(id=2, coords=(4, 0, 0), restraint=(True, True, False, False, False, True)), @@ -210,9 +221,18 @@ class TestMaterialEmission: OpenSeesRunner(_isolator_3d(), ops_module=ops).build() # Pairs are flattened in (force, deformation) command order, both envelopes. ops.uniaxialMaterial.assert_any_call( - "HystereticSM", 1, - "-posEnv", 1.57, 0.00207, 69.1, 0.0399, - "-negEnv", -1.4, -0.00057, -15.31, -0.0483, + "HystereticSM", + 1, + "-posEnv", + 1.57, + 0.00207, + 69.1, + 0.0399, + "-negEnv", + -1.4, + -0.00057, + -15.31, + -0.0483, ) def test_hystereticsm_symmetric_omits_neg_envelope(self) -> None: @@ -220,7 +240,13 @@ class TestMaterialEmission: OpenSeesRunner(_isolator_3d(), ops_module=ops).build() # No neg_env ⇒ only -posEnv is emitted (OpenSees mirrors it). ops.uniaxialMaterial.assert_any_call( - "HystereticSM", 2, "-posEnv", 0.12, 0.00067, 9.21, 0.0804, + "HystereticSM", + 2, + "-posEnv", + 0.12, + 0.00067, + 9.21, + 0.0804, ) @@ -234,24 +260,18 @@ class TestElementEmission: ops = MagicMock() OpenSeesRunner(_portal_3d(), ops_module=ops).build() # geomTransf tag 1 was allocated for "Linear"; element should use it. - ops.element.assert_any_call( - "elasticBeamColumn", 1, 1, 2, 1, 1, "-mass", 0.0 - ) + ops.element.assert_any_call("elasticBeamColumn", 1, 1, 2, 1, 1, "-mass", 0.0) def test_elastic_beam_column_cmass_appends_flag(self) -> None: ops = MagicMock() OpenSeesRunner(_isolator_3d(), ops_module=ops).build() # cMass=True ⇒ the consistent-mass flag trails the -mass density. - ops.element.assert_any_call( - "elasticBeamColumn", 2, 2, 3, 1, 1, "-mass", 0.00717, "-cMass" - ) + ops.element.assert_any_call("elasticBeamColumn", 2, 2, 3, 1, 1, "-mass", 0.00717, "-cMass") def test_zero_length_emits_mat_and_dir_lists(self) -> None: ops = MagicMock() OpenSeesRunner(_isolator_3d(), ops_module=ops).build() - ops.element.assert_any_call( - "zeroLength", 1, 1, 2, "-mat", 1, 2, "-dir", 3, 1 - ) + ops.element.assert_any_call("zeroLength", 1, 1, 2, "-mat", 1, 2, "-dir", 3, 1) class TestPatternEmission: diff --git a/tests/unit/test_section_bbox.py b/tests/services/test_section_bbox.py similarity index 65% rename from tests/unit/test_section_bbox.py rename to tests/services/test_section_bbox.py index 68ee8cd..25f6b24 100644 --- a/tests/unit/test_section_bbox.py +++ b/tests/services/test_section_bbox.py @@ -10,7 +10,6 @@ from otko.core import ( CircularPatch, ElasticSection, FiberSection, - Project, RectangularPatch, StraightLayer, ) @@ -24,11 +23,17 @@ def test_elastic_section_back_solves_rectangle() -> None: # Known rectangle: b = 0.3, h = 0.5 → A = 0.15, Iz = 3.125e-3 b, h = 0.30, 0.50 A = b * h - Iz = b * h ** 3 / 12.0 - Iy = h * b ** 3 / 12.0 + Iz = b * h**3 / 12.0 + Iy = h * b**3 / 12.0 sec = ElasticSection( - id=1, name="R", - E=200e9, A=A, Iz=Iz, Iy=Iy, G=80e9, J=1e-6, + id=1, + name="R", + E=200e9, + A=A, + Iz=Iz, + Iy=Iy, + G=80e9, + J=1e-6, ) dims = bbox_for_section(sec) assert dims is not None @@ -41,10 +46,17 @@ def test_elastic_section_back_solves_rectangle() -> None: def test_fiber_section_bbox_from_rect_patch() -> None: sec = FiberSection( id=1, - patches=[RectangularPatch( - material_id=1, n_fib_y=4, n_fib_z=4, - y_i=-0.15, z_i=-0.25, y_j=0.15, z_j=0.25, - )], + patches=[ + RectangularPatch( + material_id=1, + n_fib_y=4, + n_fib_z=4, + y_i=-0.15, + z_i=-0.25, + y_j=0.15, + z_j=0.25, + ) + ], ) dims = bbox_for_section(sec) assert dims is not None @@ -56,11 +68,16 @@ def test_fiber_section_bbox_from_rect_patch() -> None: def test_fiber_section_bbox_includes_circ_patches() -> None: sec = FiberSection( id=1, - patches=[CircularPatch( - material_id=1, n_fib_circ=8, n_fib_rad=2, - y_center=0.0, z_center=0.0, - r_outer=0.2, - )], + patches=[ + CircularPatch( + material_id=1, + n_fib_circ=8, + n_fib_rad=2, + y_center=0.0, + z_center=0.0, + r_outer=0.2, + ) + ], ) dims = bbox_for_section(sec) assert dims is not None @@ -72,15 +89,28 @@ def test_fiber_section_bbox_grows_for_layers() -> None: """A bar layer that extends past the patches must widen the bbox.""" sec = FiberSection( id=1, - patches=[RectangularPatch( - material_id=1, n_fib_y=2, n_fib_z=2, - y_i=-0.10, z_i=-0.10, y_j=0.10, z_j=0.10, - )], - layers=[StraightLayer( - material_id=2, n_bars=3, bar_area=1e-4, - y_start=-0.15, z_start=0.12, - y_end=0.15, z_end=0.12, - )], + patches=[ + RectangularPatch( + material_id=1, + n_fib_y=2, + n_fib_z=2, + y_i=-0.10, + z_i=-0.10, + y_j=0.10, + z_j=0.10, + ) + ], + layers=[ + StraightLayer( + material_id=2, + n_bars=3, + bar_area=1e-4, + y_start=-0.15, + z_start=0.12, + y_end=0.15, + z_end=0.12, + ) + ], ) dims = bbox_for_section(sec) assert dims is not None diff --git a/tests/unit/test_transient_and_export.py b/tests/services/test_transient_and_export.py similarity index 80% rename from tests/unit/test_transient_and_export.py rename to tests/services/test_transient_and_export.py index 30985b1..31b9199 100644 --- a/tests/unit/test_transient_and_export.py +++ b/tests/services/test_transient_and_export.py @@ -22,20 +22,15 @@ def fake_transient_h5(tmp_path: Path) -> Path: f.create_dataset("time", data=np.linspace(0.0, 0.04, n_steps)) for nid in (1, 2): base = nid * 10.0 - f.create_dataset(f"nodes/{nid}/disp", - data=np.full((n_steps, ndf), base)) - f.create_dataset(f"nodes/{nid}/vel", - data=np.full((n_steps, ndf), base + 0.1)) - f.create_dataset(f"nodes/{nid}/accel", - data=np.full((n_steps, ndf), base + 0.2)) - f.create_dataset("elements/100/forces", - data=np.full((n_steps, 12), 5.0)) + f.create_dataset(f"nodes/{nid}/disp", data=np.full((n_steps, ndf), base)) + f.create_dataset(f"nodes/{nid}/vel", data=np.full((n_steps, ndf), base + 0.1)) + f.create_dataset(f"nodes/{nid}/accel", data=np.full((n_steps, ndf), base + 0.2)) + f.create_dataset("elements/100/forces", data=np.full((n_steps, 12), 5.0)) return h5_path def test_node_disp_vel_accel_history_round_trip(fake_transient_h5: Path) -> None: - r = TransientResults(case_id=1, case_name="t", - h5_path=fake_transient_h5, n_steps=5, dt=0.01) + r = TransientResults(case_id=1, case_name="t", h5_path=fake_transient_h5, n_steps=5, dt=0.01) np.testing.assert_array_equal(r.node_disp_history(1), np.full((5, 6), 10.0)) np.testing.assert_array_equal(r.node_vel_history(1), np.full((5, 6), 10.1)) np.testing.assert_array_equal(r.node_accel_history(1), np.full((5, 6), 10.2)) @@ -52,8 +47,7 @@ def test_missing_history_raises_keyerror(tmp_path: Path) -> None: f.create_dataset("time", data=np.array([0.0, 0.01])) f.create_dataset("nodes/1/disp", data=np.zeros((2, 6))) - r = TransientResults(case_id=1, case_name="x", - h5_path=h5_path, n_steps=2, dt=0.01) + r = TransientResults(case_id=1, case_name="x", h5_path=h5_path, n_steps=2, dt=0.01) # disp works: assert r.node_disp_history(1).shape == (2, 6) # vel/accel raise: @@ -107,8 +101,7 @@ def test_export_time_history_video_decimates(tmp_path: Path) -> None: out = tmp_path / "th.gif" # 100 steps with every=10 → 10 frames captured. - export_time_history_video(plotter, set_step, out, n_steps=100, - fps=4, every=10) + export_time_history_video(plotter, set_step, out, n_steps=100, fps=4, every=10) plotter.close() assert out.exists() diff --git a/tests/tools/test_parse_schemas.py b/tests/tools/test_parse_schemas.py index cf9dc38..506496c 100644 --- a/tests/tools/test_parse_schemas.py +++ b/tests/tools/test_parse_schemas.py @@ -13,6 +13,7 @@ import json from pathlib import Path import pytest + from tools.gidopensees_import.parse_schemas import ( ParseError, _parse_dependencies, @@ -297,9 +298,9 @@ class TestRealFiles: steel2 = _find_entry(catalog, "Steel02") assert steel2 is not None form = next(f for f in steel2.fields if f.name == "Formulation") - assert len(form.dependencies) == 3, ( - f"Expected 3 dependency rules on Formulation, got {len(form.dependencies)}" - ) + assert ( + len(form.dependencies) == 3 + ), f"Expected 3 dependency rules on Formulation, got {len(form.dependencies)}" def test_steel02_formulation_dep_triggers(self, catalog: CatalogSpec) -> None: steel2 = _find_entry(catalog, "Steel02") @@ -316,18 +317,18 @@ class TestRealFiles: def test_all_mat_entries_have_book(self, catalog: CatalogSpec) -> None: for book in catalog.mat_books: for entry in book.entries: - assert entry.book == book.name, ( - f"{entry.name!r} has book={entry.book!r}, expected {book.name!r}" - ) + assert ( + entry.book == book.name + ), f"{entry.name!r} has book={entry.book!r}, expected {book.name!r}" def test_all_fields_have_valid_widget_type(self, catalog: CatalogSpec) -> None: valid = {"CB", "UNITS", "MAT", "SCALAR", "TUPLE"} for book in catalog.mat_books: for entry in book.entries: for f in entry.fields: - assert f.widget_type in valid, ( - f"{entry.name}.{f.name}: unknown widget_type={f.widget_type!r}" - ) + assert ( + f.widget_type in valid + ), f"{entry.name}.{f.name}: unknown widget_type={f.widget_type!r}" def test_point_restraints_condtype(self, catalog: CatalogSpec) -> None: entry = _find_cnd_entry(catalog, "Point_Restraints") @@ -354,9 +355,9 @@ class TestRealFiles: for fspec in entry.fields: for rule in fspec.dependencies: for act in rule.actions: - assert act.action in valid_actions, ( - f"{entry.name}.{fspec.name}: invalid action {act.action!r}" - ) + assert ( + act.action in valid_actions + ), f"{entry.name}.{fspec.name}: invalid action {act.action!r}" # --------------------------------------------------------------------------- diff --git a/tests/unit/core/materials/test_concrete04.py b/tests/unit/core/materials/test_concrete04.py index ccec7eb..5ea8311 100644 --- a/tests/unit/core/materials/test_concrete04.py +++ b/tests/unit/core/materials/test_concrete04.py @@ -24,9 +24,14 @@ class TestConcrete04Instantiation: def test_with_tension_construct(self) -> None: m = Concrete04( - id=2, name="C30-Tension", - fpc=-30e6, epsc0=-0.002, epscu=-0.005, Ec=30e9, - fct=3.0e6, et=1e-4, + id=2, + name="C30-Tension", + fpc=-30e6, + epsc0=-0.002, + epscu=-0.005, + Ec=30e9, + fct=3.0e6, + et=1e-4, ) assert m.fct == 3.0e6 assert m.et == 1e-4 @@ -34,9 +39,15 @@ class TestConcrete04Instantiation: def test_with_beta_construct(self) -> None: m = Concrete04( - id=3, name="C30-Cyclic", - fpc=-30e6, epsc0=-0.002, epscu=-0.005, Ec=30e9, - fct=3.0e6, et=1e-4, beta=0.1, + id=3, + name="C30-Cyclic", + fpc=-30e6, + epsc0=-0.002, + epscu=-0.005, + Ec=30e9, + fct=3.0e6, + et=1e-4, + beta=0.1, ) assert m.beta == pytest.approx(0.1) @@ -83,8 +94,14 @@ class TestConcrete04TensileParamConsistency: def test_beta_bounds(self) -> None: with pytest.raises(ValidationError): Concrete04( - id=1, fpc=-30e6, epsc0=-0.002, epscu=-0.005, Ec=30e9, - fct=3e6, et=1e-4, beta=1.5, + id=1, + fpc=-30e6, + epsc0=-0.002, + epscu=-0.005, + Ec=30e9, + fct=3e6, + et=1e-4, + beta=1.5, ) @@ -101,9 +118,14 @@ class TestConcrete04JsonRoundTrip: def test_with_tension_round_trip(self) -> None: original = Concrete04( - id=2, name="C30-T", - fpc=-30e6, epsc0=-0.002, epscu=-0.005, Ec=30e9, - fct=3.0e6, et=1e-4, + id=2, + name="C30-T", + fpc=-30e6, + epsc0=-0.002, + epscu=-0.005, + Ec=30e9, + fct=3.0e6, + et=1e-4, ) payload = material_adapter.dump_python(original, mode="json", by_alias=True) restored = material_adapter.validate_python(payload) @@ -114,6 +136,7 @@ class TestConcrete04JsonRoundTrip: def test_old_osmodel_without_concrete04_loads_cleanly(self) -> None: """An osmodel payload that doesn't mention Concrete04 is unaffected.""" from pathlib import Path + from otko.services import load_project osmodel = Path(__file__).parents[4] / "examples" / "cantilever.osmodel" diff --git a/tests/unit/test_architecture.py b/tests/unit/test_architecture.py new file mode 100644 index 0000000..9281e67 --- /dev/null +++ b/tests/unit/test_architecture.py @@ -0,0 +1,197 @@ +"""Architecture and layering rules - see ``docs/architecture.md``. + +The documented dependency stack is ``views -> viewmodels -> services -> core``; +each layer may only import from itself or a lower layer, ``core`` stays free of +Qt/solver/plotting libraries, and the rendering backend is confined to +``views/canvas3d``. These tests never import the application modules; they read +the source tree with ``ast`` so the import discipline can be checked without +executing Qt or the solver. Directories that do not exist yet simply have +nothing to walk, which trivially satisfies a "forbidden import" rule. +""" + +from __future__ import annotations + +import ast +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +SRC = ROOT / "src" / "otko" +CANVAS = SRC / "views" / "canvas3d" + +# Qt binding roots, in every spelling the project might use. +QT_ROOTS = {"PySide6", "PyQt5", "PyQt6", "PySide"} + +# core/ stays pure - no Qt and no solver/mesh/render libraries. +CORE_FORBIDDEN = QT_ROOTS | { + "openseespy", + "opstool", + "shapely", + "vtk", + "pyvista", + "plotly", + "matplotlib", +} + +# Only views/canvas3d/ may touch a 3D or plotting backend. +VTK_ROOTS = {"pyvista", "pyvistaqt", "vtk", "plotly"} + + +def _iter_python_files(root: Path) -> list[Path]: + """Return every ``*.py`` file below ``root``, or ``[]`` if it is absent.""" + if not root.exists(): + return [] + return sorted(root.rglob("*.py")) + + +def _root_imports(path: Path) -> set[str]: + """Return the top-level module names imported by a Python source file. + + Handles ``import X``, ``import X.Y``, ``from X import ...`` and + ``from X.Y import ...``. Relative imports are ignored: they cannot reach + a third-party package. + """ + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + roots: set[str] = set() + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for alias in node.names: + roots.add(alias.name.split(".", 1)[0]) + elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module: + roots.add(node.module.split(".", 1)[0]) + return roots + + +def _is_within(path: Path, directory: Path) -> bool: + """Return True when ``path`` lies inside ``directory`` (lexical check).""" + try: + path.relative_to(directory) + except ValueError: + return False + return True + + +def _violations( + root: Path, + forbidden: set[str], + *, + skip: tuple[Path, ...] = (), + exclude_dir: Path | None = None, +) -> list[str]: + """Return ``": "`` entries for every forbidden import found.""" + found: list[str] = [] + for path in _iter_python_files(root): + if path in skip: + continue + if exclude_dir is not None and _is_within(path, exclude_dir): + continue + for module in sorted(_root_imports(path) & forbidden): + found.append(f"{path.relative_to(ROOT)}: {module}") + return found + + +def test_architecture_core_has_no_qt_or_solver() -> None: + """core/ imports no Qt, solver, mesh, or plotting library.""" + violations = _violations(SRC / "core", CORE_FORBIDDEN) + assert violations == [], f"forbidden imports in core/: {violations}" + + +def test_architecture_services_no_qt_except_worker() -> None: + """No Qt in services/ except services/qt_workers.py.""" + violations = _violations( + SRC / "services", + QT_ROOTS, + skip=(SRC / "services" / "qt_workers.py",), + ) + assert violations == [], f"forbidden Qt imports in services/: {violations}" + + +def test_architecture_views_no_solver() -> None: + """views/ imports neither openseespy nor opstool.""" + violations = _violations(SRC / "views", {"openseespy", "opstool"}) + assert violations == [], f"solver imports in views/: {violations}" + + +def test_no_vtk_import_outside_canvas() -> None: + """3D/plot backends stay under views/canvas3d/.""" + violations = _violations(SRC, VTK_ROOTS, exclude_dir=CANVAS) + assert violations == [], f"backend imports outside views/canvas3d/: {violations}" + + +# The documented dependency stack; ``commands`` sits beside viewmodels (a VM +# creates commands) and ``views`` is the top, so it is excluded from this map. +_LAYER_ORDER = {"core": 0, "services": 1, "viewmodels": 2, "views": 3} + + +def _internal_imports(path: Path) -> set[str]: + """Return the fully-qualified ``otko.*`` modules imported by a source file.""" + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + found: set[str] = set() + for node in ast.walk(tree): + if isinstance(node, ast.Import): + found.update(alias.name for alias in node.names) + elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module: + found.add(node.module) + return {module for module in found if module.startswith("otko.")} + + +def test_architecture_lower_layers_do_not_import_higher() -> None: + """core/services/viewmodels never import a higher layer.""" + violations: list[str] = [] + for path in _iter_python_files(SRC): + layer = path.relative_to(SRC).parts[0] + if layer not in _LAYER_ORDER: + continue + for module in _internal_imports(path): + target = module.split(".")[1] if "." in module else "" + if target in _LAYER_ORDER and _LAYER_ORDER[target] > _LAYER_ORDER[layer]: + violations.append(f"{path.relative_to(ROOT)}: {layer} -> {module}") + assert violations == [], f"lower layer imports a higher layer: {violations}" + + +def test_architecture_viewmodels_do_not_construct_widgets() -> None: + """viewmodels emit signals and import no Qt widgets.""" + violations = _violations(SRC / "viewmodels", {"QtWidgets"}) + assert violations == [], f"viewmodels import widgets: {violations}" + for path in _iter_python_files(SRC / "viewmodels"): + assert "PySide6.QtWidgets" not in path.read_text(encoding="utf-8"), path + + +def test_architecture_report_is_qt_and_solver_free() -> None: + """report generation imports no Qt or solver (vacuous if absent).""" + report = SRC / "services" / "report" + violations = _violations(report, QT_ROOTS | {"openseespy", "opstool"}) + assert violations == [], f"report imports Qt/solver: {violations}" + + +def test_architecture_project_vm_owns_the_undo_stack() -> None: + """Only project_vm.py holds a QUndoStack.""" + holders = [ + path.name + for path in _iter_python_files(SRC / "viewmodels") + if "QUndoStack" in path.read_text(encoding="utf-8") + ] + assert holders == ["project_vm.py"], f"QUndoStack holders: {holders}" + + +def test_architecture_no_environment_configuration() -> None: + """No env-var configuration anywhere in src/otko.""" + offenders = [ + str(path.relative_to(ROOT)) + for path in _iter_python_files(SRC) + if "os.environ" in path.read_text(encoding="utf-8") + or "os.getenv" in path.read_text(encoding="utf-8") + ] + assert offenders == [], f"environment configuration in: {offenders}" + + +def test_architecture_runner_splits_emit_and_run() -> None: + """Model emission and analysis live in separate modules.""" + emit_path = SRC / "services" / "_emit.py" + run_path = SRC / "services" / "_run.py" + assert emit_path.exists() + assert run_path.exists() + emit_source = emit_path.read_text(encoding="utf-8") + run_source = run_path.read_text(encoding="utf-8") + assert "class OpenSeesEmitter" in emit_source + assert "class OpenSeesAnalysisRunner" in run_source + assert "OpenSeesEmitter" in run_source diff --git a/tests/unit/test_ci_config.py b/tests/unit/test_ci_config.py new file mode 100644 index 0000000..063f9b3 --- /dev/null +++ b/tests/unit/test_ci_config.py @@ -0,0 +1,39 @@ +"""Meta-gate checks for the CI workflow and pre-commit hook configuration.""" + +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +CI = ROOT / ".github" / "workflows" / "ci.yml" +PRECOMMIT = ROOT / ".pre-commit-config.yaml" + +HEADLESS_COMMAND = 'pytest tests/unit tests/services -m "not slow"' + + +def test_ci_declares_all_jobs() -> None: + """The workflow exposes the lint/type/test split as separate jobs.""" + text = CI.read_text(encoding="utf-8") + for job in ("lint:", "type:", "test-headless:", "test-gui:", "test-integration:"): + assert f"\n {job}\n" in text, f"missing CI job: {job}" + + +def test_ci_runs_the_headless_command() -> None: + """The headless job runs the unit + services, non-slow command verbatim.""" + assert HEADLESS_COMMAND in CI.read_text(encoding="utf-8") + + +def test_precommit_declares_expected_hooks() -> None: + """The pre-commit config wires up the lint/format/type/hygiene hooks.""" + text = PRECOMMIT.read_text(encoding="utf-8") + for hook in ( + "id: ruff", + "id: ruff-format", + "id: mypy", + "id: trailing-whitespace", + "id: end-of-file-fixer", + "id: check-yaml", + "id: check-added-large-files", + "id: check-merge-conflict", + ): + assert hook in text, f"missing pre-commit hook: {hook}" diff --git a/tests/unit/test_combinations.py b/tests/unit/test_combinations.py new file mode 100644 index 0000000..6a5133a --- /dev/null +++ b/tests/unit/test_combinations.py @@ -0,0 +1,180 @@ +"""Case-result load combinations — core validation, project wiring, combine math.""" + +from __future__ import annotations + +import numpy as np +import pytest + +from otko.core import ( + LoadCombination, + LoadCombinationItem, + Project, + StaticCase, +) +from otko.services.combinations import ( + combine_static_results, + envelope_static_results, + evaluate_combination, +) +from otko.services.results import ModalResults, StaticResults + + +def _static( + case_id: int, disp_val: float, reac_val: float, elem_val: float, n_steps: int = 1 +) -> StaticResults: + return StaticResults( + case_id=case_id, + case_name=f"case-{case_id}", + n_steps=n_steps, + node_disp={1: np.full((n_steps, 2), disp_val)}, + node_reaction={1: np.full((n_steps, 2), reac_val)}, + element_forces={1: np.full((n_steps, 3), elem_val)}, + ) + + +class TestEntity: + def test_duplicate_case_rejected(self) -> None: + with pytest.raises(ValueError, match="duplicate"): + LoadCombination( + id=1, + name="bad", + items=[ + LoadCombinationItem(case_id=1, factor=1.0), + LoadCombinationItem(case_id=1, factor=2.0), + ], + ) + + def test_non_finite_factor_rejected(self) -> None: + with pytest.raises(ValueError, match="finite"): + LoadCombination(id=1, items=[LoadCombinationItem(case_id=1, factor=float("inf"))]) + + def test_empty_items_rejected(self) -> None: + with pytest.raises(ValueError): + LoadCombination(id=1, items=[]) + + def test_round_trip(self) -> None: + combo = LoadCombination( + id=3, + name="1.2D+1.6L", + kind="Linear", + items=[ + LoadCombinationItem(case_id=1, factor=1.2), + LoadCombinationItem(case_id=2, factor=1.6), + ], + ) + assert LoadCombination.model_validate(combo.model_dump()) == combo + assert LoadCombination.model_validate_json(combo.model_dump_json()) == combo + + +class TestProject: + def test_next_id_and_lookup(self) -> None: + proj = Project() + assert proj.next_combination_id() == 1 + proj.combinations.append(LoadCombination(id=1, items=[LoadCombinationItem(case_id=1)])) + assert proj.next_combination_id() == 2 + assert proj.combination(1).id == 1 + with pytest.raises(KeyError): + proj.combination(99) + + def test_duplicate_ids_rejected(self) -> None: + with pytest.raises(ValueError, match="Duplicate combination"): + Project( + combinations=[ + LoadCombination(id=1, items=[LoadCombinationItem(case_id=1)]), + LoadCombination(id=1, items=[LoadCombinationItem(case_id=2)]), + ] + ) + + def test_validate_references_missing_case(self) -> None: + proj = Project( + analyses=[StaticCase(id=1, pattern_ids=[1])], + combinations=[LoadCombination(id=1, items=[LoadCombinationItem(case_id=9)])], + ) + with pytest.raises(ValueError, match="missing analysis case 9"): + proj.validate_references() + + def test_persistence_round_trip(self, tmp_path) -> None: # type: ignore[no-untyped-def] + from otko.services import load_project, save_project + + proj = Project( + analyses=[StaticCase(id=1, pattern_ids=[1])], + combinations=[ + LoadCombination( + id=1, + name="combo", + items=[LoadCombinationItem(case_id=1, factor=1.2)], + ) + ], + ) + path = save_project(proj, tmp_path / "c.osmodel") + assert load_project(path).combinations[0].name == "combo" + + +class TestCombine: + def test_linear_weighted_sum(self) -> None: + out = combine_static_results( + [_static(1, 1.0, 10.0, 100.0), _static(2, 2.0, 20.0, 200.0)], + [1.2, 1.6], + case_id=7, + case_name="combo", + ) + assert out.case_id == 7 + assert out.node_disp[1][0, 0] == pytest.approx(1.2 * 1.0 + 1.6 * 2.0) + assert out.node_reaction[1][0, 1] == pytest.approx(1.2 * 10.0 + 1.6 * 20.0) + assert out.element_forces[1][0, 2] == pytest.approx(1.2 * 100.0 + 1.6 * 200.0) + + def test_step_mismatch_rejected(self) -> None: + with pytest.raises(ValueError, match="Step-count"): + combine_static_results( + [_static(1, 1.0, 1.0, 1.0, n_steps=1), _static(2, 1.0, 1.0, 1.0, n_steps=2)], + [1.0, 1.0], + case_id=1, + case_name="x", + ) + + def test_length_mismatch_rejected(self) -> None: + with pytest.raises(ValueError, match="length mismatch"): + combine_static_results([_static(1, 1, 1, 1)], [1.0, 2.0], case_id=1, case_name="x") + + +class TestEnvelope: + def test_max_min(self) -> None: + out = envelope_static_results( + [_static(1, 1.0, 10.0, 5.0), _static(2, -4.0, -20.0, 7.0)], + [1.0, 1.0], + case_id=3, + case_name="env", + ) + assert out.node_disp_max[1][0, 0] == pytest.approx(1.0) + assert out.node_disp_min[1][0, 0] == pytest.approx(-4.0) + assert out.node_reaction_max[1][0, 0] == pytest.approx(10.0) + assert out.node_reaction_min[1][0, 0] == pytest.approx(-20.0) + assert out.element_forces_max[1][0, 0] == pytest.approx(7.0) + assert out.element_forces_min[1][0, 0] == pytest.approx(5.0) + + +class TestEvaluate: + def test_linear_dispatch(self) -> None: + combo = LoadCombination( + id=5, + name="c", + kind="Linear", + items=[ + LoadCombinationItem(case_id=1, factor=2.0), + LoadCombinationItem(case_id=2, factor=3.0), + ], + ) + out = evaluate_combination({1: _static(1, 1, 1, 1), 2: _static(2, 1, 1, 1)}, combo) + assert isinstance(out, StaticResults) + assert out.node_disp[1][0, 0] == pytest.approx(5.0) + + def test_missing_results_keyerror(self) -> None: + combo = LoadCombination(id=1, items=[LoadCombinationItem(case_id=2)]) + with pytest.raises(KeyError, match="no results for case 2"): + evaluate_combination({1: _static(1, 1, 1, 1)}, combo) + + def test_non_static_typeerror(self) -> None: + combo = LoadCombination(id=1, items=[LoadCombinationItem(case_id=1)]) + modal = ModalResults(case_id=1, case_name="m", eigenvalues=np.array([1.0]), mode_shapes={}) + with pytest.raises(TypeError, match="not StaticResults"): + evaluate_combination({1: modal}, combo) diff --git a/tests/unit/test_diagram_renderer.py b/tests/unit/test_diagram_renderer.py index a1cd1d1..425606b 100644 --- a/tests/unit/test_diagram_renderer.py +++ b/tests/unit/test_diagram_renderer.py @@ -15,21 +15,21 @@ import pytest # Force pyvista off-screen before any pyvista import in this module's chain. os.environ.setdefault("PYVISTA_OFF_SCREEN", "true") -import pyvista as pv # noqa: E402 +import pyvista as pv -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticBeamColumn, ElasticSection, Node, Project, ) -from otko.services.element_forces import ( # noqa: E402 +from otko.services.element_forces import ( DiagramData, ForceComponent, extract_diagram_data, ) -from otko.services.results import StaticResults # noqa: E402 -from otko.views.canvas3d.diagram_renderer import DiagramRenderer # noqa: E402 +from otko.services.results import StaticResults +from otko.views.canvas3d.diagram_renderer import DiagramRenderer pv.OFF_SCREEN = True @@ -59,12 +59,9 @@ def project_3d() -> Project: @pytest.fixture def static_results() -> StaticResults: - f10 = np.array([[100.0, 5.0, 0.0, 0.0, 0.0, 9.0, - -100.0, -5.0, 0.0, 0.0, 0.0, -9.0]]) - f20 = np.array([[-50.0, 2.0, 0.0, 0.0, 0.0, 3.0, - 50.0, -2.0, 0.0, 0.0, 0.0, -3.0]]) - return StaticResults(case_id=1, case_name="t", n_steps=1, - element_forces={10: f10, 20: f20}) + f10 = np.array([[100.0, 5.0, 0.0, 0.0, 0.0, 9.0, -100.0, -5.0, 0.0, 0.0, 0.0, -9.0]]) + f20 = np.array([[-50.0, 2.0, 0.0, 0.0, 0.0, 3.0, 50.0, -2.0, 0.0, 0.0, 0.0, -3.0]]) + return StaticResults(case_id=1, case_name="t", n_steps=1, element_forces={10: f10, 20: f20}) def test_render_axial_creates_actor(offscreen_plotter, project_3d, static_results) -> None: # type: ignore[no-untyped-def] @@ -93,14 +90,17 @@ def test_render_empty_data_does_not_create_actor(offscreen_plotter, project_3d) empty = DiagramData( component=ForceComponent.N, element_ids=np.empty(0, dtype=int), - values_i=np.empty(0), values_j=np.empty(0), abs_max=0.0, + values_i=np.empty(0), + values_j=np.empty(0), + abs_max=0.0, ) r.render(project_3d, empty, scale=1.0) assert r._actor is None def test_render_zero_magnitude_data_does_not_create_actor( - offscreen_plotter, project_3d, + offscreen_plotter, + project_3d, ) -> None: # type: ignore[no-untyped-def] """A diagram for a component that's identically zero shouldn't render an empty mesh + scalar bar — that's misleading visual noise.""" @@ -108,7 +108,9 @@ def test_render_zero_magnitude_data_does_not_create_actor( zero_for_two_elems = DiagramData( component=ForceComponent.T, element_ids=np.array([10, 20], dtype=int), - values_i=np.zeros(2), values_j=np.zeros(2), abs_max=0.0, + values_i=np.zeros(2), + values_j=np.zeros(2), + abs_max=0.0, ) r.render(project_3d, zero_for_two_elems, scale=1.0) assert r._actor is None diff --git a/tests/unit/test_docs.py b/tests/unit/test_docs.py new file mode 100644 index 0000000..f8c78e0 --- /dev/null +++ b/tests/unit/test_docs.py @@ -0,0 +1,51 @@ +"""Documentation gates — assert the user-facing docs keep their key content. + +Pure pathlib; no Qt or solver imports so this stays part of the fast unit +suite. +""" + +from __future__ import annotations + +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] +DOCS_DIR = REPO_ROOT / "docs" + + +def _text(path: Path) -> str: + """Return a file's UTF-8 text with normalized line endings.""" + return path.read_text(encoding="utf-8").replace("\r\n", "\n") + + +def test_quick_guide_exists_and_covers_core_topics() -> None: + quick_guide = DOCS_DIR / "QUICK_GUIDE.md" + assert quick_guide.is_file(), "docs/QUICK_GUIDE.md is missing" + + lowered = _text(quick_guide).lower() + for topic in ("cantilever", "report", "modal", "units", "undo"): + assert topic in lowered, f"QUICK_GUIDE.md is missing {topic!r}" + + +def test_contributing_describes_this_repo() -> None: + text = _text(REPO_ROOT / "CONTRIBUTING.md") + + for required in ( + "views", + "viewmodels", + "services", + "core", + "commands", + "Conventional Commits", + "feat:", + "main", + "pre-commit install", + 'pip install -e ".[gui,dev]"', + "python -m otko", + 'pytest -m "not slow"', + ): + assert required in text, f"CONTRIBUTING.md is missing {required!r}" + + +def test_readme_links_quick_guide() -> None: + readme = _text(REPO_ROOT / "README.md") + assert "docs/QUICK_GUIDE.md" in readme diff --git a/tests/unit/test_elements.py b/tests/unit/test_elements.py index 11987c6..e3c971a 100644 --- a/tests/unit/test_elements.py +++ b/tests/unit/test_elements.py @@ -6,8 +6,8 @@ import pytest from pydantic import TypeAdapter, ValidationError from otko.core import ( - Element, ElasticBeamColumn, + Element, ForceBeamColumn, TrussElement, ZeroLengthElement, diff --git a/tests/unit/test_formatting.py b/tests/unit/test_formatting.py new file mode 100644 index 0000000..bc1c6db --- /dev/null +++ b/tests/unit/test_formatting.py @@ -0,0 +1,51 @@ +"""Unit tests for :mod:`otko.views.formatting` (no Qt required).""" + +from __future__ import annotations + +import pytest + +from otko.core import UnitSystem +from otko.views.formatting import ( + DOF_LABELS, + MASS_LABELS, + RESTRAINT_LABELS, + format_number, + parse_float, + parse_int, + unit_label, +) + + +def test_label_tuples() -> None: + assert DOF_LABELS == ("Ux", "Uy", "Uz", "Rx", "Ry", "Rz") + assert MASS_LABELS == ("Mx", "My", "Mz", "Ixx", "Iyy", "Izz") + assert RESTRAINT_LABELS == ("Ux", "Uy", "Uz", "Rx", "Ry", "Rz") + + +def test_format_number() -> None: + assert format_number(50.0) == "50" + assert format_number(1.5) == "1.5" + + +def test_parse_float_required() -> None: + with pytest.raises(ValueError): + parse_float("", "x") + + +def test_parse_float_allow_empty() -> None: + assert parse_float("", "x", allow_empty=True) == 0.0 + + +def test_parse_int_rejects_fractional() -> None: + with pytest.raises(ValueError): + parse_int("1.5", "x") + + +def test_unit_label() -> None: + assert unit_label("force", UnitSystem.SI_M_N) == "N" + assert unit_label("moment", UnitSystem.US_FT_KIP) == "kip·ft" + + +def test_unit_label_unknown_quantity() -> None: + with pytest.raises(ValueError): + unit_label("temperature", UnitSystem.SI_M_N) diff --git a/tests/unit/test_grid_system.py b/tests/unit/test_grid_system.py index 5461d9b..6688dce 100644 --- a/tests/unit/test_grid_system.py +++ b/tests/unit/test_grid_system.py @@ -2,7 +2,6 @@ from __future__ import annotations -import math from pathlib import Path import pytest @@ -86,6 +85,7 @@ def test_dialog_parse_spacings_formats() -> None: _coords_from_spacings, _parse_spacings, ) + # Blank → no lines. assert _parse_spacings("") == [] # Single integer → N-1 unit spacings. @@ -102,12 +102,13 @@ def test_dialog_parse_spacings_formats() -> None: def test_add_node_dialog_snaps(qtbot) -> None: # type: ignore[no-untyped-def] """AddNodeDialog snaps to nearest grid line when the flag is set.""" from otko.views.dialogs.add_node import AddNodeDialog + grid = GridSystem(x_lines=[0.0, 3.0, 6.0], y_lines=[0.0, 4.0], z_lines=[0.0]) dlg = AddNodeDialog(next_node_id=1, grid=grid, ndm=3) qtbot.addWidget(dlg) - dlg._x.setValue(3.4) # → should snap to 3.0 - dlg._y.setValue(3.9) # → should snap to 4.0 - dlg._z.setValue(-0.3) # → should snap to 0.0 + dlg._x.setValue(3.4) # → should snap to 3.0 + dlg._y.setValue(3.9) # → should snap to 4.0 + dlg._z.setValue(-0.3) # → should snap to 0.0 dlg._snap_cb.setChecked(True) node = dlg.node() assert node.coords == (3.0, 4.0, 0.0) @@ -116,10 +117,13 @@ def test_add_node_dialog_snaps(qtbot) -> None: # type: ignore[no-untyped-def] def test_add_node_dialog_no_snap(qtbot) -> None: # type: ignore[no-untyped-def] """Without the snap flag, AddNodeDialog preserves entered coordinates.""" from otko.views.dialogs.add_node import AddNodeDialog + grid = GridSystem(x_lines=[0.0, 3.0], y_lines=[0.0]) dlg = AddNodeDialog(next_node_id=1, grid=grid, ndm=3) qtbot.addWidget(dlg) - dlg._x.setValue(1.7); dlg._y.setValue(0.2); dlg._z.setValue(5.5) + dlg._x.setValue(1.7) + dlg._y.setValue(0.2) + dlg._z.setValue(5.5) dlg._snap_cb.setChecked(False) node = dlg.node() assert node.coords == pytest.approx((1.7, 0.2, 5.5)) @@ -169,7 +173,7 @@ def test_coord_system_round_trip_world_local() -> None: p_local = (1.5, -0.5, 2.0) p_world = cs.local_to_world(p_local) p_back = cs.world_to_local(p_world) - for a, b in zip(p_local, p_back): + for a, b in zip(p_local, p_back, strict=False): assert a == pytest.approx(b, abs=1e-9) @@ -205,7 +209,8 @@ def test_multiple_coord_systems_round_trip(tmp_path: Path) -> None: CoordinateGridSystem( name="Floor2", coord=CoordinateSystem( - origin=(0, 0, 3.5), rotation_deg=(0, 0, 30), + origin=(0, 0, 3.5), + rotation_deg=(0, 0, 30), ), grid=GridSystem(x_lines=[0.0, 6.0], y_lines=[0.0, 4.0]), ), diff --git a/tests/unit/test_materials.py b/tests/unit/test_materials.py index 36086a5..43bde52 100644 --- a/tests/unit/test_materials.py +++ b/tests/unit/test_materials.py @@ -37,8 +37,14 @@ class TestSteel01: class TestConcrete02: def test_lambda_alias(self) -> None: c = Concrete02( - id=1, fpc=-30e6, epsc0=-0.002, fpcu=-15e6, epsU=-0.005, - ft=3e6, Ets=2e9, **{"lambda": 0.1}, + id=1, + fpc=-30e6, + epsc0=-0.002, + fpcu=-15e6, + epsU=-0.005, + ft=3e6, + Ets=2e9, + **{"lambda": 0.1}, ) assert c.lambda_ == 0.1 # Round-trip should preserve the alias. @@ -49,8 +55,14 @@ class TestConcrete02: def test_negative_signs_enforced(self) -> None: with pytest.raises(ValidationError): Concrete02( - id=1, fpc=30e6, epsc0=-0.002, fpcu=-15e6, epsU=-0.005, - ft=3e6, Ets=2e9, **{"lambda": 0.1}, + id=1, + fpc=30e6, + epsc0=-0.002, + fpcu=-15e6, + epsU=-0.005, + ft=3e6, + Ets=2e9, + **{"lambda": 0.1}, ) @@ -66,7 +78,8 @@ class TestHystereticSM: # The wire-rope axial backbone: 7 positive points (to ~69 kN), an # independent, softer negative (compression) envelope. m = HystereticSM( - id=1, name="iso-axial", + id=1, + name="iso-axial", pos_env=[(1.57, 0.00207), (3.0, 0.00436), (69.1, 0.0399)], neg_env=[(-1.4, -0.00057), (-15.31, -0.0483)], ) diff --git a/tests/unit/test_meta_gates.py b/tests/unit/test_meta_gates.py new file mode 100644 index 0000000..67ee070 --- /dev/null +++ b/tests/unit/test_meta_gates.py @@ -0,0 +1,29 @@ +"""Meta-gates tying the test markers to the CI commands that consume them.""" + +from __future__ import annotations + +from pathlib import Path + +try: + import tomllib +except ModuleNotFoundError: # Python < 3.11 + import tomli as tomllib + +ROOT = Path(__file__).resolve().parents[2] +PYPROJECT = ROOT / "pyproject.toml" +CI = ROOT / ".github" / "workflows" / "ci.yml" + +HEADLESS_COMMAND = 'pytest tests/unit tests/services -m "not slow"' + + +def test_slow_marker_is_declared() -> None: + """``[tool.pytest.ini_options]`` registers the ``slow`` marker.""" + with PYPROJECT.open("rb") as handle: + config = tomllib.load(handle) + markers = config["tool"]["pytest"]["ini_options"]["markers"] + assert any(marker.startswith("slow:") for marker in markers), markers + + +def test_ci_runs_the_headless_command() -> None: + """The headless CI command matches the marker the suite expects.""" + assert HEADLESS_COMMAND in CI.read_text(encoding="utf-8") diff --git a/tests/unit/test_node.py b/tests/unit/test_node.py index fbfbe22..a2e9c74 100644 --- a/tests/unit/test_node.py +++ b/tests/unit/test_node.py @@ -19,8 +19,9 @@ class TestNodeConstruction: assert not n.is_restrained def test_with_name_and_mass(self) -> None: - n = Node(id=42, name="A1", coords=(1.0, 2.0, 3.0), - mass=(100.0, 100.0, 100.0, 0.0, 0.0, 0.0)) + n = Node( + id=42, name="A1", coords=(1.0, 2.0, 3.0), mass=(100.0, 100.0, 100.0, 0.0, 0.0, 0.0) + ) assert n.name == "A1" assert n.mass[0] == 100.0 diff --git a/tests/unit/test_packaging.py b/tests/unit/test_packaging.py new file mode 100644 index 0000000..7b16ea4 --- /dev/null +++ b/tests/unit/test_packaging.py @@ -0,0 +1,72 @@ +"""Packaging invariants — version/solver single-sourcing and dependency hygiene. + +Pure static checks over the repository files. No imports of :mod:`otko`, +Qt or the solver stack, so this test runs anywhere (including Windows 3.12). +""" + +from __future__ import annotations + +import re +from pathlib import Path + +try: # Python 3.11+ + import tomllib +except ModuleNotFoundError: # pragma: no cover - Python 3.10 backport + import tomli as tomllib + +REPO_ROOT = Path(__file__).resolve().parents[2] +SRC_ROOT = REPO_ROOT / "src" / "otko" +PYPROJECT = REPO_ROOT / "pyproject.toml" + +_VERSION_RE = re.compile(r"^__version__\s*=", re.MULTILINE) + + +def _pyproject() -> dict: + with PYPROJECT.open("rb") as handle: + return tomllib.load(handle) + + +def _src_files() -> list[Path]: + return sorted(SRC_ROOT.rglob("*.py")) + + +def _dep_name(requirement: str) -> str: + """Return the lowercased distribution name from a PEP 508 requirement.""" + match = re.match(r"^\s*([A-Za-z0-9][A-Za-z0-9._-]*)", requirement) + return match.group(1).lower().replace("_", "-") if match else "" + + +def test_console_script_entrypoint() -> None: + assert _pyproject()["project"]["scripts"]["otko"] == "otko.__main__:main" + + +def test_project_version_is_dynamic() -> None: + project = _pyproject()["project"] + assert "version" not in project + assert project["dynamic"] == ["version"] + + +def test_single_version_assignment() -> None: + hits = [path for path in _src_files() if _VERSION_RE.search(path.read_text(encoding="utf-8"))] + assert len(hits) == 1, f"expected exactly one __version__ assignment, got {hits}" + assert hits[0] == SRC_ROOT / "_const.py" + + +def test_solver_version_literal_only_in_const() -> None: + hits = [path for path in _src_files() if "3.8.0.0" in path.read_text(encoding="utf-8")] + assert hits == [SRC_ROOT / "_const.py"], f"unexpected 3.8.0.0 occurrences: {hits}" + + +def test_ruff_line_length_and_ignore() -> None: + ruff = _pyproject()["tool"]["ruff"] + assert ruff["line-length"] == 100 + assert "E501" in ruff["lint"]["ignore"] + + +def test_no_phantom_scipy_pandas_dependencies() -> None: + project = _pyproject()["project"] + groups = [project.get("dependencies", [])] + groups.extend(project.get("optional-dependencies", {}).values()) + declared = {_dep_name(req) for group in groups for req in group} + assert "scipy" not in declared + assert "pandas" not in declared diff --git a/tests/unit/test_phase8a.py b/tests/unit/test_phase8a.py index 033ef00..c9a3842 100644 --- a/tests/unit/test_phase8a.py +++ b/tests/unit/test_phase8a.py @@ -8,7 +8,6 @@ from __future__ import annotations -import numpy as np import pytest from otko.commands.nodes import SetMassCommand @@ -67,7 +66,8 @@ def test_uniform_element_load_schema() -> None: def test_plain_load_pattern_accepts_element_loads() -> None: pat = PlainLoadPattern( - id=1, time_series_id=1, + id=1, + time_series_id=1, nodal_loads=[NodalLoad(node_id=1, forces=(0, 0, 0, 0, 0, 0))], element_loads=[UniformElementLoad(element_id=10, wy=-500.0)], ) @@ -77,10 +77,13 @@ def test_plain_load_pattern_accepts_element_loads() -> None: def test_project_with_element_loads_round_trips(tiny_project: Project, tmp_path) -> None: # type: ignore[no-untyped-def] tiny_project.time_series.append(LinearTimeSeries(id=1, name="Ramp")) - tiny_project.load_patterns.append(PlainLoadPattern( - id=1, time_series_id=1, - element_loads=[UniformElementLoad(element_id=10, wy=-1500.0, wz=10.0)], - )) + tiny_project.load_patterns.append( + PlainLoadPattern( + id=1, + time_series_id=1, + element_loads=[UniformElementLoad(element_id=10, wy=-1500.0, wz=10.0)], + ) + ) path = tmp_path / "p.osmodel" save_project(tiny_project, path) @@ -121,7 +124,7 @@ def test_set_mass_command_leaves_unselected_alone(tiny_project: Project) -> None cmd.redo() assert tiny_project.nodes[0].mass[0] == 99.0 - assert tiny_project.nodes[1].mass[0] == 5.0 # unchanged + assert tiny_project.nodes[1].mass[0] == 5.0 # unchanged cmd.undo() assert tiny_project.nodes[0].mass[0] == 0.0 @@ -136,16 +139,21 @@ def test_transient_case_rayleigh_defaults_to_zero() -> None: def test_transient_case_rayleigh_round_trip(tmp_path) -> None: # type: ignore[no-untyped-def] p = Project( - nodes=[Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), - Node(id=2, coords=(1, 0, 0))], + nodes=[Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), Node(id=2, coords=(1, 0, 0))], sections=[ElasticSection(id=1, E=2e11, A=0.01, Iz=1e-5, Iy=1e-5)], elements=[ElasticBeamColumn(id=10, nodes=(1, 2), section_id=1)], time_series=[LinearTimeSeries(id=1, name="Ramp")], load_patterns=[PlainLoadPattern(id=1, time_series_id=1)], - analyses=[TransientCase( - id=1, pattern_ids=[1], dt=0.01, n_steps=50, - rayleigh_alpha_m=0.5, rayleigh_beta_k=1.5e-4, - )], + analyses=[ + TransientCase( + id=1, + pattern_ids=[1], + dt=0.01, + n_steps=50, + rayleigh_alpha_m=0.5, + rayleigh_beta_k=1.5e-4, + ) + ], ) path = tmp_path / "r.osmodel" save_project(p, path) @@ -165,16 +173,20 @@ def test_transient_case_rayleigh_init_comm_defaults() -> None: def test_transient_case_rayleigh_init_round_trip(tmp_path) -> None: # type: ignore[no-untyped-def] p = Project( - nodes=[Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), - Node(id=2, coords=(1, 0, 0))], + nodes=[Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), Node(id=2, coords=(1, 0, 0))], sections=[ElasticSection(id=1, E=2e11, A=0.01, Iz=1e-5, Iy=1e-5)], elements=[ElasticBeamColumn(id=10, nodes=(1, 2), section_id=1)], time_series=[LinearTimeSeries(id=1, name="Ramp")], load_patterns=[PlainLoadPattern(id=1, time_series_id=1)], - analyses=[TransientCase( - id=1, pattern_ids=[1], dt=0.01, n_steps=50, - rayleigh_beta_k_init=0.01309796, - )], + analyses=[ + TransientCase( + id=1, + pattern_ids=[1], + dt=0.01, + n_steps=50, + rayleigh_beta_k_init=0.01309796, + ) + ], ) path = tmp_path / "rk.osmodel" save_project(p, path) @@ -187,14 +199,13 @@ def test_zero_length_do_rayleigh_default_and_round_trip(tmp_path) -> None: # ty from otko.core import ElasticUniaxial, ZeroLengthElement p = Project( - ndm=3, ndf=6, - nodes=[Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), - Node(id=2, coords=(0, 0, 0))], + ndm=3, + ndf=6, + nodes=[Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), Node(id=2, coords=(0, 0, 0))], materials=[ElasticUniaxial(id=1, E=1000.0)], elements=[ ZeroLengthElement(id=1, nodes=(1, 2), material_ids=(1,), dofs=(1,)), - ZeroLengthElement(id=2, nodes=(1, 2), material_ids=(1,), dofs=(1,), - do_rayleigh=True), + ZeroLengthElement(id=2, nodes=(1, 2), material_ids=(1,), dofs=(1,), do_rayleigh=True), ], ) assert p.elements[0].do_rayleigh is False # default off (unchanged emission) @@ -217,6 +228,7 @@ class _RecordingOps: def rec(*args, **kwargs): # type: ignore[no-untyped-def] self.calls.append((name, args, kwargs)) return None + return rec @@ -224,8 +236,10 @@ def _transient_setup_rayleigh_calls(case: TransientCase) -> list[tuple]: from otko.services import OpenSeesRunner p = Project( - nodes=[Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), - Node(id=2, coords=(1, 0, 0), mass=(1.0,) * 3 + (0.0,) * 3)], + nodes=[ + Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), + Node(id=2, coords=(1, 0, 0), mass=(1.0,) * 3 + (0.0,) * 3), + ], sections=[ElasticSection(id=1, E=2e11, A=0.01, Iz=1e-5, Iy=1e-5)], elements=[ElasticBeamColumn(id=10, nodes=(1, 2), section_id=1)], time_series=[LinearTimeSeries(id=1, name="Ramp")], @@ -242,7 +256,10 @@ def test_kinit_rayleigh_issues_exactly_one_call_in_beta_kinit_slot() -> None: """A Kinit (initial-stiffness) βK is emitted ONCE as ``rayleigh 0 0 βKinit 0`` — slot 3 — the single-command requirement (a second call would replace it).""" case = TransientCase( - id=1, pattern_ids=[1], dt=0.01, n_steps=10, + id=1, + pattern_ids=[1], + dt=0.01, + n_steps=10, rayleigh_beta_k_init=0.01309796, ) rcalls = _transient_setup_rayleigh_calls(case) @@ -253,7 +270,11 @@ def test_kinit_rayleigh_issues_exactly_one_call_in_beta_kinit_slot() -> None: def test_current_k_rayleigh_unchanged_slot2() -> None: """The classical current-K βK still emits in slot 2 (backward compatible).""" case = TransientCase( - id=1, pattern_ids=[1], dt=0.01, n_steps=10, rayleigh_beta_k=0.002, + id=1, + pattern_ids=[1], + dt=0.01, + n_steps=10, + rayleigh_beta_k=0.002, ) rcalls = _transient_setup_rayleigh_calls(case) assert len(rcalls) == 1 @@ -273,14 +294,13 @@ def test_zero_length_do_rayleigh_emits_flag() -> None: from otko.services import OpenSeesRunner p = Project( - ndm=3, ndf=6, - nodes=[Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), - Node(id=2, coords=(0, 0, 0))], + ndm=3, + ndf=6, + nodes=[Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), Node(id=2, coords=(0, 0, 0))], materials=[ElasticUniaxial(id=1, E=1000.0)], elements=[ ZeroLengthElement(id=1, nodes=(1, 2), material_ids=(1,), dofs=(1,)), - ZeroLengthElement(id=2, nodes=(1, 2), material_ids=(1,), dofs=(1,), - do_rayleigh=True), + ZeroLengthElement(id=2, nodes=(1, 2), material_ids=(1,), dofs=(1,), do_rayleigh=True), ], ) rec = _RecordingOps() diff --git a/tests/unit/test_phase8b.py b/tests/unit/test_phase8b.py index c398b3f..41d50fb 100644 --- a/tests/unit/test_phase8b.py +++ b/tests/unit/test_phase8b.py @@ -29,33 +29,61 @@ from otko.services import load_project, save_project # ── HystereticMaterial ─────────────────────────────────────────────── def test_hysteretic_material_schema() -> None: mat = HystereticMaterial( - id=1, name="HingeMat", - s1p=100.0, e1p=0.001, s2p=200.0, e2p=0.01, s3p=210.0, e3p=0.05, - s1n=-100.0, e1n=-0.001, s2n=-200.0, e2n=-0.01, s3n=-210.0, e3n=-0.05, + id=1, + name="HingeMat", + s1p=100.0, + e1p=0.001, + s2p=200.0, + e2p=0.01, + s3p=210.0, + e3p=0.05, + s1n=-100.0, + e1n=-0.001, + s2n=-200.0, + e2n=-0.01, + s3n=-210.0, + e3n=-0.05, ) assert mat.type == "Hysteretic" assert mat.s1p == 100.0 - assert mat.px == 1.0 # default pinching factor - assert mat.beta == 0.0 # no degradation + assert mat.px == 1.0 # default pinching factor + assert mat.beta == 0.0 # no degradation def test_hysteretic_positive_envelope_must_be_positive() -> None: with pytest.raises(ValueError): HystereticMaterial( - id=1, s1p=-1.0, e1p=0.001, s2p=200.0, e2p=0.01, - s3p=210.0, e3p=0.05, - s1n=-100.0, e1n=-0.001, s2n=-200.0, e2n=-0.01, - s3n=-210.0, e3n=-0.05, + id=1, + s1p=-1.0, + e1p=0.001, + s2p=200.0, + e2p=0.01, + s3p=210.0, + e3p=0.05, + s1n=-100.0, + e1n=-0.001, + s2n=-200.0, + e2n=-0.01, + s3n=-210.0, + e3n=-0.05, ) # ── BeamWithHingesElement ──────────────────────────────────────────── def test_beam_with_hinges_3d_schema() -> None: el = BeamWithHingesElement( - id=1, nodes=(1, 2), - section_i_id=10, section_j_id=10, - lp_i=0.1, lp_j=0.1, - E=2e11, A=0.01, Iz=1e-5, Iy=1e-5, G=8e10, J=1e-6, + id=1, + nodes=(1, 2), + section_i_id=10, + section_j_id=10, + lp_i=0.1, + lp_j=0.1, + E=2e11, + A=0.01, + Iz=1e-5, + Iy=1e-5, + G=8e10, + J=1e-6, ) assert el.type == "BeamWithHinges" assert el.lp_i == 0.1 @@ -64,10 +92,15 @@ def test_beam_with_hinges_3d_schema() -> None: def test_beam_with_hinges_2d_schema_has_optional_3d_fields() -> None: el = BeamWithHingesElement( - id=1, nodes=(1, 2), - section_i_id=10, section_j_id=10, - lp_i=0.1, lp_j=0.1, - E=2e11, A=0.01, Iz=1e-5, + id=1, + nodes=(1, 2), + section_i_id=10, + section_j_id=10, + lp_i=0.1, + lp_j=0.1, + E=2e11, + A=0.01, + Iz=1e-5, ) assert el.Iy is None assert el.G is None @@ -77,55 +110,72 @@ def test_beam_with_hinges_2d_schema_has_optional_3d_fields() -> None: # ── PushoverCase ───────────────────────────────────────────────────── def test_pushover_case_schema() -> None: c = PushoverCase( - id=1, pattern_ids=[1], - control_node=2, control_dof=1, - target_disp=0.1, step_size=0.001, + id=1, + pattern_ids=[1], + control_node=2, + control_dof=1, + target_disp=0.1, + step_size=0.001, ) assert c.type == "Pushover" assert c.control_node == 2 assert c.step_size == 0.001 - assert c.base_nodes == [] # defaults to empty → all supports + assert c.base_nodes == [] # defaults to empty → all supports def test_pushover_case_round_trip(tmp_path) -> None: # type: ignore[no-untyped-def] p = Project( meta=ProjectMeta(name="PO test", units=UnitSystem.SI_M_N), - ndm=3, ndf=6, + ndm=3, + ndf=6, nodes=[ Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), - Node(id=2, coords=(0, 0, 3.0), - mass=(1e3, 1e3, 1e3, 0, 0, 0)), + Node(id=2, coords=(0, 0, 3.0), mass=(1e3, 1e3, 1e3, 0, 0, 0)), ], materials=[ HystereticMaterial( - id=1, name="Hinge", - s1p=100e3, e1p=0.001, - s2p=150e3, e2p=0.01, - s3p=160e3, e3p=0.05, - s1n=-100e3, e1n=-0.001, - s2n=-150e3, e2n=-0.01, - s3n=-160e3, e3n=-0.05, + id=1, + name="Hinge", + s1p=100e3, + e1p=0.001, + s2p=150e3, + e2p=0.01, + s3p=160e3, + e3p=0.05, + s1n=-100e3, + e1n=-0.001, + s2n=-150e3, + e2n=-0.01, + s3n=-160e3, + e3n=-0.05, ), ], sections=[ - ElasticSection(id=1, E=2e11, A=0.01, Iz=1e-5, Iy=1e-5, - G=8e10, J=1e-6), + ElasticSection(id=1, E=2e11, A=0.01, Iz=1e-5, Iy=1e-5, G=8e10, J=1e-6), ], elements=[ ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), ], time_series=[LinearTimeSeries(id=1, name="Ramp")], - load_patterns=[PlainLoadPattern( - id=1, time_series_id=1, - nodal_loads=[NodalLoad(node_id=2, forces=(1.0, 0, 0, 0, 0, 0))], - )], - analyses=[PushoverCase( - id=1, name="Push X", - pattern_ids=[1], - control_node=2, control_dof=1, - target_disp=0.05, step_size=0.001, - base_nodes=[1], - )], + load_patterns=[ + PlainLoadPattern( + id=1, + time_series_id=1, + nodal_loads=[NodalLoad(node_id=2, forces=(1.0, 0, 0, 0, 0, 0))], + ) + ], + analyses=[ + PushoverCase( + id=1, + name="Push X", + pattern_ids=[1], + control_node=2, + control_dof=1, + target_disp=0.05, + step_size=0.001, + base_nodes=[1], + ) + ], ) path = tmp_path / "po.osmodel" save_project(p, path) @@ -145,21 +195,29 @@ def test_pushover_case_round_trip(tmp_path) -> None: # type: ignore[no-untyped- def test_beam_with_hinges_round_trip(tmp_path) -> None: # type: ignore[no-untyped-def] p = Project( meta=ProjectMeta(name="BWH", units=UnitSystem.SI_M_N), - ndm=3, ndf=6, + ndm=3, + ndf=6, nodes=[ Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), Node(id=2, coords=(0, 0, 3.0)), ], sections=[ - ElasticSection(id=10, E=2e11, A=0.01, Iz=1e-5, Iy=1e-5, - G=8e10, J=1e-6), + ElasticSection(id=10, E=2e11, A=0.01, Iz=1e-5, Iy=1e-5, G=8e10, J=1e-6), ], elements=[ BeamWithHingesElement( - id=1, nodes=(1, 2), - section_i_id=10, section_j_id=10, - lp_i=0.3, lp_j=0.3, - E=2e11, A=0.01, Iz=1e-5, Iy=1e-5, G=8e10, J=1e-6, + id=1, + nodes=(1, 2), + section_i_id=10, + section_j_id=10, + lp_i=0.3, + lp_j=0.3, + E=2e11, + A=0.01, + Iz=1e-5, + Iy=1e-5, + G=8e10, + J=1e-6, ), ], ) diff --git a/tests/unit/test_phase8c.py b/tests/unit/test_phase8c.py index c49f764..557ed34 100644 --- a/tests/unit/test_phase8c.py +++ b/tests/unit/test_phase8c.py @@ -27,7 +27,8 @@ from otko.services.spectrum import ( # ── ResponseSpectrum schema ────────────────────────────────────────── def test_response_spectrum_basic() -> None: s = ResponseSpectrum( - id=1, name="EC8 type 1", + id=1, + name="EC8 type 1", periods=[0.0001, 0.1, 0.4, 1.0, 4.0], accelerations=[0.5, 1.0, 2.5, 1.0, 0.25], damping_ratio=0.05, @@ -39,7 +40,8 @@ def test_response_spectrum_basic() -> None: def test_response_spectrum_rejects_unsorted_periods() -> None: with pytest.raises(ValueError, match="strictly increasing"): ResponseSpectrum( - id=1, periods=[0.1, 0.5, 0.4], # 0.5 then 0.4 = not increasing + id=1, + periods=[0.1, 0.5, 0.4], # 0.5 then 0.4 = not increasing accelerations=[1.0, 2.0, 1.5], ) @@ -47,7 +49,8 @@ def test_response_spectrum_rejects_unsorted_periods() -> None: def test_response_spectrum_rejects_length_mismatch() -> None: with pytest.raises(ValueError, match="length mismatch"): ResponseSpectrum( - id=1, periods=[0.1, 0.5, 1.0], + id=1, + periods=[0.1, 0.5, 1.0], accelerations=[1.0, 2.0], ) @@ -55,7 +58,8 @@ def test_response_spectrum_rejects_length_mismatch() -> None: def test_response_spectrum_rejects_zero_period() -> None: with pytest.raises(ValueError): ResponseSpectrum( - id=1, periods=[0.0, 0.5], + id=1, + periods=[0.0, 0.5], accelerations=[1.0, 2.0], ) @@ -63,16 +67,18 @@ def test_response_spectrum_rejects_zero_period() -> None: # ── interp_sa ──────────────────────────────────────────────────────── def test_interp_sa_clamps_outside_table() -> None: s = ResponseSpectrum( - id=1, periods=[0.1, 1.0], + id=1, + periods=[0.1, 1.0], accelerations=[0.5, 0.2], ) - assert interp_sa(s, 0.05) == 0.5 # below: clamp to first - assert interp_sa(s, 5.0) == 0.2 # above: clamp to last + assert interp_sa(s, 0.05) == 0.5 # below: clamp to first + assert interp_sa(s, 5.0) == 0.2 # above: clamp to last def test_interp_sa_linear_in_table() -> None: s = ResponseSpectrum( - id=1, periods=[0.1, 1.1], + id=1, + periods=[0.1, 1.1], accelerations=[1.0, 0.0], ) # midpoint linearly = 0.5 @@ -88,29 +94,26 @@ def _two_dof_modal(masses: list[float]) -> tuple[Project, ModalResults]: """ p = Project( meta=ProjectMeta(name="2dof", units=UnitSystem.SI_M_N), - ndm=3, ndf=6, + ndm=3, + ndf=6, nodes=[ Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), - Node(id=2, coords=(0, 0, 1.0), - mass=(masses[0], 0, 0, 0, 0, 0)), - Node(id=3, coords=(0, 0, 2.0), - mass=(masses[1], 0, 0, 0, 0, 0)), + Node(id=2, coords=(0, 0, 1.0), mass=(masses[0], 0, 0, 0, 0, 0)), + Node(id=3, coords=(0, 0, 2.0), mass=(masses[1], 0, 0, 0, 0, 0)), ], - sections=[ElasticSection(id=1, E=2e11, A=0.01, - Iz=1e-5, Iy=1e-5, G=8e10, J=1e-6)], + sections=[ElasticSection(id=1, E=2e11, A=0.01, Iz=1e-5, Iy=1e-5, G=8e10, J=1e-6)], elements=[ ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), ElasticBeamColumn(id=2, nodes=(2, 3), section_id=1), ], ) modal = ModalResults( - case_id=1, case_name="Modal", + case_id=1, + case_name="Modal", eigenvalues=np.array([100.0, 400.0]), mode_shapes={ - 1: {2: np.array([1.0, 0, 0, 0, 0, 0]), - 3: np.array([1.0, 0, 0, 0, 0, 0])}, - 2: {2: np.array([1.0, 0, 0, 0, 0, 0]), - 3: np.array([-1.0, 0, 0, 0, 0, 0])}, + 1: {2: np.array([1.0, 0, 0, 0, 0, 0]), 3: np.array([1.0, 0, 0, 0, 0, 0])}, + 2: {2: np.array([1.0, 0, 0, 0, 0, 0]), 3: np.array([-1.0, 0, 0, 0, 0, 0])}, }, ) return p, modal @@ -124,7 +127,7 @@ def test_mass_participation_two_dof_equal_mass() -> None: # Mode 1 (φ = (1, 1)): Γ = (1·1 + 1·1) / (1·1 + 1·1) = 1; M_eff = 1²·2 = 2 assert modes[0].participation_factor == pytest.approx(1.0) assert modes[0].effective_mass == pytest.approx(2.0) - assert modes[0].mass_ratio == pytest.approx(1.0) # 100% of total mass + assert modes[0].mass_ratio == pytest.approx(1.0) # 100% of total mass # Mode 2 (φ = (1, -1)): Γ = (1·1 + 1·-1) / (1 + 1) = 0; M_eff = 0 assert modes[1].participation_factor == pytest.approx(0.0) @@ -144,10 +147,8 @@ def test_srss_recovers_single_mode_when_only_one_active() -> None: p, modal = _two_dof_modal([1.0, 1.0]) modes = mass_participation(p, modal, direction=1) # Spectrum: constant 1.0 m/s² so Sa(T) = 1 for any T. - s = ResponseSpectrum(id=1, periods=[0.01, 100.0], - accelerations=[1.0, 1.0]) - combined, _ = combine_modal_response(modes, s, modal, - direction=1, method="SRSS") + s = ResponseSpectrum(id=1, periods=[0.01, 100.0], accelerations=[1.0, 1.0]) + combined, _ = combine_modal_response(modes, s, modal, direction=1, method="SRSS") # Mode 1 only contributes (Γ_2 = 0). u_2 = Γ_1·φ_1·Sa/ω_1² = 1·1·1/100 = 0.01 assert combined[2][0] == pytest.approx(0.01) assert combined[3][0] == pytest.approx(0.01) @@ -157,12 +158,9 @@ def test_cqc_equals_srss_for_well_separated_modes() -> None: """When modes are well-separated (ω₂/ω₁ = 2), CQC ≈ SRSS.""" p, modal = _two_dof_modal([1.0, 1.0]) modes = mass_participation(p, modal, direction=1) - s = ResponseSpectrum(id=1, periods=[0.01, 100.0], - accelerations=[1.0, 1.0]) - srss, _ = combine_modal_response(modes, s, modal, direction=1, - method="SRSS") - cqc, _ = combine_modal_response(modes, s, modal, direction=1, - method="CQC") + s = ResponseSpectrum(id=1, periods=[0.01, 100.0], accelerations=[1.0, 1.0]) + srss, _ = combine_modal_response(modes, s, modal, direction=1, method="SRSS") + cqc, _ = combine_modal_response(modes, s, modal, direction=1, method="CQC") # Mode 2 is silent (Γ=0) so both should give identical answers. np.testing.assert_array_almost_equal(srss[2], cqc[2], decimal=10) @@ -171,21 +169,29 @@ def test_cqc_equals_srss_for_well_separated_modes() -> None: def test_response_spectrum_case_round_trip(tmp_path) -> None: # type: ignore[no-untyped-def] p = Project( meta=ProjectMeta(name="rs", units=UnitSystem.SI_M_N), - ndm=3, ndf=6, - nodes=[Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), - Node(id=2, coords=(0, 0, 3))], - sections=[ElasticSection(id=1, E=2e11, A=0.01, Iz=1e-5, - Iy=1e-5, G=8e10, J=1e-6)], + ndm=3, + ndf=6, + nodes=[Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6), Node(id=2, coords=(0, 0, 3))], + sections=[ElasticSection(id=1, E=2e11, A=0.01, Iz=1e-5, Iy=1e-5, G=8e10, J=1e-6)], elements=[ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1)], - spectra=[ResponseSpectrum( - id=1, name="Demo", - periods=[0.1, 0.5, 2.0], - accelerations=[2.5, 2.5, 0.5], - )], - analyses=[ResponseSpectrumCase( - id=1, modal_case_id=2, spectrum_id=1, - direction=1, combination="CQC", damping_ratio=0.03, - )], + spectra=[ + ResponseSpectrum( + id=1, + name="Demo", + periods=[0.1, 0.5, 2.0], + accelerations=[2.5, 2.5, 0.5], + ) + ], + analyses=[ + ResponseSpectrumCase( + id=1, + modal_case_id=2, + spectrum_id=1, + direction=1, + combination="CQC", + damping_ratio=0.03, + ) + ], ) path = tmp_path / "rs.osmodel" save_project(p, path) diff --git a/tests/unit/test_phase9.py b/tests/unit/test_phase9.py index 1b6413e..24b38b9 100644 --- a/tests/unit/test_phase9.py +++ b/tests/unit/test_phase9.py @@ -10,9 +10,7 @@ import pytest from otko.core import ( AggregatorDOF, CircularPatch, - ElasticSection, FiberSection, - Fibre, Project, RectangularPatch, SectionAggregator, @@ -29,10 +27,17 @@ from otko.services.section_properties import ( def test_rectangular_patch_expands_to_correct_count() -> None: sec = FiberSection( id=1, - patches=[RectangularPatch( - material_id=1, n_fib_y=5, n_fib_z=4, - y_i=-0.1, z_i=-0.1, y_j=0.1, z_j=0.1, - )], + patches=[ + RectangularPatch( + material_id=1, + n_fib_y=5, + n_fib_z=4, + y_i=-0.1, + z_i=-0.1, + y_j=0.1, + z_j=0.1, + ) + ], ) fibres = expand_fibres(sec) assert fibres.shape == (20, 3) @@ -43,10 +48,17 @@ def test_rectangular_patch_expands_to_correct_count() -> None: def test_rectangular_patch_centroid_at_origin() -> None: sec = FiberSection( id=1, - patches=[RectangularPatch( - material_id=1, n_fib_y=8, n_fib_z=8, - y_i=-0.15, z_i=-0.15, y_j=0.15, z_j=0.15, - )], + patches=[ + RectangularPatch( + material_id=1, + n_fib_y=8, + n_fib_z=8, + y_i=-0.15, + z_i=-0.15, + y_j=0.15, + z_j=0.15, + ) + ], ) props = compute_section_props(sec) assert props.centroid_y == pytest.approx(0.0, abs=1e-12) @@ -58,13 +70,18 @@ def test_circular_patch_area_approaches_pi_r_squared() -> None: r = 0.15 sec = FiberSection( id=1, - patches=[CircularPatch( - material_id=1, n_fib_circ=32, n_fib_rad=8, - r_inner=0.0, r_outer=r, - )], + patches=[ + CircularPatch( + material_id=1, + n_fib_circ=32, + n_fib_rad=8, + r_inner=0.0, + r_outer=r, + ) + ], ) props = compute_section_props(sec) - expected = math.pi * r ** 2 + expected = math.pi * r**2 # With 32×8 = 256 fibres, area should be close (within ~1%). assert props.area == pytest.approx(expected, rel=0.01) assert props.centroid_y == pytest.approx(0.0, abs=1e-6) @@ -75,11 +92,17 @@ def test_circular_patch_area_approaches_pi_r_squared() -> None: def test_straight_layer_expands_correctly() -> None: sec = FiberSection( id=1, - layers=[StraightLayer( - material_id=2, n_bars=4, bar_area=0.0005, - y_start=-0.1, z_start=-0.12, - y_end=0.1, z_end=-0.12, - )], + layers=[ + StraightLayer( + material_id=2, + n_bars=4, + bar_area=0.0005, + y_start=-0.1, + z_start=-0.12, + y_end=0.1, + z_end=-0.12, + ) + ], ) fibres = expand_fibres(sec) assert fibres.shape == (4, 3) @@ -91,7 +114,8 @@ def test_straight_layer_expands_correctly() -> None: # ── SectionAggregator ─────────────────────────────────────────────── def test_section_aggregator_schema() -> None: agg = SectionAggregator( - id=5, name="Fiber+Torsion", + id=5, + name="Fiber+Torsion", section_id=1, pairings=[AggregatorDOF(material_id=3, dof="T")], ) @@ -102,50 +126,78 @@ def test_section_aggregator_schema() -> None: # ── Mixed section → Iy / Iz ───────────────────────────────────────── def test_Iz_of_rect_matches_analytical() -> None: """Iz = b·h³/12 for a rectangle centred at origin.""" - b, h = 0.3, 0.5 # y-range: -0.15..0.15, z-range: -0.25..0.25 + b, h = 0.3, 0.5 # y-range: -0.15..0.15, z-range: -0.25..0.25 sec = FiberSection( id=1, - patches=[RectangularPatch( - material_id=1, n_fib_y=20, n_fib_z=20, - y_i=-b / 2, z_i=-h / 2, y_j=b / 2, z_j=h / 2, - )], + patches=[ + RectangularPatch( + material_id=1, + n_fib_y=20, + n_fib_z=20, + y_i=-b / 2, + z_i=-h / 2, + y_j=b / 2, + z_j=h / 2, + ) + ], ) props = compute_section_props(sec) # Iz = about z-axis = b·h³/12? No — our convention: # Iz = Σ A_i · (y_i - ȳ)² (second moment about the z-axis). # For rect: Iz = h·b³/12. - Iz_exact = h * b ** 3 / 12.0 - Iy_exact = b * h ** 3 / 12.0 + Iz_exact = h * b**3 / 12.0 + Iy_exact = b * h**3 / 12.0 assert props.Iz == pytest.approx(Iz_exact, rel=0.01) assert props.Iy == pytest.approx(Iy_exact, rel=0.01) # ── Round-trip persistence ─────────────────────────────────────────── def test_fiber_section_with_patches_round_trips(tmp_path) -> None: # type: ignore[no-untyped-def] - from otko.core import Node, ElasticBeamColumn + from otko.core import ElasticBeamColumn, Node + p = Project( nodes=[Node(id=1, coords=(0, 0, 0)), Node(id=2, coords=(1, 0, 0))], sections=[ FiberSection( - id=1, name="RC-Beam", - patches=[RectangularPatch( - material_id=1, n_fib_y=4, n_fib_z=4, - y_i=-0.15, z_i=-0.25, y_j=0.15, z_j=0.25, - )], - layers=[StraightLayer( - material_id=2, n_bars=3, bar_area=0.0005, - y_start=-0.12, z_start=-0.22, - y_end=0.12, z_end=-0.22, - )], + id=1, + name="RC-Beam", + patches=[ + RectangularPatch( + material_id=1, + n_fib_y=4, + n_fib_z=4, + y_i=-0.15, + z_i=-0.25, + y_j=0.15, + z_j=0.25, + ) + ], + layers=[ + StraightLayer( + material_id=2, + n_bars=3, + bar_area=0.0005, + y_start=-0.12, + z_start=-0.22, + y_end=0.12, + z_end=-0.22, + ) + ], ), SectionAggregator( - id=2, name="Agg", section_id=1, + id=2, + name="Agg", + section_id=1, pairings=[AggregatorDOF(material_id=3, dof="T")], ), ], - elements=[ElasticBeamColumn( - id=1, nodes=(1, 2), section_id=1, - )], + elements=[ + ElasticBeamColumn( + id=1, + nodes=(1, 2), + section_id=1, + ) + ], ) path = tmp_path / "fib.osmodel" save_project(p, path) diff --git a/tests/unit/test_project.py b/tests/unit/test_project.py index 6df0846..fe36fd5 100644 --- a/tests/unit/test_project.py +++ b/tests/unit/test_project.py @@ -126,7 +126,8 @@ def test_validate_references_catches_missing_time_series_in_pattern() -> None: nodes=[Node(id=1, coords=(0, 0, 0))], load_patterns=[ PlainLoadPattern( - id=1, time_series_id=99, + id=1, + time_series_id=99, nodal_loads=[NodalLoad(node_id=1, forces=(0, 0, -10, 0, 0, 0))], ) ], @@ -143,8 +144,13 @@ def test_validate_references_catches_missing_material_in_fiber_patch() -> None: id=1, patches=[ RectangularPatch( - material_id=99, n_fib_y=2, n_fib_z=2, - y_i=-0.1, z_i=-0.1, y_j=0.1, z_j=0.1, + material_id=99, + n_fib_y=2, + n_fib_z=2, + y_i=-0.1, + z_i=-0.1, + y_j=0.1, + z_j=0.1, ) ], ) @@ -162,8 +168,13 @@ def test_validate_references_catches_missing_material_in_fiber_layer() -> None: id=1, layers=[ StraightLayer( - material_id=99, n_bars=3, bar_area=1e-4, - y_start=-0.1, z_start=-0.1, y_end=0.1, z_end=-0.1, + material_id=99, + n_bars=3, + bar_area=1e-4, + y_start=-0.1, + z_start=-0.1, + y_end=0.1, + z_end=-0.1, ) ], ) @@ -179,7 +190,8 @@ def test_validate_references_catches_missing_material_in_aggregator_pairing() -> sections=[ ElasticSection(id=1, E=200e9, A=0.01, Iz=8.33e-6), SectionAggregator( - id=2, section_id=1, + id=2, + section_id=1, pairings=[AggregatorDOF(material_id=99, dof="T")], ), ], @@ -194,7 +206,8 @@ def test_validate_references_catches_missing_base_section_in_aggregator() -> Non materials=[Steel01(id=1, Fy=420e6, E0=200e9, b=0.01)], sections=[ SectionAggregator( - id=2, section_id=42, + id=2, + section_id=42, pairings=[AggregatorDOF(material_id=1, dof="T")], ), ], @@ -214,19 +227,30 @@ def test_validate_references_passes_on_valid_fiber_and_aggregator() -> None: id=1, patches=[ RectangularPatch( - material_id=1, n_fib_y=2, n_fib_z=2, - y_i=-0.1, z_i=-0.1, y_j=0.1, z_j=0.1, + material_id=1, + n_fib_y=2, + n_fib_z=2, + y_i=-0.1, + z_i=-0.1, + y_j=0.1, + z_j=0.1, ) ], layers=[ StraightLayer( - material_id=2, n_bars=3, bar_area=1e-4, - y_start=-0.1, z_start=-0.1, y_end=0.1, z_end=-0.1, + material_id=2, + n_bars=3, + bar_area=1e-4, + y_start=-0.1, + z_start=-0.1, + y_end=0.1, + z_end=-0.1, ) ], ), SectionAggregator( - id=2, section_id=1, + id=2, + section_id=1, pairings=[AggregatorDOF(material_id=1, dof="T")], ), ], @@ -237,7 +261,8 @@ def test_validate_references_passes_on_valid_fiber_and_aggregator() -> None: # ────────────────────────── small smoke build ────────────────────────── def test_full_truss_project_builds_and_validates() -> None: p = Project( - ndm=2, ndf=2, + ndm=2, + ndf=2, nodes=[ Node(id=1, coords=(0, 0, 0), restraint=(True, True, False, False, False, False)), Node(id=2, coords=(4, 0, 0), restraint=(False, True, False, False, False, False)), @@ -253,7 +278,8 @@ def test_full_truss_project_builds_and_validates() -> None: time_series=[LinearTimeSeries(id=1)], load_patterns=[ PlainLoadPattern( - id=1, time_series_id=1, + id=1, + time_series_id=1, nodal_loads=[NodalLoad(node_id=3, forces=(0, -1000, 0, 0, 0, 0))], ) ], diff --git a/tests/unit/test_renderer.py b/tests/unit/test_renderer.py index e1c8fb0..1586ca2 100644 --- a/tests/unit/test_renderer.py +++ b/tests/unit/test_renderer.py @@ -5,9 +5,9 @@ from __future__ import annotations import pytest pv = pytest.importorskip("pyvista") -import numpy as np # noqa: E402 +import numpy as np -from otko.core import ( # noqa: E402 +from otko.core import ( ElasticBeamColumn, ElasticSection, LinearTimeSeries, @@ -18,8 +18,8 @@ from otko.core import ( # noqa: E402 Steel01, TrussElement, ) -from otko.services.deformation import DeformationSource # noqa: E402 -from otko.views.canvas3d.model_renderer import ( # noqa: E402 +from otko.services.deformation import DeformationSource +from otko.views.canvas3d.model_renderer import ( ModelRenderer, RendererMode, _classify_support, diff --git a/tests/unit/test_unit_labels.py b/tests/unit/test_unit_labels.py index 1fe1e3e..999f0e4 100644 --- a/tests/unit/test_unit_labels.py +++ b/tests/unit/test_unit_labels.py @@ -2,8 +2,6 @@ from __future__ import annotations -import pytest - from otko.core import UnitSystem, labels_for diff --git a/tests/unit/test_zerolength_section.py b/tests/unit/test_zerolength_section.py index 72d3409..69ef334 100644 --- a/tests/unit/test_zerolength_section.py +++ b/tests/unit/test_zerolength_section.py @@ -3,6 +3,7 @@ from __future__ import annotations import pytest +from pydantic import ValidationError from otko.core import ( ElasticSection, @@ -21,9 +22,11 @@ def test_zero_length_section_schema_defaults() -> None: def test_zero_length_section_rejects_extra_fields() -> None: - with pytest.raises(Exception): + with pytest.raises(ValidationError): ZeroLengthSectionElement( - id=1, nodes=(1, 2), section_id=1, + id=1, + nodes=(1, 2), + section_id=1, bogus="not allowed", # type: ignore[call-arg] ) @@ -31,17 +34,24 @@ def test_zero_length_section_rejects_extra_fields() -> None: def test_zero_length_section_round_trips(tmp_path) -> None: # type: ignore[no-untyped-def] """A project containing a zeroLengthSection must save+load unchanged.""" p = Project( - ndm=2, ndf=3, + ndm=2, + ndf=3, nodes=[ - Node(id=1, coords=(0, 0, 0), - restraint=(True, True, False, False, False, True)), - Node(id=2, coords=(0, 0, 0), - restraint=(False, True, False, False, False, False)), + Node(id=1, coords=(0, 0, 0), restraint=(True, True, False, False, False, True)), + Node(id=2, coords=(0, 0, 0), restraint=(False, True, False, False, False, False)), + ], + sections=[ + ElasticSection( + id=1, + name="Box", + E=200e9, + A=0.01, + Iz=1e-5, + Iy=1e-5, + G=80e9, + J=1e-6, + ) ], - sections=[ElasticSection( - id=1, name="Box", E=200e9, A=0.01, Iz=1e-5, Iy=1e-5, - G=80e9, J=1e-6, - )], elements=[ ZeroLengthSectionElement(id=10, nodes=(1, 2), section_id=1), ],