diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2c834e6..ebc3520 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,25 +14,12 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- # Pinned to the pre-commit revisions so CI and local hooks agree.
- - run: pip install ruff==0.4.4
+ - run: pip install ruff mypy
- run: ruff check src tests
- run: ruff format --check src tests
- 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]
+ test:
+ needs: lint
strategy:
fail-fast: false
matrix:
@@ -44,38 +31,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 \
libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 \
libxcb-shape0 libxcb-sync1 libxcb-xfixes0 libxcb-xinerama0 \
libxcb-cursor0 libdbus-1-3 libgl1 libxkbcommon0 xvfb
- - run: pip install -e ".[gui,dev]"
- - 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
+ - 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"
diff --git a/.gitignore b/.gitignore
index eac3bc1..c82640e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,7 +61,3 @@ examples/ozan*.osmodel
examples/ozan_results/
/Ex*.csv
/Ex*.png
-
-# BEGIN oh-my-opencode-slim clonedeps
-.slim/clonedeps/repos/
-# END oh-my-opencode-slim clonedeps
diff --git a/.ignore b/.ignore
deleted file mode 100644
index 32dbf4f..0000000
--- a/.ignore
+++ /dev/null
@@ -1,9 +0,0 @@
-# BEGIN oh-my-opencode-slim clonedeps
-!.slim/
-!.slim/clonedeps.json
-!.slim/clonedeps/
-!.slim/clonedeps/repos/
-!.slim/clonedeps/repos/**
-.slim/clonedeps/repos/**/.git/
-.slim/clonedeps/repos/**/.git/**
-# END oh-my-opencode-slim clonedeps
diff --git a/.slim/clonedeps.json b/.slim/clonedeps.json
deleted file mode 100644
index ddddcab..0000000
--- a/.slim/clonedeps.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "version": "1.0.0",
- "updatedAt": "2026-09-16T17:30:00.000Z",
- "dependencies": [
- {
- "name": "opstool",
- "resolvedVersion": "1.0.26",
- "repoUrl": "https://github.com/yexiang92/opstool.git",
- "ref": "v1.0.26",
- "path": ".slim/clonedeps/repos/yexiang92__opstool",
- "packagePath": "opstool/vis",
- "reason": "Read-only source for evaluating whether to port opstool's PyVista/Plotly visualization settings into otko's canvas backends, or to depend on opstool.vis directly (GPL-3.0, compatible with otko's AGPL-3.0 via GPLv3 section 13)."
- }
- ]
-}
diff --git a/AGENTS.md b/AGENTS.md
index babac38..bdc5f2e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -5,7 +5,7 @@ Pre-alpha SAP2000-style desktop GUI for OpenSeesPy. Python 3.10+; **Windows requ
## Install
```bash
-pip install -e ".[gui,dev]" # desktop: Qt + PyVista + plotly.js backend + dev tools
+pip install -e ".[gui,dev]" # desktop: Qt + PyVista + dev tools
pip install -e . # headless: core + services only, no Qt (scripts, notebooks, web backends)
python -m otko # launch GUI (src/otko/app.py:run)
```
@@ -19,8 +19,6 @@ Strict one-way MVVM + services: `views → viewmodels → services → core`.
- `core/` (entities: `project.py`, `geometry/`, `materials/`, `sections/`, `loads/`, `analysis/`, `catalog/`): stdlib + numpy + pydantic only. **No Qt, no openseespy. Period.**
- `services/` (`opensees_runner.py`, `persistence.py`, `results.py`, ...): may use core + h5py + openseespy. **No Qt.**
- `views/`: PySide6/pyvistaqt only. **No direct `import openseespy`** — go through a service.
-- `views/canvas3d/` (**PyVista/VTK**, default) and `views/canvas_plotly/` (plotly.js in a `QWebEngineView`) are two backends for the same central 3D view. Both satisfy the `CanvasBackend` protocol in `views/canvas_base.py`, share one `SelectionState` owned by `MainWindow`, and are swapped live via **Options → Canvas Backend** (persisted in `QSettings` under `canvas/backend`). `MainWindow._activate_canvas` keeps both widgets in a `QStackedWidget` — never destroy a canvas mid-session (VTK leaves dangling make-current callbacks). Backend-specific gaps are declared by `CanvasCapabilities` (e.g. Plotly has no force-diagram overlay or video export yet); gate UI on `canvas.capabilities`, never on the backend name. `canvas_plotly/trace_builder.py` is pure (no Qt, no pyvista) and unit-tested headless. `PlotlyCanvas` pushes with `Plotly.react`, which resets scene attributes the layout omits: data-only pushes pass `preserveView` so `html.py` carries the live camera and padded axis ranges forward, and the hover/snap marker is restyled only when its visible state changes (a restyle per hover event redraws per mouse move until the stack blows). Regression tests: `tests/gui/test_plotly_view_preservation.py`, `tests/gui/test_plotly_hover.py`.
-- `views/canvas3d/style.py` (`RenderStyle`) is the single source of truth for colours/sizes on **both** backends; renderers must read it rather than hard-coding a colour. The `STYLE_FIELDS` subset is user-editable via **Options → Plot Properties…** (`views/dialogs/plot_properties.py`), previews live through `MainWindow.set_plot_style(..., persist=False)`, and persists as JSON in `QSettings` under `plot/props`.
- `viewmodels/` bridges core↔Qt (signals, `QUndoStack`); `commands/` holds `QUndoCommand` subclasses.
- Rules: public functions need type hints + docstring; new domain entities go through Pydantic validation; ops >50 ms run off the GUI thread (`AnalysisWorker` in QThread, cancel via `isInterruptionRequested()`, results cross threads as lightweight `ResultsHandle` to HDF5).
@@ -58,17 +56,3 @@ git remote add origin ssh://git@smill-home.ddns.net/smill/otko.git
- `examples/*.py` are source of truth; `examples/*.osmodel` are generated artifacts (checked in). Never hand-edit `.osmodel` — change the script and regen: `python examples/cantilever.py` (each script saves, reloads, asserts clean round-trip).
- Projects persist as single Pydantic-validated JSON `.osmodel` (diffable); analysis output goes to `*.osresults.h5` (HDF5, one group per case).
- Quick smoke: open `examples/cantilever.osmodel` → run `Tip-Load` → M3 peaks 50 kN·m at fixed end.
-
-## Cloned Dependency Source
-
-Read-only dependency source repositories are available under
-`.slim/clonedeps/repos/` for inspection. Do not edit these clones. The
-structured manifest is `.slim/clonedeps.json`.
-
-- `.slim/clonedeps/repos/yexiang92__opstool/` — `yexiang92/opstool` at `v1.0.26`; the OpenSeesPy
- pre/post-processor whose PyVista and Plotly visualization settings
- (`opstool/vis/{pyvista,plotly}/plot_utils.py`, `plot_resp_base.py`, `vis_model.py`) are the
- reference for otko's canvas look-and-feel. GPL-3.0, and GPLv3 §13 explicitly permits combining
- it with otko's AGPL-3.0. Any code actually ported from it must keep the opstool copyright notice
- and record that it was modified (GPLv3 §5a/b) — add that entry to `NOTICE` when the port lands.
-
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ab9023e..9f911d7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,97 +1,37 @@
# Contributing
-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.
+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 # Linux / macOS
-# .venv\Scripts\activate # Windows
-pip install -e ".[gui,dev]"
+source .venv/bin/activate
+pip install -e ".[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 # line-length 100, E501 ignored
+ruff format src tests
mypy src/otko/core src/otko/services
-pytest -m "not slow" # CI gate: lint → this, on 3.10/3.11/3.12 × ubuntu/windows/macos
+pytest -m "not slow"
```
-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)
-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.
+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.
## Commit style
Conventional Commits — `feat:`, `fix:`, `refactor:`, `docs:`, `test:`,
-`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.
+`chore:`, `ci:`.
diff --git a/NOTICE b/NOTICE
deleted file mode 100644
index 24b2dd1..0000000
--- a/NOTICE
+++ /dev/null
@@ -1,85 +0,0 @@
-# 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.
-```
-
-## Ported / adapted code: `opstool`
-
-The canvas element colour palette in `src/otko/views/canvas3d/style.py`
-(per-family element colours and the diverging response colour scale) and the
-resulting diagram colouring in `views/canvas3d/diagram_renderer.py` were
-adapted from the `opstool` project, which is distributed under the **GNU
-General Public License v3.0**. opstool is Copyright © Yexiang Yan and
-contributors.
-
-In accordance with GPLv3 §5(a)/(b) this notice records that the material was
-modified and adapted for OTKO. Combining the GPLv3-covered material with
-OTKO's AGPL-3.0 code is permitted by GPLv3 §13; the combined work is
-conveyed under AGPL-3.0, and the GPLv3 terms continue to apply to the
-opstool-derived portions.
-
-## 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 c291d67..7a6d53d 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,13 @@
-
+
+Draw the model. Click run. Read the diagrams.
+
- A SAP2000-style desktop GUI for
- OpenSeesPy.
- Draw the model, click run, look at the diagrams.
+
+
+
@@ -14,84 +16,47 @@
---
-
+
+
+  |
+  |
+  |
+
+
+ | SAP2000-style menus |
+ Compact dark toolbar |
+ Parametric grid wizard |
+
+
-## Why
+## What is OTKO?
OpenSees does nonlinear FEM well. Its user interface is a script.
-OTKO puts a visual front-end on it:
-
-- Draw nodes, frames, supports, and loads on a snapped grid.
-- Assign materials, sections, and load patterns through dialogs.
-- Run static, modal, pushover, and time-history analyses with progress
- and cancel.
-- Look at the results — deformed shape, mode shapes, force
- diagrams, pushover curves, time-history plots, hysteresis loops.
-- Save the model as one `.osmodel` JSON file. Diffs cleanly in Git,
- builds cleanly from Python.
+OTKO puts a visual front-end on top of it: draw nodes, frames,
+supports, and loads on a snapped grid; assign materials, sections,
+and load patterns through dialogs; run the analysis with progress
+and cancel; then read the results — deformed shapes, mode shapes,
+force diagrams, pushover curves, hysteresis loops.
Underneath, the `core` Pydantic model works fine from a script or
-notebook. The GUI is a front-end, not the whole product.
+notebook without any GUI. The GUI is a front-end, not the whole
+product.
-## What works today
+## Highlights
-- **Modeling** — grids, nodes, frames (elastic + force-based), trusses,
- quads, zero-length sections, restraints, equalDOF constraints,
- distributed loads, ground motions (`PathTimeSeries` /
- `UniformExcitation`).
-- **Materials and sections** — `Steel01`, `Steel02`, `Concrete01`,
- `Concrete02`, `ElasticPP`, `Hysteretic`, fiber sections (rectangular /
- circular patches + rebar layers), `SectionAggregator`,
- `BeamWithHinges`.
-- **Analyses** — static (load- or displacement-controlled), modal,
- displacement-controlled pushover, transient time-history with
- mode-1 Rayleigh damping. Chained workflows: gravity preload →
- `loadConst -time 0.0` → pushover or transient.
-- **Post-processing** — deformed shape (with scale slider), animated
- mode shapes, axial / shear / moment diagrams, pushover curves
- (in display units), time-history plots, hysteresis loops,
- response-spectrum SRSS / CQC, snapshot + video export.
-- **Persistence** — one JSON `.osmodel` per project, Pydantic-validated,
- round-trips clean.
-- **Examples** — 20+ verified examples, including OpenSees
- Wiki Examples 1–4 and a fiber-section RC frame pushover.
- See [`examples/README.md`](examples/README.md).
+| Area | What you get |
+| ---- | ------------ |
+| **Modeling** | Grid wizards, nodes, frames (elastic + force-based), trusses, quads, zero-length sections, restraints, equalDOF constraints, distributed loads, ground motions (`PathTimeSeries` / `UniformExcitation`). |
+| **Materials & sections** | `Steel01`, `Steel02`, `Concrete01`, `Concrete02`, `ElasticPP`, `Hysteretic`, fiber sections (rectangular / circular patches + rebar), `SectionAggregator`, `BeamWithHinges`. |
+| **Analysis** | Static (load- or displacement-controlled), modal, pushover, transient time-history with mode-1 Rayleigh damping. Chained workflows: gravity preload → `loadConst -time 0.0` → pushover or transient. |
+| **Post-processing** | Deformed shape with scale slider, animated mode shapes, axial / shear / moment diagrams, pushover curves, time-history plots, hysteresis loops, response-spectrum SRSS / CQC, snapshot + video export. |
+| **Persistence** | One Pydantic-validated `.osmodel` JSON per project — diffs cleanly in Git, builds cleanly from Python. Analysis output in HDF5 (`*.osresults.h5`). |
-## Tech stack
+20+ verified examples ship with the repo, including OpenSees Wiki
+Examples 1–4 and a fiber-section RC frame pushover. See
+[`examples/README.md`](examples/README.md).
-| Layer | Library |
-| ------------ | ------------------------------------ |
-| GUI | PySide6 (Qt 6) |
-| 3D viewport | PyVista + pyvistaqt (VTK) |
-| 2D plots | pyqtgraph |
-| Solver | OpenSeesPy 3.8.0.0 |
-| Numerics | NumPy |
-| Storage | Pydantic v2 (model), h5py (results) |
-| Tests | pytest, pytest-qt |
-| Lint / type | ruff, mypy |
-
-## Architecture
-
-Strict MVVM + service layer. `core` is pure Python — no Qt,
-no OpenSeesPy imports — and unit-tests in isolation.
-
-```
-views (Qt) → viewmodels → services (OpenSeesRunner, Persistence) → core (model)
-```
-
-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):
+## Quick start
```bash
git clone ssh://git@smill-home.ddns.net/smill/otko.git
@@ -101,26 +66,9 @@ python -m venv .venv
.venv\Scripts\activate # Windows
source .venv/bin/activate # Linux / macOS
-pip install -e ".[gui,dev]"
-```
+pip install -e ".[gui,dev]" # desktop: Qt + PyVista + dev tools
+# pip install -e . # headless: core + services only
-**Headless** (core + services only, no Qt):
-
-```bash
-pip install -e .
-```
-
-That pulls pydantic, numpy, h5py, openseespy and nothing else.
-Use it for scripts, notebooks, and web backends that reuse
-`otko.core` or `otko.services` without the GUI.
-
-Python 3.10+. On Windows use **3.12+** — `openseespywin==3.8.0.0`
-has no 3.11 wheel (`Requires-Python >=3.12`). Both pins already
-live in `pyproject.toml`.
-
-## Quick start
-
-```bash
python -m otko
```
@@ -128,15 +76,58 @@ Then:
1. **File → Open** → `examples/cantilever.osmodel`.
2. **Analyze → Cases** → run `Tip-Load`.
-3. **Display → Show Force Diagram** → **M3**: linear moment,
- 50 kN·m at the fixed end. **V2**: constant -10 kN.
-4. **Display → Show Deformed Shape** → cantilever curve, as advertised.
+3. **Display → Show Force Diagram** → **M3**: 50 kN·m at the fixed end.
+ **V2**: constant −10 kN.
+4. **Display → Show Deformed Shape** → the cantilever curve.
-Nonlinear version: open `examples/portal_pushover.osmodel`,
-run `Push-X`, **Display → Show Pushover Curve**. Elastic ramp,
-then a yield plateau as the base hinges form.
+Nonlinear version: open `examples/portal_pushover.osmodel`, run
+`Push-X`, **Display → Show Pushover Curve** — elastic ramp, then a
+yield plateau as the base hinges form.
-## Run the test suite
+Python 3.10+. On Windows use **3.12+** — `openseespywin==3.8.0.0`
+has no 3.11 wheel (`Requires-Python >=3.12`). Both pins already
+live in `pyproject.toml`.
+
+## Architecture
+
+Strict MVVM + service layer, one direction only:
+
+```
+views (Qt) → viewmodels → services (OpenSeesRunner, Persistence) → core (model)
+```
+
+`core` is pure Python — no Qt, no OpenSeesPy imports — and unit-tests
+in isolation. PySide6 + PyVista on the view, pyqtgraph for 2D plots,
+Pydantic v2 for the model, h5py for results. Long version, including
+the OpenSeesPy command order the runner emits, in
+[`docs/architecture.md`](docs/architecture.md).
+
+## Roadmap
+
+[`docs/roadmap.md`](docs/roadmap.md) has the phase-by-phase plan.
+Phases 0–7 (modeling, analysis, post-processing) are mostly done.
+Phase 8 — isolators, ground-motion library, IDA, fiber-section
+editor polish — is where the open work is.
+
+## Contributing
+
+Collaborators welcome — most useful to people who already work with
+OpenSees and want a shorter path from idea to model:
+
+- **Structural / earthquake engineers** who can tell us when a feature
+ is almost right but not quite.
+- **Researchers** running pushover, IDA, or response-spectrum studies
+ against hand-built scripts.
+- **Python / Qt developers** into scientific desktop apps, VTK
+ rendering, or Pydantic schema design.
+- **Students** learning FEM and GUI architecture — the examples and
+ tests are meant to read as documentation.
+- **UX / icon designers** willing to argue about dialogs and toolbars.
+
+Bug reports and reproducible test cases count as contributions. Setup
+and the rules enforced in review: [`CONTRIBUTING.md`](CONTRIBUTING.md).
+
+Test suite:
```bash
pytest tests/unit # pure logic, milliseconds
@@ -147,39 +138,10 @@ pytest tests/integration # real OpenSeesPy runs on bundled examples
CI runs lint + the non-`slow` subset on Linux / macOS / Windows
× Python 3.10 / 3.11 / 3.12.
-## Roadmap
-
-[`docs/roadmap.md`](docs/roadmap.md) has the phase-by-phase plan.
-Phases 0–7 (modeling, analysis, post-processing) are mostly done.
-Phase 8 (isolators, ground-motion library, IDA, fiber-section
-editor polish) is where the open work is.
-
-## Collaborators wanted
-
-Most useful to people who already work with OpenSees and want a
-shorter path from idea to model — and would rather build it together
-than alone. Open an issue or say hi if you are:
-
-- A **structural / earthquake engineer** who knows OpenSees Tcl
- or OpenSeesPy and can tell us when a feature is almost right
- but not quite.
-- A **researcher** running pushover, IDA, or response-spectrum studies
- who can check the GUI against hand-built scripts.
-- A **Python / Qt developer** into scientific desktop apps,
- VTK rendering, or Pydantic schema design.
-- A **student** learning FEM and GUI architecture at the same time —
- the examples and tests are meant to read as documentation.
-- A **UX / icon designer** willing to argue about dialogs, toolbar
- icons, and visual language.
-
-Bug reports and reproducible test cases count as contributions.
-See [`CONTRIBUTING.md`](CONTRIBUTING.md) for setup and the rules
-enforced in review.
-
## License
OTKO is **GNU Affero General Public License v3.0**
-([`LICENSE`](LICENSE)). Read the license itself, not just this:
+([`LICENSE`](LICENSE)). In short:
- Research, education, personal projects: fine, keep the copyright
notice.
@@ -190,6 +152,6 @@ OTKO is **GNU Affero General Public License v3.0**
modifications under AGPL-3.0.
Commercial forks stay open. If you need a different arrangement
-(e.g. closed-source commercial license), open an issue.
+(e.g. a closed-source commercial license), open an issue.
Copyright © 2026 Ozan and contributors.
diff --git a/docs/QUICK_GUIDE.md b/docs/QUICK_GUIDE.md
deleted file mode 100644
index 0bc4b8c..0000000
--- a/docs/QUICK_GUIDE.md
+++ /dev/null
@@ -1,132 +0,0 @@
-# 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
deleted file mode 100644
index 9ef3bfb..0000000
--- a/docs/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# 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/docs/logo.svg b/docs/logo.svg
index 3b6f06f..2e3b844 100644
--- a/docs/logo.svg
+++ b/docs/logo.svg
@@ -5,41 +5,40 @@
-
-
+
+
-
+
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
OTKO
-
+
A SAP2000-STYLE GUI FOR OPENSEESPY
diff --git a/snip3.png b/docs/screenshots/grid.png
similarity index 100%
rename from snip3.png
rename to docs/screenshots/grid.png
diff --git a/snip1.png b/docs/screenshots/menu.png
similarity index 100%
rename from snip1.png
rename to docs/screenshots/menu.png
diff --git a/snip2.png b/docs/screenshots/toolbar.png
similarity index 100%
rename from snip2.png
rename to docs/screenshots/toolbar.png
diff --git a/pyproject.toml b/pyproject.toml
index d67c45f..93bfc44 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "otko"
-dynamic = ["version"]
+version = "0.1.0"
description = "OTKO — a modern, SAP2000-style desktop GUI for OpenSeesPy."
readme = "README.md"
requires-python = ">=3.10"
@@ -42,7 +42,10 @@ gui = [
"vtk>=9.3",
"pyqtgraph>=0.13",
"imageio[ffmpeg]>=2.34",
- "plotly>=5.18",
+ # 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",
@@ -52,7 +55,6 @@ dev = [
"mypy>=1.8",
"pre-commit>=3.6",
"hatch>=1.9",
- "tomli>=2.0; python_version < '3.11'",
]
docs = [
"sphinx>=7.2",
@@ -70,9 +72,6 @@ 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
@@ -90,25 +89,7 @@ select = [
"SIM", # simplify
"RUF", # ruff-specific
]
-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"]
+ignore = ["E501"] # line length handled by formatter
[tool.ruff.format]
quote-style = "double"
@@ -116,12 +97,11 @@ quote-style = "double"
# ─────────── Mypy ───────────
[tool.mypy]
python_version = "3.10"
-warn_unused_configs = true
-plugins = []
-
-[[tool.mypy.overrides]]
-module = ["otko.core.*", "otko.services.*", "otko.viewmodels.*"]
strict = true
+warn_unused_ignores = true
+warn_return_any = true
+disallow_untyped_defs = true
+plugins = []
[[tool.mypy.overrides]]
module = ["openseespy.*", "pyvista.*", "pyvistaqt.*", "vtk.*", "pyqtgraph.*"]
@@ -141,8 +121,3 @@ 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
deleted file mode 100644
index f10ddfd..0000000
--- a/specifications/15-rebuild-adoption-plan.md
+++ /dev/null
@@ -1,383 +0,0 @@
-# 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
deleted file mode 100644
index 3c8f8d4..0000000
--- a/specifications/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# 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 6291478..d9d3102 100644
--- a/src/otko/__init__.py
+++ b/src/otko/__init__.py
@@ -1,5 +1,3 @@
"""OTKO — a desktop GUI for OpenSeesPy."""
-from otko._const import __version__
-
-__all__ = ["__version__"]
+__version__ = "0.1.0"
diff --git a/src/otko/_const.py b/src/otko/_const.py
deleted file mode 100644
index 51e97ce..0000000
--- a/src/otko/_const.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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 eddf86f..4d6647e 100644
--- a/src/otko/commands/__init__.py
+++ b/src/otko/commands/__init__.py
@@ -6,11 +6,6 @@ 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,
@@ -29,6 +24,14 @@ 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,
@@ -55,14 +58,6 @@ 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,
@@ -72,50 +67,25 @@ 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",
- "AddCombinationsCommand",
- "DeleteCombinationsCommand",
- "UpdateCombinationCommand",
+ "AddMaterialsCommand", "DeleteMaterialsCommand", "UpdateMaterialCommand",
+ "AddSectionsCommand", "DeleteSectionsCommand", "UpdateSectionCommand",
+ "AddNodalLoadsCommand", "AddElementLoadsCommand",
+ "UpdateNodalLoadCommand", "DeleteNodalLoadsCommand",
+ "UpdateElementLoadCommand", "DeleteElementLoadsCommand",
+ "MoveNodesCommand", "ReplicateCommand", "MirrorCommand", "Plane",
+ "AddAnalysisCasesCommand", "DeleteAnalysisCasesCommand", "UpdateAnalysisCaseCommand",
"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 444f34c..c26dc22 100644
--- a/src/otko/commands/analysis.py
+++ b/src/otko/commands/analysis.py
@@ -13,7 +13,8 @@ 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)
@@ -34,16 +35,19 @@ 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
+ (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.project.analyses[:] = [c for c in self.project.analyses if c.id not in self._case_ids]
self._notify()
def undo(self) -> None:
@@ -56,7 +60,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 0e8b3dd..c740057 100644
--- a/src/otko/commands/base.py
+++ b/src/otko/commands/base.py
@@ -27,18 +27,20 @@ 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
deleted file mode 100644
index 23f1aa8..0000000
--- a/src/otko/commands/combinations.py
+++ /dev/null
@@ -1,80 +0,0 @@
-"""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 3f94205..2fb051b 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,7 +34,8 @@ 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()
@@ -48,7 +49,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
@@ -71,7 +72,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 f1967ba..6487a9d 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 e271746..894c56b 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 789b6c8..7822caf 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,10 +101,7 @@ 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:
@@ -122,7 +119,7 @@ class AddElementLoadsCommand(ProjectCommand):
def __init__(
self,
- vm: ProjectViewModel,
+ vm: "ProjectViewModel",
element_ids: set[int],
wy: float = 0.0,
wz: float = 0.0,
@@ -160,10 +157,7 @@ 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))
@@ -177,10 +171,7 @@ 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 9729ced..bce4088 100644
--- a/src/otko/commands/materials.py
+++ b/src/otko/commands/materials.py
@@ -13,9 +13,7 @@ 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)
@@ -36,14 +34,15 @@ 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
@@ -64,7 +63,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 e24840c..b285159 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,18 +39,17 @@ 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}
@@ -59,8 +58,7 @@ 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}
@@ -71,7 +69,9 @@ 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 3fd0065..362e2a6 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 94142a6..ecd6054 100644
--- a/src/otko/commands/sections.py
+++ b/src/otko/commands/sections.py
@@ -13,9 +13,7 @@ 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)
@@ -36,14 +34,15 @@ 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
@@ -60,7 +59,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 1d03ab5..8e11b6b 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,7 +31,9 @@ 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()
@@ -41,7 +43,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
@@ -67,7 +69,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]] = []
@@ -91,7 +93,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
@@ -117,7 +119,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
@@ -142,7 +144,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 07c335d..b8d60cc 100644
--- a/src/otko/commands/transforms.py
+++ b/src/otko/commands/transforms.py
@@ -15,9 +15,10 @@ All copies inherit the source node's restraint, mass, and name.
from __future__ import annotations
-from typing import TYPE_CHECKING, Literal
+from typing import TYPE_CHECKING, Any, Literal
from otko.commands.base import ProjectCommand
+from otko.core import Node
if TYPE_CHECKING:
from otko.viewmodels import ProjectViewModel
@@ -29,7 +30,7 @@ class MoveNodesCommand(ProjectCommand):
def __init__(
self,
- vm: ProjectViewModel,
+ vm: "ProjectViewModel",
node_ids: set[int],
offset: tuple[float, float, float],
) -> None:
@@ -44,9 +45,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:
@@ -62,7 +63,7 @@ class ReplicateCommand(ProjectCommand):
def __init__(
self,
- vm: ProjectViewModel,
+ vm: "ProjectViewModel",
node_ids: set[int],
element_ids: set[int],
offset: tuple[float, float, float],
@@ -83,8 +84,7 @@ 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,34 +95,30 @@ 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
]
@@ -146,7 +142,7 @@ class MirrorCommand(ProjectCommand):
def __init__(
self,
- vm: ProjectViewModel,
+ vm: "ProjectViewModel",
node_ids: set[int],
element_ids: set[int],
plane: Plane,
@@ -170,8 +166,7 @@ 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()
@@ -181,30 +176,28 @@ 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 34e7482..9443696 100644
--- a/src/otko/core/__init__.py
+++ b/src/otko/core/__init__.py
@@ -50,8 +50,6 @@ from otko.core.loads import (
ConstantTimeSeries,
ImposedSupportMotionPattern,
LinearTimeSeries,
- LoadCombination,
- LoadCombinationItem,
LoadPattern,
NodalLoad,
PathTimeSeries,
@@ -167,8 +165,6 @@ __all__ = [
"PlainLoadPattern",
"UniformExcitationPattern",
"ImposedSupportMotionPattern",
- "LoadCombination",
- "LoadCombinationItem",
"NodalLoad",
"UniformElementLoad",
"ResponseSpectrum",
diff --git a/src/otko/core/_base.py b/src/otko/core/_base.py
index da3e9b5..72e9d2f 100644
--- a/src/otko/core/_base.py
+++ b/src/otko/core/_base.py
@@ -16,13 +16,11 @@ 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 e8835b7..eba5330 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
+from typing import Annotated, Literal, Union
from pydantic import Field, PositiveFloat, PositiveInt, model_validator
@@ -83,8 +83,7 @@ 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"
@@ -101,7 +100,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,
@@ -115,11 +114,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."
@@ -196,7 +195,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"
@@ -245,30 +244,23 @@ 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[
- StaticCase | ModalCase | TransientCase | PushoverCase | ResponseSpectrumCase,
+ Union[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 31978fc..1581566 100644
--- a/src/otko/core/catalog/__init__.py
+++ b/src/otko/core/catalog/__init__.py
@@ -8,9 +8,7 @@ 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
@@ -23,12 +21,8 @@ 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
@@ -56,16 +50,10 @@ 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
@@ -79,124 +67,64 @@ 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 170a391..d0aed63 100644
--- a/src/otko/core/catalog/generated/__init__.py
+++ b/src/otko/core/catalog/generated/__init__.py
@@ -7,9 +7,7 @@ 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
@@ -22,12 +20,8 @@ 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
@@ -55,16 +49,10 @@ 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
@@ -77,141 +65,19 @@ 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 52c13e1..63a05a4 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,40 +498,29 @@ 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 e25d1d4..880646f 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,45 +654,34 @@ 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 032d37f..884e4f0 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,15 +112,16 @@ 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 7316257..9e51c2b 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,36 +202,15 @@ 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 13c9c60..cf44214 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,36 +202,18 @@ 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 f4df18b..061e782 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,35 +202,17 @@ 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 771a25b..4fce8b6 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,38 +202,20 @@ 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 224cecf..a8a3593 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,37 +228,19 @@ 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 fd52e19..3f4328b 100644
--- a/src/otko/core/catalog/generated/conditions/__init__.py
+++ b/src/otko/core/catalog/generated/conditions/__init__.py
@@ -15,173 +15,42 @@ 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 3c063d7..e1c021d 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,9 +15,10 @@ 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 16131a0..250dbec 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,13 +100,12 @@ 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 140d8f2..d00e49a 100644
--- a/src/otko/core/catalog/generated/conditions/line_displacements.py
+++ b/src/otko/core/catalog/generated/conditions/line_displacements.py
@@ -15,14 +15,15 @@ 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 43d050a..cdfa971 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,17 +17,16 @@ 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 69a5422..e03e48a 100644
--- a/src/otko/core/catalog/generated/conditions/line_forces.py
+++ b/src/otko/core/catalog/generated/conditions/line_forces.py
@@ -15,14 +15,15 @@ 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 c4b57a2..891c038 100644
--- a/src/otko/core/catalog/generated/conditions/line_mass.py
+++ b/src/otko/core/catalog/generated/conditions/line_mass.py
@@ -17,15 +17,16 @@ 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 dc8a0b1..5f24808 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,3 +25,4 @@ 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 57679ed..fbf062d 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,3 +25,4 @@ 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 15ef27e..74455ee 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,13 +310,12 @@ 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 aa0b5b9..107a4fb 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,10 +17,11 @@ 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 d105126..abfff93 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,12 +17,11 @@ 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 3303b6b..0a0cbc2 100644
--- a/src/otko/core/catalog/generated/conditions/line_uniform_forces.py
+++ b/src/otko/core/catalog/generated/conditions/line_uniform_forces.py
@@ -15,11 +15,12 @@ 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 600c99e..5d96720 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,14 +17,15 @@ 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 8a52b87..b8403c9 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,11 +15,10 @@ 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 01c115d..c51ad33 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,13 +100,12 @@ 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 e95eb4a..45073fd 100644
--- a/src/otko/core/catalog/generated/conditions/point_displacements.py
+++ b/src/otko/core/catalog/generated/conditions/point_displacements.py
@@ -15,14 +15,15 @@ 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 5c8465e..b5166b0 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,11 +15,10 @@ 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 969e08e..b5106af 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,17 +17,16 @@ 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 2bf40d5..599b530 100644
--- a/src/otko/core/catalog/generated/conditions/point_forces.py
+++ b/src/otko/core/catalog/generated/conditions/point_forces.py
@@ -15,14 +15,15 @@ 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 9898b6d..da6ddef 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,10 +17,11 @@ 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 5afb059..d4143f8 100644
--- a/src/otko/core/catalog/generated/conditions/point_mass.py
+++ b/src/otko/core/catalog/generated/conditions/point_mass.py
@@ -17,15 +17,16 @@ 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 70155a1..24dd864 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,3 +25,4 @@ 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 4a45da1..ea4fbd6 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,13 +310,12 @@ 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 3bdb1f3..1996c5f 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,9 +15,10 @@ 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 bc69de3..115514d 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,10 +17,11 @@ 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 cb56d02..8c0bad6 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,9 +15,10 @@ 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 6d9cdc9..fac5ffd 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,12 +17,11 @@ 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 aa82b6f..7be402d 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,10 +60,11 @@ 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 813ace9..c0aa22e 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,15 +181,16 @@ 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 c9c7b25..a2c2a1b 100644
--- a/src/otko/core/catalog/generated/conditions/surface_displacements.py
+++ b/src/otko/core/catalog/generated/conditions/surface_displacements.py
@@ -15,14 +15,15 @@ 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 dbf8ea9..ac53c76 100644
--- a/src/otko/core/catalog/generated/conditions/surface_forces.py
+++ b/src/otko/core/catalog/generated/conditions/surface_forces.py
@@ -15,14 +15,15 @@ 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 1001fb0..b7c08ec 100644
--- a/src/otko/core/catalog/generated/conditions/surface_mass.py
+++ b/src/otko/core/catalog/generated/conditions/surface_mass.py
@@ -17,15 +17,16 @@ 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 d32d2aa..e33f80f 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,3 +25,4 @@ 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 f3ed8c9..d086691 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,3 +25,4 @@ 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 c88e9d1..7c40c31 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,13 +310,12 @@ 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 a527d58..2281556 100644
--- a/src/otko/core/catalog/generated/conditions/volume_mass.py
+++ b/src/otko/core/catalog/generated/conditions/volume_mass.py
@@ -17,15 +17,16 @@ 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 6f25b2f..48a38d0 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,3 +25,4 @@ 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 c268826..eb57cdd 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,3 +25,4 @@ 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 7b59d3b..361c6c8 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,15 +181,16 @@ 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 6e685b9..040344a 100644
--- a/src/otko/core/catalog/generated/contact.py
+++ b/src/otko/core/catalog/generated/contact.py
@@ -17,13 +17,14 @@ 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 04bdb57..2638cb1 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,22 +216,21 @@ 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 58bf76b..77e3c1a 100644
--- a/src/otko/core/catalog/generated/damage2p.py
+++ b/src/otko/core/catalog/generated/damage2p.py
@@ -17,22 +17,21 @@ 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 1ccfce7..e23893a 100644
--- a/src/otko/core/catalog/generated/displacement_based_beam_column.py
+++ b/src/otko/core/catalog/generated/displacement_based_beam_column.py
@@ -17,19 +17,18 @@ 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 ad411c7..79b6ce1 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,32 +213,16 @@ 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 0e575f5..4321f87 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,24 +306,25 @@ 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 86cbeb5..8443902 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,24 +60,10 @@ 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 af479db..a19ab57 100644
--- a/src/otko/core/catalog/generated/elastic_membrane_plate.py
+++ b/src/otko/core/catalog/generated/elastic_membrane_plate.py
@@ -17,13 +17,14 @@ 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 f1a225a..5a3f863 100644
--- a/src/otko/core/catalog/generated/elastic_orthotropic.py
+++ b/src/otko/core/catalog/generated/elastic_orthotropic.py
@@ -17,19 +17,20 @@ 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 fee4bf3..4b36761 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,50 +358,26 @@ 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 e460af0..27221b1 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,45 +333,23 @@ 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 dc17b4f..2d98fb1 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,25 +352,26 @@ 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 ca8b6fc..8e02988 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,33 +326,27 @@ 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 5ee6e3f..a047e2d 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,36 +1988,34 @@ 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
@@ -2026,14 +2024,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
@@ -2042,24 +2040,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
@@ -2075,12 +2073,13 @@ 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 4850326..bce9428 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,17 +50,18 @@ 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 9f9a812..193d671 100644
--- a/src/otko/core/catalog/generated/fiberint.py
+++ b/src/otko/core/catalog/generated/fiberint.py
@@ -17,32 +17,33 @@ 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 10de81b..c0a6567 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,20 +17,19 @@ 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 183019a..e26d416 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,22 +60,17 @@ 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 b21d04d..e41c545 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,12 +106,11 @@ 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 809b4b7..bed462d 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,11 +56,12 @@ 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 1f6d3ad..66b7f0e 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,48 +617,49 @@ 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 e5cc1e9..1ea1283 100644
--- a/src/otko/core/catalog/generated/initial_strain.py
+++ b/src/otko/core/catalog/generated/initial_strain.py
@@ -17,11 +17,12 @@ 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 4938462..cd31467 100644
--- a/src/otko/core/catalog/generated/initial_stress.py
+++ b/src/otko/core/catalog/generated/initial_stress.py
@@ -17,11 +17,12 @@ 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 1dc77e9..9cea9ee 100644
--- a/src/otko/core/catalog/generated/j2plasticity.py
+++ b/src/otko/core/catalog/generated/j2plasticity.py
@@ -17,15 +17,16 @@ 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 3b9f2d2..515478e 100644
--- a/src/otko/core/catalog/generated/layeredshell.py
+++ b/src/otko/core/catalog/generated/layeredshell.py
@@ -17,40 +17,41 @@ 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 a066d64..343b55d 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,14 +180,15 @@ 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 813e7e1..b613566 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,14 +154,15 @@ 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 93c2073..45facaa 100644
--- a/src/otko/core/catalog/generated/plate_fiber.py
+++ b/src/otko/core/catalog/generated/plate_fiber.py
@@ -17,14 +17,13 @@ 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 e510fa8..e62d0c3 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,36 +76,30 @@ 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 b60cd29..253454d 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,33 +76,32 @@ 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 d239293..89a9a68 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,17 +76,18 @@ 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 73e8724..d2c6230 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,21 +56,12 @@ 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 c283988..2b31ef5 100644
--- a/src/otko/core/catalog/generated/quad.py
+++ b/src/otko/core/catalog/generated/quad.py
@@ -17,25 +17,20 @@ 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 6393d90..a1fb541 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,28 +50,18 @@ 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 61fdbbc..4641469 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,22 +56,12 @@ 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 e029923..2b451c3 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,13 +56,14 @@ 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 cf6f384..0cfb1ec 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,15 +56,16 @@ 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 dc5c4d3..9a6b95a 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,20 +206,21 @@ 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 47f00ee..6eb399c 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,14 +154,15 @@ 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 e47d08a..1991283 100644
--- a/src/otko/core/catalog/generated/shell.py
+++ b/src/otko/core/catalog/generated/shell.py
@@ -17,17 +17,13 @@ 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 030a9ea..5fee640 100644
--- a/src/otko/core/catalog/generated/shelldkgq.py
+++ b/src/otko/core/catalog/generated/shelldkgq.py
@@ -17,15 +17,13 @@ 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 927ada6..8e91dd5 100644
--- a/src/otko/core/catalog/generated/standard_brick_element.py
+++ b/src/otko/core/catalog/generated/standard_brick_element.py
@@ -17,18 +17,16 @@ 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 0f3c320..a928a79 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,27 +182,23 @@ 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 19b2c51..9fe1c55 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,34 +227,30 @@ 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 418d78b..ada28d8 100644
--- a/src/otko/core/catalog/generated/tri31.py
+++ b/src/otko/core/catalog/generated/tri31.py
@@ -17,20 +17,19 @@ 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 a337c69..c789e73 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,22 +216,21 @@ 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 39cb493..819052a 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,17 +56,11 @@ 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 70adc8c..5f43e7d 100644
--- a/src/otko/core/catalog/generated/user_material.py
+++ b/src/otko/core/catalog/generated/user_material.py
@@ -17,11 +17,12 @@ 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 fd9c380..e659a9a 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,10 +56,11 @@ 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 1f53eac..c2c021d 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,13 +82,14 @@ 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 0980d6b..9742d21 100644
--- a/src/otko/core/defaults.py
+++ b/src/otko/core/defaults.py
@@ -159,8 +159,12 @@ 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 35a2026..f78588f 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
+from typing import Annotated, Literal, Union
from pydantic import Field, PositiveFloat, PositiveInt
@@ -91,9 +91,7 @@ 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=(
@@ -123,9 +121,7 @@ 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):
@@ -183,8 +179,7 @@ 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(
@@ -198,14 +193,16 @@ class QuadElement(Entity):
Element = Annotated[
- TrussElement
- | CorotTrussElement
- | ElasticBeamColumn
- | ForceBeamColumn
- | DispBeamColumn
- | ZeroLengthElement
- | ZeroLengthSectionElement
- | BeamWithHingesElement
- | QuadElement,
+ Union[
+ 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 f7d61ea..d726f68 100644
--- a/src/otko/core/geometry/grid.py
+++ b/src/otko/core/geometry/grid.py
@@ -87,17 +87,15 @@ 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)
@@ -123,21 +121,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):
@@ -162,9 +160,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 68a1557..77429fc 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
+from typing import Annotated, Literal, Union
-from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator
+from pydantic import BaseModel, ConfigDict, Field, PositiveInt
from otko.core._base import Entity
@@ -77,19 +77,15 @@ 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.",
)
@@ -110,7 +106,7 @@ class ResponseSpectrum(Entity):
TimeSeries = Annotated[
- LinearTimeSeries | ConstantTimeSeries | PathTimeSeries,
+ Union[LinearTimeSeries, ConstantTimeSeries, PathTimeSeries],
Field(discriminator="type"),
]
@@ -189,67 +185,13 @@ 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[
- PlainLoadPattern | UniformExcitationPattern | ImposedSupportMotionPattern,
+ Union[PlainLoadPattern, UniformExcitationPattern, ImposedSupportMotionPattern],
Field(discriminator="type"),
]
diff --git a/src/otko/core/materials/__init__.py b/src/otko/core/materials/__init__.py
index daa84f6..a12d75e 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
+from typing import Annotated, Literal, Union
from pydantic import Field, PositiveFloat, model_validator
@@ -58,9 +58,7 @@ 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)
@@ -85,10 +83,7 @@ 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.")
@@ -113,24 +108,20 @@ 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:
@@ -147,8 +138,7 @@ 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.")
@@ -186,8 +176,7 @@ 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).",
)
@@ -217,8 +206,7 @@ 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(
@@ -229,16 +217,18 @@ class HystereticSM(Entity):
# ──────────────────────────── Discriminated union ────────────────────────────
Material = Annotated[
- ElasticIsotropic
- | ElasticUniaxial
- | Steel01
- | Steel02
- | Concrete01
- | Concrete02
- | Concrete04
- | ElasticPP
- | HystereticMaterial
- | HystereticSM,
+ Union[
+ 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 f2d030a..5ed9046 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 LoadCombination, LoadPattern, ResponseSpectrum, TimeSeries
+from otko.core.loads import LoadPattern, ResponseSpectrum, TimeSeries
from otko.core.materials import Material
from otko.core.sections import Section
from otko.core.units import UnitSystem
@@ -110,7 +110,6 @@ _INDEX_KINDS: tuple[str, ...] = (
"load_patterns",
"spectra",
"analyses",
- "combinations",
)
@@ -156,17 +155,15 @@ 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:
@@ -174,7 +171,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``,
@@ -206,12 +203,10 @@ 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)
@@ -219,11 +214,10 @@ 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(
@@ -233,7 +227,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),
@@ -242,7 +236,6 @@ 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)):
@@ -277,10 +270,6 @@ 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"))
@@ -294,20 +283,6 @@ 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.
@@ -420,14 +395,5 @@ 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 530e0ca..5e8ce0e 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
+from typing import Annotated, Literal, Union
from pydantic import BaseModel, ConfigDict, Field, PositiveFloat, PositiveInt
@@ -61,7 +61,7 @@ class RectShape(BaseModel):
SectionShape = Annotated[
- PipeShape | AngleShape | RectShape,
+ Union[PipeShape, AngleShape, RectShape],
Field(discriminator="kind"),
]
@@ -75,12 +75,8 @@ 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=(
@@ -165,12 +161,12 @@ class StraightLayer(BaseModel):
Patch = Annotated[
- RectangularPatch | CircularPatch,
+ Union[RectangularPatch, CircularPatch],
Field(discriminator="kind"),
]
Layer = Annotated[
- StraightLayer,
+ Union[StraightLayer],
Field(discriminator="kind"),
]
@@ -202,8 +198,7 @@ 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).",
)
@@ -225,6 +220,6 @@ class SectionAggregator(Entity):
Section = Annotated[
- ElasticSection | FiberSection | SectionAggregator,
+ Union[ElasticSection, FiberSection, SectionAggregator],
Field(discriminator="type"),
]
diff --git a/src/otko/core/units.py b/src/otko/core/units.py
index 63b40cc..426ccd2 100644
--- a/src/otko/core/units.py
+++ b/src/otko/core/units.py
@@ -42,46 +42,30 @@ 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 0c14bb3..b14f725 100644
--- a/src/otko/services/__init__.py
+++ b/src/otko/services/__init__.py
@@ -6,12 +6,6 @@ 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,
@@ -34,16 +28,12 @@ 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 ea6507a..61e7530 100644
--- a/src/otko/services/_emit.py
+++ b/src/otko/services/_emit.py
@@ -409,10 +409,7 @@ 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]
@@ -433,13 +430,9 @@ 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
@@ -654,27 +647,15 @@ 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 fa80790..c23f5ea 100644
--- a/src/otko/services/animation_export.py
+++ b/src/otko/services/animation_export.py
@@ -12,9 +12,8 @@ the main thread.
from __future__ import annotations
import math
-from collections.abc import Callable
from pathlib import Path
-from typing import Any
+from typing import Any, Callable
import numpy as np
@@ -56,7 +55,8 @@ 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,4 +94,5 @@ 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
deleted file mode 100644
index 20a0d3a..0000000
--- a/src/otko/services/combinations.py
+++ /dev/null
@@ -1,235 +0,0 @@
-"""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 8c60f7b..a93340f 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,11 +38,8 @@ 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.
@@ -60,16 +57,13 @@ 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.
@@ -84,9 +78,10 @@ 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
@@ -106,15 +101,12 @@ 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.
@@ -147,7 +139,8 @@ 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 d802579..36de591 100644
--- a/src/otko/services/element_forces.py
+++ b/src/otko/services/element_forces.py
@@ -41,36 +41,27 @@ 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:
@@ -78,12 +69,10 @@ _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,
}
@@ -133,7 +122,6 @@ 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 5886360..1ae5710 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._const import OPENSEESPY_VERSION, __version__
+from otko import __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 = f"openseespy=={OPENSEESPY_VERSION}"
+PINNED_OPENSEESPY = "openseespy==3.8.0.0"
#: 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 the solver version,
+ The script source. The header pins ``openseespy==3.8.0.0``,
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 e494521..fe70fbc 100644
--- a/src/otko/services/material_tester.py
+++ b/src/otko/services/material_tester.py
@@ -63,9 +63,7 @@ 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.")
@@ -162,35 +160,17 @@ 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]
@@ -207,25 +187,10 @@ 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__}")
@@ -274,13 +239,10 @@ 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 ce99a54..618c2e5 100644
--- a/src/otko/services/peer_record.py
+++ b/src/otko/services/peer_record.py
@@ -11,7 +11,6 @@ building the PathTimeSeries.
from __future__ import annotations
-import contextlib
import re
from pathlib import Path
@@ -47,8 +46,7 @@ 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))
@@ -65,9 +63,10 @@ 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():
- # Skip stray non-numeric tokens.
- with contextlib.suppress(ValueError):
+ try:
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
@@ -83,8 +82,10 @@ def parse_plain_values(path: str | Path) -> list[float]:
vals: list[float] = []
for line in text.splitlines():
for tok in line.split():
- with contextlib.suppress(ValueError):
+ try:
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 aeaf199..4419a1b 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,10 +52,8 @@ 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 3163ed5..43b608f 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 18f5a20..edabcc6 100644
--- a/src/otko/services/section_bbox.py
+++ b/src/otko/services/section_bbox.py
@@ -52,10 +52,8 @@ 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
@@ -71,7 +69,6 @@ def bbox_for_section(
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 a637428..a041721 100644
--- a/src/otko/services/section_properties.py
+++ b/src/otko/services/section_properties.py
@@ -21,6 +21,7 @@ import numpy as np
from otko.core.sections import (
CircularPatch,
FiberSection,
+ Fibre,
RectangularPatch,
StraightLayer,
)
@@ -34,9 +35,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:
@@ -64,11 +65,9 @@ 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:
@@ -103,8 +102,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 9cde026..a242dbf 100644
--- a/src/otko/services/spectrum.py
+++ b/src/otko/services/spectrum.py
@@ -24,22 +24,20 @@ 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.
@@ -81,23 +79,21 @@ 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
@@ -141,7 +137,8 @@ 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)
@@ -159,7 +156,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
@@ -172,8 +169,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 5773307..7046e0c 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 9ea06d1..148371d 100644
--- a/src/otko/viewmodels/project_vm.py
+++ b/src/otko/viewmodels/project_vm.py
@@ -11,7 +11,6 @@ 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
@@ -24,8 +23,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:
@@ -41,8 +40,10 @@ class ProjectViewModel(QObject):
@Slot(bool)
def _on_stack_clean_changed(self, clean: bool) -> None:
# Guard against the late-fire that Qt sends during destruction.
- with contextlib.suppress(RuntimeError):
+ try:
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 4352b00..e0f3651 100644
--- a/src/otko/views/action_handlers.py
+++ b/src/otko/views/action_handlers.py
@@ -49,7 +49,6 @@ from otko.views.dialogs import (
AssignZeroLengthSectionDialog,
CoordinateGridSystemsDialog,
LinearTimeSeriesDialog,
- LoadCombinationManagerDialog,
MaterialLibraryDialog,
MaterialTesterDialog,
MirrorDialog,
@@ -918,11 +917,6 @@ 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.")
@@ -967,7 +961,10 @@ 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 57c0a70..949fa35 100644
--- a/src/otko/views/canvas3d/diagram_renderer.py
+++ b/src/otko/views/canvas3d/diagram_renderer.py
@@ -22,7 +22,6 @@ Convention:
from __future__ import annotations
-import contextlib
import logging
from typing import Any
@@ -31,26 +30,18 @@ import pyvista as pv
from otko.core import Project
from otko.services.element_forces import DiagramData, ForceComponent
-from otko.views.canvas3d.style import RenderStyle
_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,
}
@@ -63,9 +54,8 @@ class DiagramRenderer:
- ``clear()`` removes the overlay.
"""
- def __init__(self, plotter: Any, style: RenderStyle | None = None) -> None:
+ def __init__(self, plotter: Any) -> None:
self._plotter = plotter
- self._style = style or RenderStyle()
self._actor: Any = None
self._label_actor: Any = None
@@ -79,10 +69,8 @@ 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}
@@ -91,8 +79,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:
@@ -118,19 +106,15 @@ 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]))
@@ -154,15 +138,11 @@ 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",
- # Shared response palette (opstool's default diverging scale), so
- # the PyVista and Plotly backends stay visually consistent.
- cmap=list(self._style.response_scale_colors),
+ cmap="coolwarm",
clim=(-vmax, vmax),
show_scalar_bar=True,
scalar_bar_args={"title": comp_label, "n_labels": 5},
@@ -177,7 +157,8 @@ 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
@@ -222,10 +203,9 @@ 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,
@@ -259,8 +239,10 @@ class DiagramRenderer:
pass
self._actor = None
if self._label_actor is not None:
- with contextlib.suppress(Exception):
+ try:
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 3e00425..b761d62 100644
--- a/src/otko/views/canvas3d/model_canvas.py
+++ b/src/otko/views/canvas3d/model_canvas.py
@@ -12,6 +12,7 @@ needing a reference to the renderer.
from __future__ import annotations
+import os
from typing import Any
import numpy as np
@@ -24,17 +25,13 @@ from otko.core import Project
from otko.views.canvas3d.model_renderer import ModelRenderer
from otko.views.canvas3d.selection import SelectionState
from otko.views.canvas3d.style import RenderStyle
-from otko.views.canvas_base import CanvasCapabilities
-PICK_DEBUG = False
+_PICK_DEBUG = os.environ.get("OSS_PICK_DEBUG") == "1"
class ModelCanvas(QtInteractor): # type: ignore[misc]
"""The central 3D viewport widget."""
- #: VTK supports every optional overlay layer.
- capabilities = CanvasCapabilities()
-
# Convenience signals re-emitted from SelectionState.
nodePicked = Signal(int)
elementPicked = Signal(int)
@@ -130,7 +127,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}"
@@ -151,7 +148,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 "
@@ -184,7 +181,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 "
@@ -194,7 +191,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. ──
@@ -205,7 +202,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:
@@ -263,7 +260,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 "
@@ -342,17 +339,6 @@ class ModelCanvas(QtInteractor): # type: ignore[misc]
self._renderer.set_show_local_axes(enabled)
self.render()
- def set_style(self, style: RenderStyle) -> None:
- """Swap the visual style and repaint the scene.
-
- Applies the background immediately (it lives outside the renderer)
- and rebuilds the actors whose colours come from the style LUTs.
- """
- self._style = style
- self.set_background(style.background_bottom, top=style.background_top)
- self._renderer.set_style(style)
- self.render()
-
def set_display_options(self, *, show_node_labels: bool, show_element_labels: bool) -> None:
"""Toggle viewport labels such as node and element names."""
self._renderer.set_display_options(
diff --git a/src/otko/views/canvas3d/model_renderer.py b/src/otko/views/canvas3d/model_renderer.py
index 12c88cb..568c079 100644
--- a/src/otko/views/canvas3d/model_renderer.py
+++ b/src/otko/views/canvas3d/model_renderer.py
@@ -10,7 +10,6 @@ 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
@@ -25,20 +24,15 @@ from otko.core import (
ElasticBeamColumn,
ForceBeamColumn,
NodalLoad,
+ QuadElement,
+ UniformElementLoad,
PlainLoadPattern,
Project,
- QuadElement,
TrussElement,
- UniformElementLoad,
ZeroLengthElement,
ZeroLengthSectionElement,
)
-from otko.views.canvas3d.style import (
- SELECTED_STATE,
- RenderStyle,
- element_family_index,
- family_palette,
-)
+from otko.views.canvas3d.style import RenderStyle
class RendererMode(enum.Enum):
@@ -190,6 +184,9 @@ 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
+
def __init__(self, plotter: Any, style: RenderStyle | None = None) -> None:
self._plotter = plotter
self._style = style or RenderStyle()
@@ -208,8 +205,6 @@ class ModelRenderer:
self._frame_actor: Any = None
self._frame_ids_ordered: list[int] = []
self._frame_id_to_row: dict[int, int] = {}
- #: Per-frame palette index (family), parallel to ``_frame_ids_ordered``.
- self._frame_family: list[int] = []
self._node_label_actor: Any = None
self._element_label_actor: Any = None
self._show_node_labels: bool = False
@@ -224,8 +219,10 @@ 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
- with contextlib.suppress(Exception):
+ try:
self._plotter.enable_anti_aliasing("ssaa")
+ except Exception:
+ pass
# ── public API ───────────────────────────────────────────────────
def render(self, project: Project | None) -> None:
@@ -282,16 +279,6 @@ class ModelRenderer:
if self._project is not None:
self.render(self._project)
- def set_style(self, style: RenderStyle) -> None:
- """Swap the visual style and rebuild the scene it colours.
-
- Colours are baked into actor LUTs at build time, so a style change
- needs a full re-render rather than a scalar-array tweak.
- """
- self._style = style
- if self._project is not None:
- self.render(self._project)
-
def set_working_plane(self, plane: tuple[str, float] | None) -> None:
"""Update the working-plane filter and rebuild the grid overlay.
@@ -320,12 +307,11 @@ class ModelRenderer:
self._reglyph_nodes()
if self._frame_pd is not None and self._frame_ids_ordered:
- # Restore each cell to its family slot, then flag the selected ones.
- states = np.array(self._frame_family, dtype=np.int8)
+ states = np.zeros(len(self._frame_ids_ordered), dtype=np.int8)
for eid in element_ids:
row = self._frame_id_to_row.get(eid)
if row is not None:
- states[row] = SELECTED_STATE
+ states[row] = 1
self._frame_pd.cell_data["_oss_state"] = states
self._frame_pd.Modified()
@@ -343,8 +329,10 @@ class ModelRenderer:
"""
# Remove any previous marker.
if self._hover_actor is not None:
- with contextlib.suppress(Exception):
+ try:
self._plotter.remove_actor(self._hover_actor, render=False)
+ except Exception:
+ pass
self._hover_actor = None
if world_point is None:
@@ -388,13 +376,15 @@ 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:
- with contextlib.suppress(Exception):
+ try:
self._plotter.remove_actor(self._node_actor, render=False)
+ except Exception:
+ pass
self._node_glyph = glyph
self._node_actor = self._plotter.add_mesh(
glyph,
scalars="_oss_state",
- cmap=[self._style.node_color, self._style.node_selected_color],
+ cmap=self._NODE_LUT,
clim=[0, 1],
show_scalar_bar=False,
pickable=True,
@@ -407,7 +397,6 @@ class ModelRenderer:
return
cells: list[int] = []
ids: list[int] = []
- families: list[int] = []
for el in frames:
try:
i = self._node_id_to_row[el.nodes[0]]
@@ -416,7 +405,6 @@ class ModelRenderer:
continue
cells.extend([2, i, j])
ids.append(el.id)
- families.append(element_family_index(el))
if not ids:
return
pd = pv.PolyData()
@@ -424,18 +412,16 @@ class ModelRenderer:
pd.lines = np.array(cells, dtype=np.int64)
pd.cell_data["_oss_id"] = np.array(ids, dtype=np.int64)
pd.cell_data["_oss_kind"] = np.array(["element"] * len(ids), dtype=object)
- # Cell scalar carries the palette slot (family, or SELECTED_STATE).
- pd.cell_data["_oss_state"] = np.array(families, dtype=np.int8)
+ pd.cell_data["_oss_state"] = np.zeros(len(ids), dtype=np.int8)
self._frame_pd = pd
self._frame_ids_ordered = ids
self._frame_id_to_row = {eid: i for i, eid in enumerate(ids)}
- self._frame_family = families
self._frame_actor = self._plotter.add_mesh(
pd,
scalars="_oss_state",
- cmap=family_palette(self._style),
- clim=[0, SELECTED_STATE],
+ cmap=self._FRAME_LUT,
+ clim=[0, 1],
show_scalar_bar=False,
line_width=3.0,
pickable=True,
@@ -498,17 +484,10 @@ class ModelRenderer:
plane_axis = axis_idx
plane_offset_local = plane_off - cs_shift
- # 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:
+ def _on_active_plane(local_pt: tuple[float, float, float]) -> bool:
+ if plane_axis is None:
return True
- return abs(local_pt[_axis] - _offset) < 1e-6
+ return abs(local_pt[plane_axis] - plane_offset_local) < 1e-6 # type: ignore[operator]
# Collect active + dim segments separately so they get their
# own polydata + actor (different opacity / color).
@@ -517,21 +496,13 @@ 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],
- _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:
+ def add_seg(p1: tuple[float, float, float], p2: tuple[float, float, float]) -> 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]
@@ -643,7 +614,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])
@@ -694,16 +665,18 @@ 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])
@@ -780,8 +753,8 @@ class ModelRenderer:
"""Add one semi-transparent steel-blue non-pickable aux actor."""
actor = self._plotter.add_mesh(
mesh,
- color=self._style.extrusion_color,
- opacity=self._style.extrusion_opacity,
+ color=(0.35, 0.60, 0.85), # cool steel-blue
+ opacity=0.22,
show_edges=True,
edge_color=(0.15, 0.25, 0.45),
line_width=1.0,
@@ -1001,7 +974,7 @@ class ModelRenderer:
ndf = project.ndf
dof_idx = _dof_indices(ndf)
size = max(self._diag_of_points(self._node_original_points) * 0.015, 1e-6)
- support_color = self._style.support_color
+ support_color = (1.0, 0.5, 0.1)
for node in project.nodes:
if not any(node.restraint[i] for i in dof_idx):
continue
@@ -1015,7 +988,7 @@ class ModelRenderer:
if not project.load_patterns or self._node_original_points is None:
return
scale = max(self._diag_of_points(self._node_original_points) * 0.05, 1e-6)
- load_color = self._style.nodal_load_color
+ load_color = (0.2, 0.85, 0.2)
node_by_id = {n.id: n for n in project.nodes}
elem_by_id = {e.id: e for e in project.elements}
for pattern in project.load_patterns:
@@ -1046,7 +1019,7 @@ class ModelRenderer:
# Draw N arrows along the element span, each perpendicular
# to the axis in the direction of the load. Uses the same
# orange-green palette as nodal loads but with shorter arrows.
- elem_load_color = self._style.element_load_color # distributed loads
+ elem_load_color = (1.0, 0.55, 0.2) # orange
n_arrows_per_elem = 5
for eload in pattern.element_loads:
if not isinstance(eload, UniformElementLoad):
@@ -1121,11 +1094,15 @@ class ModelRenderer:
self._clear_label_actors()
for a in (self._node_actor, self._frame_actor):
if a is not None:
- with contextlib.suppress(Exception):
+ try:
self._plotter.remove_actor(a, render=False)
+ except Exception:
+ pass
for a in self._aux_actors:
- with contextlib.suppress(Exception):
+ try:
self._plotter.remove_actor(a, render=False)
+ except Exception:
+ pass
self._node_actor = None
self._frame_actor = None
self._aux_actors.clear()
@@ -1135,7 +1112,6 @@ class ModelRenderer:
self._node_ids_ordered = []
self._node_id_to_row = {}
self._frame_ids_ordered = []
- self._frame_family = []
self._frame_id_to_row = {}
self._node_original_points = None
self._deformation = None
@@ -1144,8 +1120,10 @@ class ModelRenderer:
def _clear_label_actors(self) -> None:
for actor in (self._node_label_actor, self._element_label_actor):
if actor is not None:
- with contextlib.suppress(Exception):
+ try:
self._plotter.remove_actor(actor, render=False)
+ except Exception:
+ pass
self._node_label_actor = None
self._element_label_actor = None
@@ -1168,7 +1146,7 @@ class ModelRenderer:
return self._plotter.add_point_labels(
pts,
labels,
- font_size=self._style.label_font_size,
+ font_size=12,
shape_opacity=0.15,
text_color="black",
point_color="white",
@@ -1198,7 +1176,7 @@ class ModelRenderer:
return self._plotter.add_point_labels(
np.asarray(centers),
labels,
- font_size=self._style.label_font_size,
+ font_size=12,
shape_opacity=0.15,
text_color="black",
point_color="white",
diff --git a/src/otko/views/canvas3d/style.py b/src/otko/views/canvas3d/style.py
index a55d43b..21b7a34 100644
--- a/src/otko/views/canvas3d/style.py
+++ b/src/otko/views/canvas3d/style.py
@@ -4,64 +4,11 @@ All colors, sizes, and glyph parameters live here. Changes apply
globally through the renderer; downstream code never hard-codes a
color or radius. A future "theme" feature can simply swap a different
RenderStyle instance.
-
-The subset in :attr:`RenderStyle.EDITABLE` is surfaced to the user through
-Options → Plot Properties… and persisted in ``QSettings`` (``plot/props``).
-Palette values marked "opstool" are adapted from opstool v1.0.26 (GPL-3.0);
-see ``NOTICE``.
"""
from __future__ import annotations
-from dataclasses import dataclass, replace
-from typing import Any, ClassVar
-
-from otko.core import CorotTrussElement, TrussElement, ZeroLengthElement
-
-#: Selected elements / nodes occupy the last slot of the palette LUT.
-SELECTED_STATE: int = 3
-
-
-def element_family_index(element: object) -> int:
- """Palette slot for a frame element: 0 beam, 1 truss, 2 link/zero-length."""
- if isinstance(element, TrussElement | CorotTrussElement):
- return 1
- if isinstance(element, ZeroLengthElement):
- return 2
- return 0
-
-
-def family_palette(style: RenderStyle) -> list[str]:
- """LUT shared by both backends: [beam, truss, link, selected]."""
- return [
- style.element_beam_color,
- style.element_truss_color,
- style.element_link_color,
- style.selected_color,
- ]
-
-
-#: Colours / sizes the Plot Properties dialog exposes, in display order.
-STYLE_FIELDS: tuple[tuple[str, str, str], ...] = (
- ("element_beam_color", "Beam elements", "color"),
- ("element_truss_color", "Truss elements", "color"),
- ("element_link_color", "Link / zero-length", "color"),
- ("selected_color", "Selection highlight", "color"),
- ("node_color", "Nodes", "color"),
- ("node_selected_color", "Selected nodes", "color"),
- ("support_color", "Supports", "color"),
- ("nodal_load_color", "Nodal loads", "color"),
- ("element_load_color", "Element loads", "color"),
- ("extrusion_color", "Section extrusions", "color"),
- ("extrusion_opacity", "Extrusion opacity", "float"),
- ("background_top", "Background (top)", "color"),
- ("background_bottom", "Background (bottom)", "color"),
- ("show_axis_outline", "Axis outline (grid + ticks)", "bool"),
- ("label_font_size", "Label font size", "int"),
-)
-
-#: Field names the dialog edits and that are persisted between sessions.
-EDITABLE_FIELDS: tuple[str, ...] = tuple(name for name, _label, _kind in STYLE_FIELDS)
+from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
@@ -72,58 +19,20 @@ class RenderStyle:
background_top: str = "#dbe2ef"
background_bottom: str = "#f5f7fb"
- node_color: str = "#d9d9d9" # normal node sphere / marker
- node_selected_color: str = "#00ffff" # selected node
+ node_color: str = "#f0a500" # warm gold
+ node_selected_color: str = "#00d4ff" # bright cyan
frame_color: str = "#1f1f1f"
- truss_color: str = "#2e5cb8" # also the Z-axis colour of the view triad
+ truss_color: str = "#2e5cb8"
zerolength_color: str = "#a020f0"
- selected_color: str = "#00ffff" # selected element highlight
+ selected_color: str = "#00d4ff"
- # ── element palette (adapted from opstool v1.0.26, GPL-3.0) ───────
- # opstool colours wireframe elements by *family* rather than painting
- # every frame the same colour, which makes mixed beam/truss/link models
- # readable at a glance. Values come from ``PLOT_ARGS_DEFAULT`` in
- # ``opstool/vis/{pyvista,plotly}/plot_utils.py``; see ``NOTICE``.
- element_beam_color: str = "#0652ff" # beam-column family
- element_truss_color: str = "#FF8C00" # truss family
- element_link_color: str = "#39FF14" # zero-length / link family
-
- # ── overlay colours ─────────────────────────────────────────────
- support_color: str = "#ff8019"
- nodal_load_color: str = "#33d933"
- element_load_color: str = "#ff8c33"
- extrusion_color: str = "#598cff"
- extrusion_opacity: float = 0.22
- label_font_size: int = 12
-
- #: Draw the plotly scene grid + tick marks around the model (the "outline"
- #: of opstool's scene recipe). Off by default to keep the SAP2000-like
- #: clean viewport; the coloured X/Y/Z axis lines always stay visible.
- show_axis_outline: bool = False
-
- #: Diverging scale for scalar response overlays (force diagrams today,
- #: nodal / element response plots later): blue → red, evenly spaced.
- #: This is opstool's ``default_cmap`` (RdYlBu reversed).
- response_scale_colors: tuple[str, ...] = (
- "#313695",
- "#4575b4",
- "#74add1",
- "#abd9e9",
- "#e0f3f8",
- "#fee090",
- "#fdae61",
- "#f46d43",
- "#d73027",
- "#a50026",
- )
-
- fix_color: str = "#c0392b" # firebrick
+ fix_color: str = "#c0392b" # firebrick
pin_color: str = "#c0392b"
roller_color: str = "#e67e22"
custom_support_color: str = "#7f8c8d"
- load_color: str = "#27ae60" # Y-axis colour of the view triad
+ load_color: str = "#27ae60"
mass_color: str = "#9b59b6"
# ── sizes (relative to bbox diagonal unless absolute) ────────────
@@ -136,28 +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
-
- #: Names of the fields the Plot Properties dialog edits.
- EDITABLE: ClassVar[tuple[str, ...]] = EDITABLE_FIELDS
-
- # ── helpers ──────────────────────────────────────────────────────
- def response_colorscale(self) -> list[tuple[float, str]]:
- """Plotly ``colorscale`` form of :attr:`response_scale_colors`."""
- count = len(self.response_scale_colors)
- if count < 2:
- return [(0.0, self.response_scale_colors[0])]
- return [
- (index / (count - 1), color) for index, color in enumerate(self.response_scale_colors)
- ]
-
- def with_updates(self, **changes: Any) -> RenderStyle:
- """Return a copy with ``changes`` applied (frozen-dataclass safe)."""
- return replace(self, **changes)
-
- def editable_values(self) -> dict[str, Any]:
- """The dialog-managed subset of this style, as a plain dict."""
- return {name: getattr(self, name) for name in self.EDITABLE}
diff --git a/src/otko/views/canvas_base.py b/src/otko/views/canvas_base.py
deleted file mode 100644
index 0352961..0000000
--- a/src/otko/views/canvas_base.py
+++ /dev/null
@@ -1,92 +0,0 @@
-"""Backend-agnostic contract for the central 3D canvas.
-
-Two backends implement it:
-
-- :class:`otko.views.canvas3d.ModelCanvas` — PyVista/VTK (native OpenGL, default)
-- :class:`otko.views.canvas_plotly.PlotlyCanvas` — plotly.js in a QWebEngineView
-
-The ``MainWindow`` swaps between them at runtime, so both must expose the same
-public surface (signals, selection, working plane, view presets, display
-toggles) and declare what optional overlay layers they support via
-:class:`CanvasCapabilities`.
-"""
-
-from __future__ import annotations
-
-import importlib.util
-from dataclasses import dataclass
-from typing import Any, Protocol
-
-
-def plotly_available() -> bool:
- """True when the optional ``plotly`` package can be imported.
-
- The Plotly backend needs plotly.js (shipped inside the ``plotly`` wheel)
- to render; without it the backend menu entry is disabled.
- """
- return importlib.util.find_spec("plotly") is not None
-
-
-@dataclass(frozen=True)
-class CanvasCapabilities:
- """Optional overlay layers a backend may or may not implement.
-
- Consumers query these (see ``RenderControls._refresh_action_enablement``)
- instead of assuming a feature works on every backend.
- """
-
- #: Force-diagram ribbon overlay (N/V/M diagrams).
- diagrams: bool = True
- #: SAP2000-style section extrusion overlay.
- extrusions: bool = True
- #: Per-element local-axis triad overlay.
- local_axes: bool = True
- #: Node / element text labels.
- labels: bool = True
- #: Off-screen frame capture (mode-shape / time-history video export).
- animation_export: bool = True
-
-
-class CanvasBackend(Protocol):
- """Structural contract implemented by every canvas backend.
-
- This mirrors :class:`ModelCanvas`; it exists so the swap path and the
- consumers have one written-down interface to code against.
- """
-
- capabilities: CanvasCapabilities
- selection: Any
-
- def show_project(self, project: Any) -> None: ...
-
- def clear_model(self) -> None: ...
-
- def render(self) -> None: ...
-
- def reset_camera(self) -> None: ...
-
- def view_isometric(self) -> None: ...
-
- def view_xy(self) -> None: ...
-
- def view_xz(self) -> None: ...
-
- def view_yz(self) -> None: ...
-
- def set_working_plane(self, plane: str, offset: float) -> None: ...
-
- def clear_working_plane(self) -> None: ...
-
- def working_plane_type(self) -> str | None: ...
-
- def working_plane_offset(self) -> float | None: ...
-
- def set_snap_preview_enabled(self, enabled: bool) -> None: ...
-
- def set_show_section_extrusions(self, enabled: bool) -> None: ...
-
- def set_show_local_axes(self, enabled: bool) -> None: ...
-
- def set_display_options(self, *, show_node_labels: bool, show_element_labels: bool) -> None: ...
-
- def set_default_selection_enabled(self, enabled: bool) -> None: ...
diff --git a/src/otko/views/canvas_plotly/__init__.py b/src/otko/views/canvas_plotly/__init__.py
deleted file mode 100644
index 7fabb6d..0000000
--- a/src/otko/views/canvas_plotly/__init__.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Plotly-backed canvas package (optional GUI backend).
-
-``PlotlyCanvas`` is exported lazily (PEP 562): the pure
-:mod:`~otko.views.canvas_plotly.trace_builder` must stay importable in the
-headless test job, which has no Qt at all.
-"""
-
-from __future__ import annotations
-
-from typing import Any
-
-from otko.views.canvas_plotly.trace_builder import (
- PlotlyTraceBuilder,
- Scene,
- SceneOptions,
-)
-
-__all__ = ["PlotlyCanvas", "PlotlyTraceBuilder", "Scene", "SceneOptions"]
-
-
-def __getattr__(name: str) -> Any:
- if name == "PlotlyCanvas":
- from otko.views.canvas_plotly.plotly_canvas import PlotlyCanvas
-
- return PlotlyCanvas
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/src/otko/views/canvas_plotly/bridge.py b/src/otko/views/canvas_plotly/bridge.py
deleted file mode 100644
index 9dafbce..0000000
--- a/src/otko/views/canvas_plotly/bridge.py
+++ /dev/null
@@ -1,28 +0,0 @@
-"""Qt ↔ plotly.js bridge exposed through ``QWebChannel``.
-
-The page calls :meth:`_Bridge.picked` / :meth:`_Bridge.snapClicked`; every
-call is re-emitted here and wired to the canvas' own Qt signals.
-"""
-
-from __future__ import annotations
-
-from PySide6.QtCore import QObject, Signal, Slot
-
-
-class _Bridge(QObject):
- """Slot surface the JavaScript side addresses as ``otkoBridge``."""
-
- #: (kind, entity id, additive modifier held) where kind is node|element.
- picked = Signal(str, int, bool)
- #: World-space coordinates of a clicked grid intersection.
- snapClicked = Signal(float, float, float)
-
- @Slot(str, int, bool)
- def onPicked(self, kind: str, entity_id: int, additive: bool) -> None:
- """JS entry point: a model entity (or snap target) was clicked."""
- self.picked.emit(kind, int(entity_id), bool(additive))
-
- @Slot(float, float, float)
- def onSnapClicked(self, x: float, y: float, z: float) -> None:
- """JS entry point: a grid-intersection target was clicked."""
- self.snapClicked.emit(float(x), float(y), float(z))
diff --git a/src/otko/views/canvas_plotly/html.py b/src/otko/views/canvas_plotly/html.py
deleted file mode 100644
index 46e145e..0000000
--- a/src/otko/views/canvas_plotly/html.py
+++ /dev/null
@@ -1,276 +0,0 @@
-"""HTML/JS runtime for the Plotly canvas.
-
-The page is materialised once per process into a temp directory and loaded
-from ``file://``: the plotly.js bundle is ~5 MB, which is past
-``QWebEngineView.setHtml``'s data-URL limit, and writing it to disk also lets
-the browser cache it across figure updates.
-
-The JS side exposes three entry points to Python (called via
-``QWebEnginePage.runJavaScript``):
-
-- ``otkoUpdate(payloadJson)`` — replace data + layout with ``Plotly.react``,
- which diffs client-side and leaves the interactive camera untouched.
-- ``otkoSetCamera(cameraJson)`` — apply a camera alone (view presets,
- parallel-projection toggle).
-- ``otkoSetSnapEnabled(bool)`` — arm/disarm the hover snap-target preview.
-
-Clicks travel the other way through the ``otkoBridge`` QWebChannel object:
-entity picks carry the trace ``meta.kind`` and the point ``customdata``.
-"""
-
-from __future__ import annotations
-
-import atexit
-import shutil
-import tempfile
-from pathlib import Path
-
-#: Materialised runtime directory (plotly.min.js + index.html + qwebchannel.js).
-_ASSETS_DIR: Path | None = None
-
-# ``qrc:///qtwebchannel/qwebchannel.js`` is served by QtWebEngine's internal
-# resource scheme; it is not reachable through QFile.
-_PAGE = """
-
-
-
-
-
-
-
-
-
-
-
-
-"""
-
-
-def runtime_url() -> str:
- """Ensure the JS runtime is on disk and return the page's file path.
-
- Called once per :class:`~otko.views.canvas_plotly.PlotlyCanvas`; the
- directory is reused and removed at interpreter exit.
- """
- global _ASSETS_DIR
- directory = _ensure_dir()
- index = directory / "index.html"
- if not index.exists():
- (directory / "index.html").write_text(_PAGE, encoding="utf-8")
- (directory / "plotly.min.js").write_text(_plotly_js(), encoding="utf-8")
- return str(index)
-
-
-def _ensure_dir() -> Path:
- global _ASSETS_DIR
- if _ASSETS_DIR is None:
- _ASSETS_DIR = Path(tempfile.mkdtemp(prefix="otko-plotly-"))
- atexit.register(shutil.rmtree, _ASSETS_DIR, True)
- return _ASSETS_DIR
-
-
-def _plotly_js() -> str:
- """The offline plotly.js bundle shipped inside the ``plotly`` package."""
- from plotly.offline import get_plotlyjs
-
- return get_plotlyjs()
diff --git a/src/otko/views/canvas_plotly/plotly_canvas.py b/src/otko/views/canvas_plotly/plotly_canvas.py
deleted file mode 100644
index ddecb15..0000000
--- a/src/otko/views/canvas_plotly/plotly_canvas.py
+++ /dev/null
@@ -1,417 +0,0 @@
-"""Plotly-backed 3D canvas.
-
-A ``QWebEngineView`` hosting plotly.js, driven over ``QWebChannel``. It
-implements the same public surface as :class:`otko.views.canvas3d.ModelCanvas`
-(signals, selection, working plane, view presets, display toggles) so
-``MainWindow`` can swap the two at runtime.
-
-Update strategy: figures are pushed with ``Plotly.react``. Because react
-resets any scene attribute the incoming layout omits, a non-framing push is
-flagged ``preserveView`` and the JS side carries the live camera and axis
-ranges forward — so re-rendering on a model edit or selection change never
-yanks the camera the user is orbiting. Only an explicit re-frame (new
-project, view preset, ``reset_camera``, style change) sends the computed
-camera plus opstool-style padded axis ranges.
-"""
-
-from __future__ import annotations
-
-import json
-import math
-from collections.abc import Callable
-from dataclasses import replace
-from typing import Any
-
-from PySide6.QtCore import QObject, QUrl, Signal
-from PySide6.QtWebChannel import QWebChannel
-from PySide6.QtWebEngineCore import QWebEnginePage
-from PySide6.QtWebEngineWidgets import QWebEngineView
-from PySide6.QtWidgets import QVBoxLayout, QWidget
-
-from otko.views.canvas3d.model_renderer import RendererMode
-from otko.views.canvas3d.selection import SelectionState
-from otko.views.canvas3d.style import RenderStyle
-from otko.views.canvas_base import CanvasCapabilities
-from otko.views.canvas_plotly import html as _html
-from otko.views.canvas_plotly.bridge import _Bridge
-from otko.views.canvas_plotly.trace_builder import (
- PlotlyTraceBuilder,
- Scene,
- SceneOptions,
-)
-
-#: View preset directions (unit-ish vectors from the scene centre to the eye).
-_VIEW_DIRECTIONS = {
- "iso": (1.0, 1.0, 0.8),
- "xy": (0.0, 0.0, 1.0),
- "xz": (0.0, -1.0, 0.0),
- "yz": (1.0, 0.0, 0.0),
-}
-
-
-class _LoggingPage(QWebEnginePage):
- """A web page that forwards the JS console to the app's console dock.
-
- A plotly failure inside the page is otherwise invisible from Python (the
- ``react`` promise just rejects), which makes a frozen canvas very hard to
- diagnose.
- """
-
- def __init__(self, log: Callable[[str, str], None], parent: QObject | None = None) -> None:
- super().__init__(parent)
- self._log = log
-
- def javaScriptConsoleMessage(
- self,
- level: QWebEnginePage.JavaScriptConsoleMessageLevel,
- message: str,
- line_number: int,
- source_id: str,
- ) -> None:
- names = {
- QWebEnginePage.JavaScriptConsoleMessageLevel.ErrorMessageLevel: "error",
- QWebEnginePage.JavaScriptConsoleMessageLevel.WarningMessageLevel: "warning",
- }
- severity = names.get(level, "info")
- source = source_id.rsplit("/", 1)[-1] or "plotly"
- self._log(f"[web] {source}:{line_number} {message}", severity)
-
-
-class _CameraShim:
- """Mimics ``canvas.camera.parallel_projection`` as consumed elsewhere."""
-
- def __init__(self, canvas: PlotlyCanvas) -> None:
- self._canvas = canvas
-
- @property
- def parallel_projection(self) -> bool:
- return self._canvas._parallel
-
- @parallel_projection.setter
- def parallel_projection(self, value: bool) -> None:
- self._canvas.set_parallel_projection(bool(value))
-
-
-class _PlotlyRendererFacade:
- """Stand-in for ``ModelRenderer`` covering the calls made on ``_renderer``.
-
- ``RenderControls`` and ``DockManager`` reach into ``canvas._renderer`` for
- ``render`` / ``set_mode`` / ``_mode``; this keeps those code paths
- backend-agnostic.
- """
-
- def __init__(self, canvas: PlotlyCanvas) -> None:
- self._canvas = canvas
-
- @property
- def _mode(self) -> RendererMode:
- return self._canvas._mode
-
- @property
- def _project(self) -> Any:
- return self._canvas._project
-
- @property
- def _working_plane(self) -> tuple[str, float] | None:
- return self._canvas._working_plane
-
- def render(self, project: Any) -> None:
- self._canvas.set_project(project)
-
- def set_mode(self, mode: RendererMode, deformation: Any = None) -> None:
- self._canvas.set_mode(mode, deformation)
-
- def set_working_plane(self, plane: tuple[str, float] | None) -> None:
- if plane is None:
- self._canvas.set_working_plane(None, 0.0)
- else:
- self._canvas.set_working_plane(plane[0], plane[1])
-
- def set_show_section_extrusions(self, on: bool) -> None:
- self._canvas.set_show_section_extrusions(on)
-
- def set_show_local_axes(self, on: bool) -> None:
- self._canvas.set_show_local_axes(on)
-
- def set_display_options(self, *, show_node_labels: bool, show_element_labels: bool) -> None:
- self._canvas.set_display_options(
- show_node_labels=show_node_labels,
- show_element_labels=show_element_labels,
- )
-
-
-class PlotlyCanvas(QWidget):
- """The central 3D viewport, rendered by plotly.js in a web view."""
-
- nodePicked = Signal(int)
- elementPicked = Signal(int)
- emptyClicked = Signal(float, float, float)
-
- #: Force diagrams and off-screen video capture are not implemented on
- #: this backend yet (both are PyVista-specific today).
- capabilities = CanvasCapabilities(diagrams=False, animation_export=False)
-
- def __init__(
- self,
- parent: QWidget | None = None,
- style: RenderStyle | None = None,
- selection: SelectionState | None = None,
- log: Callable[[str, str], None] | None = None,
- ) -> None:
- super().__init__(parent)
- self._log = log
- self._style = style or RenderStyle()
- self.selection = selection or SelectionState(self)
- self._builder = PlotlyTraceBuilder(self._style)
- self._project: Any = None
- self._scene = Scene(data=[], layout={})
- self._options = SceneOptions()
- self._mode = RendererMode.MODEL
- self._parallel = False
- self._view_preset = "iso"
- self._snap_enabled = False
- self._default_selection_enabled = True
- self._working_plane: tuple[str, float] | None = None
- self._camera = _CameraShim(self)
- self._framing_dirty = True
- self._renderer = _PlotlyRendererFacade(self)
- self._ready = False
-
- self._build_ui()
- self.selection.selectionChanged.connect(self._on_selection_changed)
-
- # ── construction ─────────────────────────────────────────────────
- def _build_ui(self) -> None:
- layout = QVBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- self._web = QWebEngineView(self)
- if self._log is not None:
- self._web.setPage(_LoggingPage(self._log, self._web))
- self._channel = QWebChannel(self._web.page())
- self._bridge = _Bridge(self)
- self._channel.registerObject("otkoBridge", self._bridge)
- self._web.page().setWebChannel(self._channel)
- self._web.loadFinished.connect(self._on_loaded)
- self._bridge.picked.connect(self._on_picked)
- self._bridge.snapClicked.connect(self._on_snap_clicked)
- layout.addWidget(self._web)
- self._web.load(QUrl.fromLocalFile(_html.runtime_url()))
-
- def _on_loaded(self, ok: bool) -> None:
- if not ok:
- return
- self._ready = True
- self._push_scene()
- self._eval(f"window.otkoSetSnapEnabled({_js_bool(self._snap_enabled)})")
-
- # ── public API (mirrors ModelCanvas) ─────────────────────────────
- def show_project(self, project: Any) -> None:
- """Render (or clear) a project; frame the camera when nodes exist."""
- self.set_project(project)
- if project is not None and project.nodes:
- self._view_preset = "iso"
- self._framing_dirty = True
- self.render()
-
- def clear_model(self) -> None:
- """Remove the model but keep the view/selection machinery."""
- self.selection.clear()
- self.set_project(None)
- self.render()
-
- def render(self, *args: Any, **kwargs: Any) -> None:
- """Rebuild the figure and hand it to plotly.js.
-
- ``QWidget.render`` is overloaded for painting into a target; those
- calls are forwarded untouched so the widget stays well-behaved, and
- only the no-argument canvas idiom (shared with ``ModelCanvas``)
- triggers a figure push.
- """
- if args or kwargs:
- super().render(*args, **kwargs)
- return
- self._push_scene()
-
- def set_project(self, project: Any) -> None:
- """Bind a project without rendering (used by the ``_renderer`` facade)."""
- self._project = project
-
- def set_mode(self, mode: RendererMode, deformation: Any = None) -> None:
- """Set MODEL / DEFORMED / MODAL plus the displacement source to apply."""
- self._mode = mode
- self._options = replace(self._options, deformation=deformation)
-
- def set_parallel_projection(self, on: bool) -> None:
- self._parallel = bool(on)
- self._framing_dirty = True
-
- def reset_camera(self) -> None:
- self._view_preset = "iso"
- self._framing_dirty = True
- self.render()
-
- def view_isometric(self) -> None:
- self._view_preset = "iso"
- self._framing_dirty = True
- self.render()
-
- def view_xy(self) -> None:
- """Top view: the eye sits on +Z looking down."""
- self._view_preset = "xy"
- self._framing_dirty = True
- self.render()
-
- def view_xz(self) -> None:
- """Front view: the eye sits on -Y."""
- self._view_preset = "xz"
- self._framing_dirty = True
- self.render()
-
- def view_yz(self) -> None:
- """Right view: the eye sits on +X."""
- self._view_preset = "yz"
- self._framing_dirty = True
- self.render()
-
- # ── working plane ────────────────────────────────────────────────
- def set_working_plane(self, plane: str | None, offset: float) -> None:
- """Filter the grid overlay to the active plan / elevation level."""
- if plane is None:
- self._working_plane = None
- else:
- if plane not in ("XY", "XZ", "YZ"):
- raise ValueError(f"Unsupported working plane: {plane!r}")
- self._working_plane = (plane, float(offset))
- self._options = replace(self._options, working_plane=self._working_plane)
- self.render()
-
- def clear_working_plane(self) -> None:
- self.set_working_plane(None, 0.0)
-
- def working_plane_type(self) -> str | None:
- return self._working_plane[0] if self._working_plane is not None else None
-
- def working_plane_offset(self) -> float | None:
- return self._working_plane[1] if self._working_plane is not None else None
-
- # ── display toggles ──────────────────────────────────────────────
- def set_snap_preview_enabled(self, enabled: bool) -> None:
- """Toggle the hover snap-target preview (draw tools turn it on)."""
- self._snap_enabled = bool(enabled)
- self._eval(f"window.otkoSetSnapEnabled({_js_bool(self._snap_enabled)})")
-
- def set_show_section_extrusions(self, enabled: bool) -> None:
- self._options = replace(self._options, show_extrusions=bool(enabled))
- self.render()
-
- def set_show_local_axes(self, enabled: bool) -> None:
- self._options = replace(self._options, show_local_axes=bool(enabled))
- self.render()
-
- def set_style(self, style: RenderStyle) -> None:
- """Swap the visual style and repaint the figure.
-
- Deliberately *not* a re-frame: colour/opacity are trace props and the
- background/outline ride the layout, so a style change (including the
- live preview while dragging a colour) updates without snapping the
- camera the user has moved.
- """
- self._style = style
- self._builder.set_style(style)
- self.render()
-
- def set_display_options(self, *, show_node_labels: bool, show_element_labels: bool) -> None:
- self._options = replace(
- self._options,
- show_node_labels=bool(show_node_labels),
- show_element_labels=bool(show_element_labels),
- )
- self.render()
-
- def set_default_selection_enabled(self, enabled: bool) -> None:
- """When False, picks fire signals but do not touch :attr:`selection`."""
- self._default_selection_enabled = bool(enabled)
-
- # ── internals ───────────────────────────────────────────────────
- def _push_scene(self) -> None:
- """Send the figure to plotly.js.
-
- ``Plotly.react`` resets any scene attribute the incoming layout omits,
- which used to snap the camera *and* the padded ranges back on every
- selection change. So a non-framing push marks itself
- ``preserveView`` and the JS side carries the live camera/ranges
- forward; only an explicit re-frame (new project, view preset,
- ``reset_camera``) sends the computed framing. Style changes are
- non-framing: they re-colour in place.
- """
- self._scene = self._builder.build(self._project, self._options)
- if not self._ready:
- # Nothing to push yet; crucially this must come *before* the
- # framing flag is consumed, or the framing scheduled before
- # loadFinished would be dropped on the floor.
- return
- layout = dict(self._scene.layout)
- re_framed = self._framing_dirty
- if re_framed:
- scene_layout = dict(layout["scene"])
- scene_layout["camera"] = self._camera_dict(self._scene)
- for axis, override in self._scene.axis_overrides().items():
- scene_layout[axis] = {**scene_layout.get(axis, {}), **override}
- layout["scene"] = scene_layout
- self._framing_dirty = False
- payload = json.dumps({"data": self._scene.data, "layout": layout})
- self._eval(f"window.otkoUpdate({json.dumps(payload)}, {_js_bool(not re_framed)})")
-
- def _camera_dict(self, scene: Scene) -> dict[str, Any]:
- cx, cy, cz = scene.center
- distance = max(scene.diagonal, 1e-6) * 1.6
- direction = _VIEW_DIRECTIONS.get(self._view_preset, _VIEW_DIRECTIONS["iso"])
- norm = math.sqrt(sum(component * component for component in direction)) or 1.0
- eye = (
- cx + direction[0] / norm * distance,
- cy + direction[1] / norm * distance,
- cz + direction[2] / norm * distance,
- )
- # Looking straight down the Z axis needs a non-degenerate up vector.
- up = (0.0, 1.0, 0.0) if self._view_preset == "xy" else (0.0, 0.0, 1.0)
- return {
- "eye": {"x": eye[0], "y": eye[1], "z": eye[2]},
- "center": {"x": cx, "y": cy, "z": cz},
- "up": {"x": up[0], "y": up[1], "z": up[2]},
- "projection": {"type": "orthographic" if self._parallel else "perspective"},
- }
-
- def _eval(self, js: str) -> None:
- if self._ready:
- self._web.page().runJavaScript(js)
-
- def _on_selection_changed(self, nodes: frozenset[int], elements: frozenset[int]) -> None:
- self._options = replace(
- self._options,
- selection_nodes=frozenset(nodes),
- selection_elements=frozenset(elements),
- )
- self.render()
-
- def _on_picked(self, kind: str, entity_id: int, additive: bool) -> None:
- if kind == "node":
- if self._default_selection_enabled:
- if additive:
- self.selection.toggle_node(entity_id)
- else:
- self.selection.select_node(entity_id)
- self.nodePicked.emit(entity_id)
- elif kind == "element":
- if self._default_selection_enabled:
- if additive:
- self.selection.toggle_element(entity_id)
- else:
- self.selection.select_element(entity_id)
- self.elementPicked.emit(entity_id)
-
- def _on_snap_clicked(self, x: float, y: float, z: float) -> None:
- self.emptyClicked.emit(float(x), float(y), float(z))
-
-
-def _js_bool(value: bool) -> str:
- return "true" if value else "false"
diff --git a/src/otko/views/canvas_plotly/trace_builder.py b/src/otko/views/canvas_plotly/trace_builder.py
deleted file mode 100644
index fa5fc94..0000000
--- a/src/otko/views/canvas_plotly/trace_builder.py
+++ /dev/null
@@ -1,1028 +0,0 @@
-"""Project → Plotly figure dictionaries.
-
-Pure data transformation: no Qt, no pyvista, no plotly import. The
-:class:`PlotlyCanvas` serialises the returned :class:`Scene` to JSON and
-hands it to plotly.js; the unit tests exercise it headless.
-
-Geometry conventions mirror :mod:`otko.views.canvas3d.model_renderer` so the
-two backends draw the same model, but the output medium differs:
-
-- Nodes / supports / labels are ``scatter3d`` markers, whose size is in
- *pixels* (plotly.js does not scale markers with world units).
-- Frames are ``scatter3d`` line traces. Plotly cannot colour individual
- segments of one line trace, so selection is expressed as two traces
- (normal + selected) whose segment lists are regrouped on every update.
-- Loads and local axes are ``cone`` traces (plotly.js has no 3D arrow glyph).
-- Section extrusions are ``mesh3d`` box sweeps from
- :func:`otko.services.section_bbox.bbox_for_section` — the same bbox
- fallback the PyVista backend uses when a shape hint is not an exact fit.
-
-Picking rides on ``customdata`` + trace ``meta``: every pickable trace is
-tagged ``meta={"kind": "node" | "element" | "snap"}`` and the JS side reads
-``point.data.meta.kind`` to decide what the click meant.
-"""
-
-from __future__ import annotations
-
-from dataclasses import dataclass, field
-from typing import Any
-
-import numpy as np
-
-from otko.core import (
- BeamWithHingesElement,
- CorotTrussElement,
- DispBeamColumn,
- ElasticBeamColumn,
- ForceBeamColumn,
- NodalLoad,
- PlainLoadPattern,
- Project,
- QuadElement,
- TrussElement,
- UniformElementLoad,
- ZeroLengthElement,
- ZeroLengthSectionElement,
-)
-from otko.views.canvas3d.style import (
- RenderStyle,
- element_family_index,
- family_palette,
-)
-
-_FRAME_CLASSES = (
- ElasticBeamColumn,
- DispBeamColumn,
- ForceBeamColumn,
- BeamWithHingesElement,
- TrussElement,
- CorotTrussElement,
- ZeroLengthElement,
-)
-
-#: Triangle indices for the 8-corner box sweep built by :meth:`_SceneBuilder._box_corners`.
-_BOX_TRIS = (
- (0, 1, 2),
- (0, 2, 3),
- (4, 5, 6),
- (4, 6, 7),
- (0, 1, 5),
- (0, 5, 4),
- (3, 2, 6),
- (3, 6, 7),
- (0, 3, 7),
- (0, 7, 4),
- (1, 2, 6),
- (1, 6, 5),
-)
-
-#: Support kind → plotly 3D marker symbol.
-_SUPPORT_SYMBOLS = {
- "fix": "square",
- "pin": "triangle-up",
- "roller": "circle",
- "custom": "diamond",
-}
-
-_NODE_MARKER_SIZE = 7.0
-_SUPPORT_MARKER_SIZE = 11.0
-_SNAP_MARKER_SIZE = 8.0
-
-
-@dataclass(frozen=True)
-class SceneOptions:
- """Everything the builder needs beyond the project itself."""
-
- selection_nodes: frozenset[int] = frozenset()
- selection_elements: frozenset[int] = frozenset()
- #: Anything exposing ``shifted(points, node_ids)`` (e.g. ``DeformationSource``).
- deformation: Any = None
- working_plane: tuple[str, float] | None = None
- show_node_labels: bool = False
- show_element_labels: bool = False
- show_extrusions: bool = False
- show_local_axes: bool = False
-
-
-@dataclass
-class Scene:
- """A transport-ready plotly figure plus framing metadata."""
-
- data: list[dict[str, Any]]
- layout: dict[str, Any]
- center: tuple[float, float, float] = (0.0, 0.0, 0.0)
- diagonal: float = 1.0
- #: Trace index (in ``data``) of the empty hover-snap marker, or -1.
- hover_trace: int = -1
- #: Padded ``(min, max)`` per axis, used to frame the camera deterministically.
- axis_bounds: dict[str, tuple[float, float]] = field(default_factory=dict)
-
- def to_payload(self) -> dict[str, Any]:
- """Figure dict without the camera — camera is owned by the widget."""
- return {"data": self.data, "layout": self.layout}
-
- def axis_overrides(self) -> dict[str, Any]:
- """Nested ``scene`` range pins for the framed data window.
-
- Only pushed together with the camera (view presets / new project), so
- the user's own zoom and pan survive ordinary data updates. The padding
- comes from opstool's scene recipe (``pad_ratio`` 0.15).
- """
- return {
- f"{name}axis": {"range": [low, high], "autorange": False}
- for name, (low, high) in self.axis_bounds.items()
- }
-
-
-@dataclass
-class _Mesh:
- """Accumulates a triangle soup for a single ``mesh3d`` trace."""
-
- x: list[float] = field(default_factory=list)
- y: list[float] = field(default_factory=list)
- z: list[float] = field(default_factory=list)
- i: list[int] = field(default_factory=list)
- j: list[int] = field(default_factory=list)
- k: list[int] = field(default_factory=list)
-
- def add_box(self, corners: np.ndarray) -> None:
- base = len(self.x)
- for cx, cy, cz in corners:
- self.x.append(float(cx))
- self.y.append(float(cy))
- self.z.append(float(cz))
- for a, b, c in _BOX_TRIS:
- self.i.append(base + a)
- self.j.append(base + b)
- self.k.append(base + c)
-
- @property
- def is_empty(self) -> bool:
- return not self.x
-
- def as_trace(self, *, color: str, opacity: float, name: str) -> dict[str, Any]:
- return {
- "type": "mesh3d",
- "x": self.x,
- "y": self.y,
- "z": self.z,
- "i": self.i,
- "j": self.j,
- "k": self.k,
- "color": color,
- "opacity": opacity,
- "flatshading": True,
- "hoverinfo": "skip",
- "name": name,
- "showscale": False,
- }
-
-
-# ── small geometry helpers ────────────────────────────────────────────────
-def _diag_of_points(pts: np.ndarray | None) -> float:
- if pts is None or len(pts) == 0:
- return 1.0
- mn, mx = pts.min(axis=0), pts.max(axis=0)
- d = float(np.linalg.norm(mx - mn))
- return d if d > 0 else 1.0
-
-
-#: Padding fraction added around the model when framing the view.
-#: From opstool's scene recipe (``pad_ratio=0.15`` for model views).
-_PAD_RATIO = 0.15
-
-
-def _padded_axis_bounds(pts: np.ndarray | None) -> dict[str, tuple[float, float]]:
- """Padded ``(min, max)`` per axis; degenerate axes get unit slack."""
- if pts is None or len(pts) == 0:
- return {name: (-1.0, 1.0) for name in ("x", "y", "z")}
- lower = np.asarray(pts, dtype=float).min(axis=0)
- upper = np.asarray(pts, dtype=float).max(axis=0)
- pad = (upper - lower) * _PAD_RATIO
- bounds: dict[str, tuple[float, float]] = {}
- for index, name in enumerate(("x", "y", "z")):
- low = float(lower[index] - pad[index])
- high = float(upper[index] + pad[index])
- if high - low < 1e-9: # planar / single-point model
- low, high = low - 1.0, high + 1.0
- bounds[name] = (low, high)
- return bounds
-
-
-def _diag_of_bounds(bounds: dict[str, tuple[float, float]]) -> float:
- """Padded bounding-box diagonal — the framing distance yardstick."""
- diagonal = float(np.linalg.norm([high - low for low, high in bounds.values()]))
- return diagonal if diagonal > 0 else 1.0
-
-
-def _frame_basis(el: Any, x_local: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
- """Local (y, z) basis — mirrors ``ModelRenderer._frame_basis``."""
- x = x_local / float(np.linalg.norm(x_local))
- vecxz = getattr(el, "vecxz", None)
- if vecxz is not None:
- try:
- v = np.asarray(vecxz, dtype=float)
- z_local = v - float(np.dot(v, x)) * x
- n = float(np.linalg.norm(z_local))
- if n > 1e-9:
- z_local /= n
- y_local = np.cross(z_local, x)
- m = float(np.linalg.norm(y_local))
- if m > 1e-9:
- return y_local / m, z_local
- except (TypeError, ValueError):
- pass
- z_global = np.array([0.0, 0.0, 1.0])
- y_local = np.cross(z_global, x)
- if float(np.linalg.norm(y_local)) < 1e-6:
- y_local = np.cross(np.array([1.0, 0.0, 0.0]), x)
- y_local /= float(np.linalg.norm(y_local))
- z_local = np.cross(x, y_local)
- return y_local, z_local
-
-
-def _dof_indices(ndf: int) -> tuple[int, ...]:
- if ndf == 6:
- return (0, 1, 2, 3, 4, 5)
- if ndf == 3:
- return (0, 1, 5)
- if ndf == 2:
- return (0, 1)
- return tuple(range(ndf))
-
-
-def _classify_support(restraint: tuple[bool, ...], dof_idx: tuple[int, ...]) -> str:
- flags = [restraint[i] for i in dof_idx]
- if all(flags):
- return "fix"
- trans_flags = [flags[k] for k, idx in enumerate(dof_idx) if idx < 3]
- rot_flags = [flags[k] for k, idx in enumerate(dof_idx) if idx >= 3]
- if trans_flags and all(trans_flags) and not any(rot_flags):
- return "pin"
- if sum(flags) == 1:
- return "roller"
- return "custom"
-
-
-def _line_trace(
- segments: list[tuple[tuple[float, float, float], tuple[float, float, float]]],
- *,
- color: str,
- width: float,
- name: str,
- opacity: float = 1.0,
- meta: dict[str, Any] | None = None,
- customdata: list[Any] | None = None,
-) -> dict[str, Any]:
- """One ``scatter3d`` line trace from None-separated segment endpoints."""
- x: list[float | None] = []
- y: list[float | None] = []
- z: list[float | None] = []
- for a, b in segments:
- x.extend([float(a[0]), float(b[0]), None])
- y.extend([float(a[1]), float(b[1]), None])
- z.extend([float(a[2]), float(b[2]), None])
- trace: dict[str, Any] = {
- "type": "scatter3d",
- "mode": "lines",
- "x": x,
- "y": y,
- "z": z,
- "line": {"color": color, "width": width},
- "opacity": opacity,
- "hoverinfo": "skip",
- "name": name,
- "showlegend": False,
- }
- if meta is not None:
- trace["meta"] = meta
- if customdata is not None:
- trace["customdata"] = customdata
- return trace
-
-
-class PlotlyTraceBuilder:
- """Builds the full figure for a project."""
-
- def __init__(self, style: RenderStyle | None = None) -> None:
- self._style = style or RenderStyle()
-
- def set_style(self, style: RenderStyle) -> None:
- """Swap the visual style; the next :meth:`build` picks it up."""
- self._style = style
-
- # ── public ───────────────────────────────────────────────────────
- def build(self, project: Project | None, options: SceneOptions | None = None) -> Scene:
- opts = options or SceneOptions()
- if project is None:
- return Scene(data=[], layout=self._layout())
-
- nodes = list(project.nodes)
- node_ids = [n.id for n in nodes]
- points = np.array([n.coords for n in nodes], dtype=float) if nodes else np.empty((0, 3))
- if opts.deformation is not None and len(points):
- points = np.asarray(opts.deformation.shifted(points, node_ids), dtype=float)
- node_row = {nid: i for i, nid in enumerate(node_ids)}
-
- data: list[dict[str, Any]] = []
- grid_pts = self._build_grid(project, data, opts)
- self._build_extrusions(project, data, opts)
- self._build_local_axes(project, data, opts)
- self._build_loads(project, data, opts)
- self._build_supports(project, data, opts)
- self._build_frames(project, data, opts, points, node_row)
- self._build_nodes(data, opts, points, node_ids)
- self._build_labels(project, data, opts, points, node_row)
- hover_trace = self._build_hover_marker(data)
-
- candidates = [points] if len(points) else []
- if grid_pts is not None:
- candidates.append(grid_pts)
- all_pts = np.vstack(candidates) if candidates else np.empty((0, 3))
- center = tuple(np.mean(all_pts, axis=0)) if len(all_pts) else (0.0, 0.0, 0.0)
- bounds = _padded_axis_bounds(all_pts)
- return Scene(
- data=data,
- layout=self._layout(),
- center=(float(center[0]), float(center[1]), float(center[2])),
- diagonal=_diag_of_bounds(bounds),
- hover_trace=hover_trace,
- axis_bounds=bounds,
- )
-
- # ── layout ───────────────────────────────────────────────────────
- def _layout(self) -> dict[str, Any]:
- style = self._style
- outline = style.show_axis_outline
-
- def axis(color: str, title: str) -> dict[str, Any]:
- # Coloured axis lines + titles stay visible as the orientation cue
- # (plotly has no corner triad); the grid/ticks are the optional
- # "outline" from opstool's scene recipe.
- return {
- "title": {"text": title, "font": {"color": color, "size": 12}},
- "showgrid": outline,
- "showbackground": False,
- "zeroline": outline,
- "showticklabels": outline,
- "showspikes": False,
- "visible": True,
- "linecolor": color,
- "linewidth": 2,
- }
-
- return {
- "paper_bgcolor": style.background_bottom,
- "plot_bgcolor": style.background_bottom,
- "showlegend": False,
- "margin": {"l": 0, "r": 0, "t": 0, "b": 0},
- "uirevision": "otko",
- "scene": {
- "bgcolor": style.background_bottom,
- "aspectmode": "data",
- "dragmode": "orbit",
- "xaxis": axis(style.fix_color, "X"),
- "yaxis": axis(style.load_color, "Y"),
- "zaxis": axis(style.truss_color, "Z"),
- },
- }
-
- # ── grid ─────────────────────────────────────────────────────────
- def _build_grid(
- self, project: Project, data: list[dict[str, Any]], opts: SceneOptions
- ) -> np.ndarray | None:
- coord_systems = getattr(project, "coord_systems", None) or []
- palette = [
- ((0.08, 0.08, 0.08), (0.85, 0.55, 0.00)),
- ((0.20, 0.35, 0.55), (0.85, 0.55, 0.00)),
- ((0.20, 0.55, 0.30), (0.85, 0.55, 0.00)),
- ((0.55, 0.20, 0.40), (0.85, 0.55, 0.00)),
- ((0.35, 0.20, 0.55), (0.85, 0.55, 0.00)),
- ]
- all_dots: list[np.ndarray] = []
-
- for idx, cs in enumerate(coord_systems):
- grid = cs.grid
- if not grid.visible or getattr(grid, "hide_all", False):
- continue
- xs = list(grid.x_lines)
- ys = list(grid.y_lines)
- zs = list(grid.z_lines)
- if not (xs or ys or zs):
- continue
- palette_idx = 0 if cs.is_global() else (idx % (len(palette) - 1)) + 1
- grid_color, dot_color = palette[palette_idx]
- grid_hex = _rgb_to_hex(grid_color)
- dot_hex = _rgb_to_hex(dot_color)
-
- xmin, xmax = (min(xs), max(xs)) if xs else (-1.0, 1.0)
- ymin, ymax = (min(ys), max(ys)) if ys else (-1.0, 1.0)
- if xmin == xmax:
- xmin, xmax = xmin - 1.0, xmax + 1.0
- if ymin == ymax:
- ymin, ymax = ymin - 1.0, ymax + 1.0
-
- plane_axis: int | None = None
- plane_offset: float | None = None
- if opts.working_plane is not None:
- name, off = opts.working_plane
- axis_idx = {"XY": 2, "XZ": 1, "YZ": 0}[name]
- plane_axis = axis_idx
- plane_offset = off - cs.coord.origin[axis_idx]
-
- def on_plane(
- local: tuple[float, float, float],
- _axis: int | None = plane_axis,
- _offset: float | None = plane_offset,
- ) -> bool:
- if _axis is None or _offset is None:
- return True
- return abs(local[_axis] - _offset) < 1e-6
-
- active: list[tuple[tuple[float, float, float], tuple[float, float, float]]] = []
- dim: list[tuple[tuple[float, float, float], tuple[float, float, float]]] = []
-
- def add_seg(
- p1: tuple[float, float, float],
- p2: tuple[float, float, float],
- _active: list = active,
- _dim: list = dim,
- _on_plane: Any = on_plane,
- _cs: Any = cs,
- ) -> None:
- on = _on_plane(p1) and _on_plane(p2)
- bucket = _active if on else _dim
- bucket.append((_cs.coord.local_to_world(p1), _cs.coord.local_to_world(p2)))
-
- z_planes = zs if zs else [0.0]
- for z in z_planes:
- for x in xs:
- add_seg((x, ymin, z), (x, ymax, z))
- for y in ys:
- add_seg((xmin, y, z), (xmax, y, z))
- if opts.working_plane is None and zs and xs and ys:
- for x in xs:
- for y in ys:
- add_seg((x, y, zs[0]), (x, y, zs[-1]))
-
- if dim:
- data.append(_line_trace(dim, color=grid_hex, width=1, name="grid", opacity=0.18))
- if active:
- data.append(_line_trace(active, color=grid_hex, width=2, name="grid-active"))
-
- active_dots: list[tuple[float, float, float]] = []
- dim_dots: list[tuple[float, float, float]] = []
- for z in z_planes:
- for x in xs or [0.0]:
- for y in ys or [0.0]:
- pt = cs.coord.local_to_world((x, y, z))
- (active_dots if on_plane((x, y, z)) else dim_dots).append(pt)
- if dim_dots:
- data.append(self._dot_trace(dim_dots, color="#999999", opacity=0.35))
- if active_dots:
- data.append(
- self._dot_trace(
- active_dots,
- color=dot_hex,
- opacity=1.0,
- meta={"kind": "snap"},
- )
- )
- all_dots.extend(np.asarray(active_dots, dtype=float))
-
- return np.vstack(all_dots) if all_dots else None
-
- @staticmethod
- def _dot_trace(
- points: list[tuple[float, float, float]],
- *,
- color: str,
- opacity: float,
- meta: dict[str, Any] | None = None,
- ) -> dict[str, Any]:
- customdata = (
- [[float(p[0]), float(p[1]), float(p[2])] for p in points] if meta is not None else None
- )
- trace: dict[str, Any] = {
- "type": "scatter3d",
- "mode": "markers",
- "x": [float(p[0]) for p in points],
- "y": [float(p[1]) for p in points],
- "z": [float(p[2]) for p in points],
- "marker": {"color": color, "size": _SNAP_MARKER_SIZE, "line": {"width": 0}},
- "opacity": opacity,
- "hoverinfo": "skip",
- "name": "snap",
- "showlegend": False,
- }
- if meta is not None:
- trace["meta"] = meta
- trace["customdata"] = customdata
- return trace
-
- # ── model entities ───────────────────────────────────────────────
- def _build_nodes(
- self,
- data: list[dict[str, Any]],
- opts: SceneOptions,
- points: np.ndarray,
- node_ids: list[int],
- ) -> None:
- if not len(points):
- return
- colors = [
- self._style.node_selected_color
- if nid in opts.selection_nodes
- else self._style.node_color
- for nid in node_ids
- ]
- data.append(
- {
- "type": "scatter3d",
- "mode": "markers",
- "x": [float(p[0]) for p in points],
- "y": [float(p[1]) for p in points],
- "z": [float(p[2]) for p in points],
- "marker": {
- "color": colors,
- "size": _NODE_MARKER_SIZE,
- "line": {"color": "#4d4d4d", "width": 1},
- },
- "customdata": list(node_ids),
- "meta": {"kind": "node"},
- # Engineering-notation hover (opstool's trace recipe) so a
- # hover identifies the entity instead of showing the raw id.
- "hovertemplate": "Node #%{customdata}",
- "name": "nodes",
- "showlegend": False,
- }
- )
-
- def _build_frames(
- self,
- project: Project,
- data: list[dict[str, Any]],
- opts: SceneOptions,
- points: np.ndarray,
- node_row: dict[int, int],
- ) -> None:
- """One line trace whose per-point ``line.color`` carries the palette.
-
- plotly accepts an *array* for ``Scatter3d.line.color`` mapped through
- a ``colorscale``, so a single trace can colour every element
- individually — by family and by selection — instead of one trace per
- colour. That also leaves room to colour by response value later.
- Each element's two endpoints (and its ``None`` separator) share one
- colour index, so each drawn segment is a solid colour.
- """
- if not len(points):
- return
- scale_colors = family_palette(self._style)
- selected_index = len(scale_colors) - 1
-
- x: list[float | None] = []
- y: list[float | None] = []
- z: list[float | None] = []
- color_index: list[float] = []
- customdata: list[Any] = []
- for el in project.elements:
- if not isinstance(el, _FRAME_CLASSES):
- continue
- i = node_row.get(el.nodes[0])
- j = node_row.get(el.nodes[1])
- if i is None or j is None:
- continue
- index = float(
- selected_index if el.id in opts.selection_elements else element_family_index(el)
- )
- x.extend([float(points[i][0]), float(points[j][0]), None])
- y.extend([float(points[i][1]), float(points[j][1]), None])
- z.extend([float(points[i][2]), float(points[j][2]), None])
- color_index.extend([index, index, index])
- customdata.extend([el.id, el.id, None])
- if not x:
- return
-
- count = len(scale_colors)
- data.append(
- {
- "type": "scatter3d",
- "mode": "lines",
- "x": x,
- "y": y,
- "z": z,
- "line": {
- "color": color_index,
- "colorscale": [
- (index / (count - 1), color) for index, color in enumerate(scale_colors)
- ],
- "cmin": 0,
- "cmax": count - 1,
- "width": 4,
- },
- "customdata": customdata,
- "meta": {"kind": "element"},
- "hovertemplate": "Element #%{customdata}",
- "name": "elements",
- "showlegend": False,
- }
- )
-
- def _build_supports(
- self, project: Project, data: list[dict[str, Any]], opts: SceneOptions
- ) -> None:
- if not project.nodes:
- return
- dof_idx = _dof_indices(project.ndf)
- groups: dict[str, list[Any]] = {}
- for node in project.nodes:
- if not any(node.restraint[i] for i in dof_idx):
- continue
- kind = _classify_support(node.restraint, dof_idx)
- groups.setdefault(kind, []).append(node)
- for kind, nodes in groups.items():
- data.append(
- {
- "type": "scatter3d",
- "mode": "markers",
- "x": [float(n.coords[0]) for n in nodes],
- "y": [float(n.coords[1]) for n in nodes],
- "z": [float(n.coords[2]) for n in nodes],
- "marker": {
- "color": self._style.support_color,
- "size": _SUPPORT_MARKER_SIZE,
- "symbol": _SUPPORT_SYMBOLS[kind],
- "line": {"color": "#7f3f00", "width": 1},
- },
- "hoverinfo": "skip",
- "name": f"support-{kind}",
- "showlegend": False,
- }
- )
-
- # ── loads / axes / extrusions ────────────────────────────────────
- def _build_loads(
- self, project: Project, data: list[dict[str, Any]], opts: SceneOptions
- ) -> None:
- if not project.load_patterns or not project.nodes:
- return
- node_by_id = {n.id: n for n in project.nodes}
- elem_by_id = {e.id: e for e in project.elements}
- pts = np.array([n.coords for n in project.nodes], dtype=float)
- scale = max(_diag_of_points(pts) * 0.05, 1e-6)
-
- nodal_x: list[float] = []
- nodal_y: list[float] = []
- nodal_z: list[float] = []
- nodal_u: list[float] = []
- nodal_v: list[float] = []
- nodal_w: list[float] = []
- dist_x: list[float] = []
- dist_y: list[float] = []
- dist_z: list[float] = []
- dist_u: list[float] = []
- dist_v: list[float] = []
- dist_w: list[float] = []
-
- for pattern in project.load_patterns:
- if not isinstance(pattern, PlainLoadPattern):
- continue
- for nload in pattern.nodal_loads:
- if not isinstance(nload, NodalLoad):
- continue
- node = node_by_id.get(nload.node_id)
- if node is None:
- continue
- f = np.asarray(nload.forces[:3], dtype=float)
- mag = float(np.linalg.norm(f))
- if mag < 1e-12:
- continue
- direction = f / mag
- tail = np.asarray(node.coords, dtype=float) - direction * scale
- nodal_x.append(float(tail[0]))
- nodal_y.append(float(tail[1]))
- nodal_z.append(float(tail[2]))
- nodal_u.append(float(direction[0]))
- nodal_v.append(float(direction[1]))
- nodal_w.append(float(direction[2]))
-
- for eload in pattern.element_loads:
- if not isinstance(eload, UniformElementLoad):
- continue
- elem = elem_by_id.get(eload.element_id)
- if elem is None:
- continue
- node_i = node_by_id.get(elem.nodes[0])
- node_j = node_by_id.get(elem.nodes[1])
- if node_i is None or node_j is None:
- continue
- pi = np.asarray(node_i.coords, dtype=float)
- pj = np.asarray(node_j.coords, dtype=float)
- axis = pj - pi
- length = float(np.linalg.norm(axis))
- if length < 1e-9:
- continue
- x_local = axis / length
- z_global = np.array([0.0, 0.0, 1.0])
- y_local = np.cross(z_global, x_local)
- if float(np.linalg.norm(y_local)) < 1e-6:
- y_local = np.cross(np.array([0.0, 1.0, 0.0]), x_local)
- y_local /= float(np.linalg.norm(y_local))
- z_local = np.cross(x_local, y_local)
- load_vec = eload.wx * x_local + eload.wy * y_local + eload.wz * z_local
- mag = float(np.linalg.norm(load_vec))
- if mag < 1e-12:
- continue
- direction = load_vec / mag
- n_arrows = 5
- for k in range(n_arrows):
- t = (k + 0.5) / n_arrows
- tail = pi + t * axis - direction * (0.4 * scale)
- dist_x.append(float(tail[0]))
- dist_y.append(float(tail[1]))
- dist_z.append(float(tail[2]))
- dist_u.append(float(direction[0]))
- dist_v.append(float(direction[1]))
- dist_w.append(float(direction[2]))
-
- if nodal_x:
- data.append(
- _cone_trace(
- nodal_x,
- nodal_y,
- nodal_z,
- nodal_u,
- nodal_v,
- nodal_w,
- color=self._style.nodal_load_color,
- name="nodal-loads",
- size=scale,
- )
- )
- if dist_x:
- data.append(
- _cone_trace(
- dist_x,
- dist_y,
- dist_z,
- dist_u,
- dist_v,
- dist_w,
- color=self._style.element_load_color,
- name="element-loads",
- size=0.6 * scale,
- )
- )
-
- def _build_local_axes(
- self, project: Project, data: list[dict[str, Any]], opts: SceneOptions
- ) -> None:
- if not opts.show_local_axes or not project.nodes:
- return
- node_by_id = {n.id: n for n in project.nodes}
- pts = np.array([n.coords for n in project.nodes], dtype=float)
- cap = max(_diag_of_points(pts) * 0.08, 1e-6)
- axes: dict[str, dict[str, list[float]]] = {
- "x": {"x": [], "y": [], "z": [], "u": [], "v": [], "w": []},
- "y": {"x": [], "y": [], "z": [], "u": [], "v": [], "w": []},
- "z": {"x": [], "y": [], "z": [], "u": [], "v": [], "w": []},
- }
- for el in project.elements:
- if isinstance(el, QuadElement | ZeroLengthElement | ZeroLengthSectionElement):
- continue
- if not isinstance(
- el,
- ElasticBeamColumn
- | DispBeamColumn
- | ForceBeamColumn
- | BeamWithHingesElement
- | TrussElement
- | CorotTrussElement,
- ):
- continue
- node_i = node_by_id.get(el.nodes[0])
- node_j = node_by_id.get(el.nodes[1])
- if node_i is None or node_j is None:
- continue
- pi = np.asarray(node_i.coords, dtype=float)
- pj = np.asarray(node_j.coords, dtype=float)
- axis = pj - pi
- length = float(np.linalg.norm(axis))
- if length < 1e-9:
- continue
- x_local = axis / length
- y_local, z_local = _frame_basis(el, x_local)
- mid = (pi + pj) / 2.0
- for key, direction in (("x", x_local), ("y", y_local), ("z", z_local)):
- bucket = axes[key]
- bucket["x"].append(float(mid[0]))
- bucket["y"].append(float(mid[1]))
- bucket["z"].append(float(mid[2]))
- bucket["u"].append(float(direction[0]))
- bucket["v"].append(float(direction[1]))
- bucket["w"].append(float(direction[2]))
- for key, color in (("x", "#ff0000"), ("y", "#00bf00"), ("z", "#3366ff")):
- bucket = axes[key]
- if bucket["x"]:
- data.append(
- _cone_trace(
- bucket["x"],
- bucket["y"],
- bucket["z"],
- bucket["u"],
- bucket["v"],
- bucket["w"],
- color=color,
- name=f"local-{key}",
- size=cap,
- )
- )
-
- def _build_extrusions(
- self, project: Project, data: list[dict[str, Any]], opts: SceneOptions
- ) -> None:
- if not opts.show_extrusions:
- return
- from otko.services.section_bbox import bbox_for_section
-
- node_by_id = {n.id: n for n in project.nodes}
- section_by_id = {s.id: s for s in project.sections}
- mesh = _Mesh()
- for el in project.elements:
- if not isinstance(el, _FRAME_CLASSES):
- continue
- if isinstance(el, TrussElement | CorotTrussElement | QuadElement | ZeroLengthElement):
- continue
- section_id = getattr(el, "section_id", None)
- if section_id is None:
- continue
- section = section_by_id.get(section_id)
- if section is None:
- continue
- dims = bbox_for_section(section, project)
- if dims is None:
- continue
- w_y, h_z = dims
- if w_y <= 0 or h_z <= 0:
- continue
- node_i = node_by_id.get(el.nodes[0])
- node_j = node_by_id.get(el.nodes[1])
- if node_i is None or node_j is None:
- continue
- pi = np.asarray(node_i.coords, dtype=float)
- pj = np.asarray(node_j.coords, dtype=float)
- axis = pj - pi
- length = float(np.linalg.norm(axis))
- if length < 1e-9:
- continue
- x_local = axis / length
- y_local, z_local = _frame_basis(el, x_local)
- mesh.add_box(self._box_corners(pi, x_local, y_local, z_local, length, w_y, h_z))
- if not mesh.is_empty:
- data.append(
- mesh.as_trace(
- color=self._style.extrusion_color,
- opacity=self._style.extrusion_opacity,
- name="extrusions",
- )
- )
-
- @staticmethod
- def _box_corners(
- pi: np.ndarray,
- x_local: np.ndarray,
- y_local: np.ndarray,
- z_local: np.ndarray,
- length: float,
- w_y: float,
- h_z: float,
- ) -> np.ndarray:
- hy, hz = w_y / 2.0, h_z / 2.0
- offsets = np.array(
- [
- [0.0, -hy, -hz],
- [length, -hy, -hz],
- [length, +hy, -hz],
- [0.0, +hy, -hz],
- [0.0, -hy, +hz],
- [length, -hy, +hz],
- [length, +hy, +hz],
- [0.0, +hy, +hz],
- ]
- )
- basis = np.column_stack([x_local, y_local, z_local])
- return pi + offsets @ basis.T
-
- # ── labels / hover marker ────────────────────────────────────────
- def _build_labels(
- self,
- project: Project,
- data: list[dict[str, Any]],
- opts: SceneOptions,
- points: np.ndarray,
- node_row: dict[int, int],
- ) -> None:
- if opts.show_node_labels and len(points):
- labels = [(n.name.strip() if n.name.strip() else f"N{n.id}") for n in project.nodes]
- data.append(
- _text_trace(
- points, labels, name="node-labels", font_size=self._style.label_font_size
- )
- )
- if opts.show_element_labels and len(points):
- centers: list[np.ndarray] = []
- labels: list[str] = []
- for el in project.elements:
- if len(el.nodes) != 2:
- continue
- i = node_row.get(el.nodes[0])
- j = node_row.get(el.nodes[1])
- if i is None or j is None:
- continue
- centers.append((points[i] + points[j]) / 2.0)
- labels.append(el.name.strip() if el.name.strip() else f"E{el.id}")
- if centers:
- data.append(
- _text_trace(
- np.asarray(centers),
- labels,
- name="element-labels",
- font_size=self._style.label_font_size,
- )
- )
-
- @staticmethod
- def _build_hover_marker(data: list[dict[str, Any]]) -> int:
- data.append(
- {
- "type": "scatter3d",
- "mode": "markers",
- "x": [],
- "y": [],
- "z": [],
- "marker": {
- "color": "#ffd900",
- "size": 13,
- "line": {"color": "#8a6d00", "width": 1},
- },
- "hoverinfo": "skip",
- "name": "snap-hover",
- "showlegend": False,
- "meta": {"kind": "hover"},
- }
- )
- return len(data) - 1
-
-
-def _cone_trace(
- x: list[float],
- y: list[float],
- z: list[float],
- u: list[float],
- v: list[float],
- w: list[float],
- *,
- color: str,
- name: str,
- size: float,
-) -> dict[str, Any]:
- return {
- "type": "cone",
- "x": x,
- "y": y,
- "z": z,
- "u": u,
- "v": v,
- "w": w,
- "anchor": "tail",
- "sizemode": "absolute",
- "sizeref": float(size),
- "colorscale": [[0, color], [1, color]],
- "showscale": False,
- "hoverinfo": "skip",
- "name": name,
- "showlegend": False,
- }
-
-
-def _text_trace(
- points: np.ndarray, labels: list[str], *, name: str, font_size: int
-) -> dict[str, Any]:
- return {
- "type": "scatter3d",
- "mode": "text",
- "x": [float(p[0]) for p in points],
- "y": [float(p[1]) for p in points],
- "z": [float(p[2]) for p in points],
- "text": labels,
- "textposition": "top center",
- "textfont": {"size": font_size, "color": "#111111"},
- "hoverinfo": "skip",
- "name": name,
- "showlegend": False,
- }
-
-
-def _rgb_to_hex(rgb: tuple[float, float, float]) -> str:
- r, g, b = (int(round(v * 255)) for v in rgb)
- return f"#{r:02x}{g:02x}{b:02x}"
diff --git a/src/otko/views/dialogs/__init__.py b/src/otko/views/dialogs/__init__.py
index e4dff02..73305a1 100644
--- a/src/otko/views/dialogs/__init__.py
+++ b/src/otko/views/dialogs/__init__.py
@@ -12,7 +12,6 @@ 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,
@@ -34,7 +33,6 @@ from otko.views.dialogs.move import MoveDialog
from otko.views.dialogs.path_time_series import PathTimeSeriesDialog
from otko.views.dialogs.pattern_loads import PatternLoadsDialog
from otko.views.dialogs.plain_pattern import PlainPatternDialog
-from otko.views.dialogs.plot_properties import PlotPropertiesDialog
from otko.views.dialogs.quick_grid_lines import QuickGridLinesDialog
from otko.views.dialogs.quick_guide import QuickGuideDialog
from otko.views.dialogs.replicate import ReplicateDialog
@@ -62,7 +60,6 @@ __all__ = [
"DisplayOptionsDialog",
"GridSystemDialog",
"LinearTimeSeriesDialog",
- "LoadCombinationManagerDialog",
"MaterialLibraryDialog",
"MaterialTesterDialog",
"MirrorDialog",
@@ -70,7 +67,6 @@ __all__ = [
"PathTimeSeriesDialog",
"PatternLoadsDialog",
"PlainPatternDialog",
- "PlotPropertiesDialog",
"QuickGridLinesDialog",
"QuickGuideDialog",
"ReplicateDialog",
diff --git a/src/otko/views/dialogs/add_node.py b/src/otko/views/dialogs/add_node.py
index 1c5ddc5..d58c577 100644
--- a/src/otko/views/dialogs/add_node.py
+++ b/src/otko/views/dialogs/add_node.py
@@ -40,15 +40,12 @@ 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
@@ -74,18 +71,16 @@ 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 9ebf0ec..38f5094 100644
--- a/src/otko/views/dialogs/assign_equal_dof.py
+++ b/src/otko/views/dialogs/assign_equal_dof.py
@@ -42,12 +42,10 @@ 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()
@@ -66,7 +64,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:
@@ -81,10 +79,7 @@ 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 7e5f4e3..bdf993d 100644
--- a/src/otko/views/dialogs/assign_hinge.py
+++ b/src/otko/views/dialogs/assign_hinge.py
@@ -64,7 +64,9 @@ 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."
@@ -158,20 +160,18 @@ 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,22 +185,21 @@ 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 534ba69..c77d2ad 100644
--- a/src/otko/views/dialogs/assign_load.py
+++ b/src/otko/views/dialogs/assign_load.py
@@ -8,6 +8,7 @@ from PySide6.QtWidgets import (
QDialogButtonBox,
QDoubleSpinBox,
QFormLayout,
+ QInputDialog,
QLabel,
QLineEdit,
QVBoxLayout,
@@ -79,7 +80,9 @@ 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 f87a710..5a8f59c 100644
--- a/src/otko/views/dialogs/assign_masses.py
+++ b/src/otko/views/dialogs/assign_masses.py
@@ -8,8 +8,6 @@ it maps cleanly onto our existing :class:`SetMassCommand`.
from __future__ import annotations
-import contextlib
-
from PySide6.QtWidgets import (
QCheckBox,
QDialog,
@@ -58,22 +56,16 @@ 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])
@@ -85,7 +77,8 @@ 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)
@@ -105,16 +98,14 @@ class AssignMassesDialog(QDialog):
self._my.setValue(self._mx.value())
self._mx.valueChanged.connect(self._my.setValue)
else:
- with contextlib.suppress(RuntimeError, TypeError):
+ try:
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 5d62f4a..6127e99 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 8b3a5bc..61f4872 100644
--- a/src/otko/views/dialogs/assign_support.py
+++ b/src/otko/views/dialogs/assign_support.py
@@ -8,6 +8,7 @@ model's actual ``ndf`` at translation time.
from __future__ import annotations
+from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QButtonGroup,
QCheckBox,
@@ -21,12 +22,13 @@ 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 b2c580c..c1d23f0 100644
--- a/src/otko/views/dialogs/assign_zls.py
+++ b/src/otko/views/dialogs/assign_zls.py
@@ -25,9 +25,8 @@ 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
@@ -36,14 +35,12 @@ 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()
@@ -56,7 +53,8 @@ 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 d608992..be1309c 100644
--- a/src/otko/views/dialogs/case_forms.py
+++ b/src/otko/views/dialogs/case_forms.py
@@ -21,6 +21,7 @@ from PySide6.QtWidgets import (
QListWidget,
QListWidgetItem,
QSpinBox,
+ QVBoxLayout,
QWidget,
)
@@ -36,14 +37,9 @@ 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)
@@ -231,14 +227,7 @@ 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"]
@@ -302,8 +291,7 @@ 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(),
@@ -334,9 +322,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)
@@ -344,8 +332,7 @@ 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(),
)
@@ -372,19 +359,13 @@ 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)
@@ -395,12 +376,10 @@ 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)
@@ -417,20 +396,17 @@ 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)
@@ -447,14 +423,12 @@ 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(),
@@ -488,18 +462,12 @@ 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)
@@ -507,12 +475,10 @@ 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)
@@ -545,8 +511,7 @@ 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),
@@ -587,12 +552,10 @@ 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)
@@ -605,8 +568,7 @@ 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 3ba5f29..d451d2b 100644
--- a/src/otko/views/dialogs/case_manager.py
+++ b/src/otko/views/dialogs/case_manager.py
@@ -4,7 +4,6 @@ from __future__ import annotations
from typing import TYPE_CHECKING
-from pydantic import ValidationError
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QDialog,
@@ -20,6 +19,7 @@ from PySide6.QtWidgets import (
QVBoxLayout,
QWidget,
)
+from pydantic import ValidationError
from otko.commands import (
AddAnalysisCasesCommand,
@@ -27,14 +27,10 @@ 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_for
+from otko.views.dialogs.case_forms import FORM_REGISTRY, form_for
if TYPE_CHECKING:
from otko.core import Project
@@ -60,23 +56,16 @@ 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)]
@@ -84,15 +73,13 @@ 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}.")
@@ -163,7 +150,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)
@@ -214,31 +201,22 @@ 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
deleted file mode 100644
index 882736d..0000000
--- a/src/otko/views/dialogs/combination_dialog.py
+++ /dev/null
@@ -1,258 +0,0 @@
-"""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 595464e..78459a1 100644
--- a/src/otko/views/dialogs/coord_grid_systems.py
+++ b/src/otko/views/dialogs/coord_grid_systems.py
@@ -61,7 +61,8 @@ 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
@@ -87,33 +88,28 @@ 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)
@@ -164,7 +160,8 @@ 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)
@@ -192,13 +189,9 @@ 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))
@@ -252,7 +245,9 @@ 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()
@@ -295,7 +290,8 @@ 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)
@@ -336,7 +332,9 @@ 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 ──────────────────────────────────────────────────
@@ -353,17 +351,14 @@ 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)
@@ -385,11 +380,8 @@ 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
@@ -400,11 +392,9 @@ 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)
@@ -424,8 +414,7 @@ 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 7440b04..5ff378a 100644
--- a/src/otko/views/dialogs/define_grid_data.py
+++ b/src/otko/views/dialogs/define_grid_data.py
@@ -18,8 +18,6 @@ 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 (
@@ -29,6 +27,7 @@ from PySide6.QtWidgets import (
QComboBox,
QDialog,
QDialogButtonBox,
+ QDoubleSpinBox,
QFormLayout,
QGroupBox,
QHBoxLayout,
@@ -53,6 +52,7 @@ from otko.core.geometry import (
GridSystem,
)
+
_LINE_TYPE_CHOICES = ["Primary", "Secondary"]
_BUBBLE_LOC_CHOICES = ["Start", "End"]
@@ -60,14 +60,7 @@ _BUBBLE_LOC_CHOICES = ["Start", "End"]
class _AxisGridTable(QWidget):
"""A single X/Y/Z spreadsheet — one row per GridLine."""
- COLUMNS: ClassVar[list[str]] = [
- "Grid ID",
- "Ordinate",
- "Line Type",
- "Visibility",
- "Bubble Loc",
- "Color",
- ]
+ COLUMNS = ["Grid ID", "Ordinate", "Line Type", "Visibility", "Bubble Loc", "Color"]
def __init__(self, axis: str, parent: QWidget | None = None) -> None:
super().__init__(parent)
@@ -134,9 +127,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
@@ -176,28 +169,23 @@ 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 ───────────────────────────────────────────────────
@@ -230,8 +218,7 @@ 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
@@ -263,13 +250,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")
@@ -291,7 +278,8 @@ 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
@@ -340,7 +328,9 @@ 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)
@@ -385,7 +375,8 @@ 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)
@@ -397,7 +388,8 @@ 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:
@@ -423,7 +415,6 @@ class DefineGridSystemDataDialog(QDialog):
from otko.views.dialogs.locate_origin import (
CoordSystemLocationOrientationDialog,
)
-
dlg = CoordSystemLocationOrientationDialog(
origin=self._origin,
rotation_deg=self._rotation_deg,
@@ -438,13 +429,11 @@ 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))
@@ -476,8 +465,7 @@ 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 306a678..19945d4 100644
--- a/src/otko/views/dialogs/distributed_load.py
+++ b/src/otko/views/dialogs/distributed_load.py
@@ -141,11 +141,9 @@ 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 c1ac05c..4f94f71 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,
- QLabel,
QLineEdit,
+ QLabel,
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,7 +104,9 @@ 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)
@@ -126,7 +128,6 @@ 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 ""
@@ -135,7 +136,6 @@ 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,7 +152,8 @@ 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 28a8205..5121c0e 100644
--- a/src/otko/views/dialogs/locate_origin.py
+++ b/src/otko/views/dialogs/locate_origin.py
@@ -13,6 +13,7 @@ from PySide6.QtWidgets import (
QDoubleSpinBox,
QFormLayout,
QGroupBox,
+ QHBoxLayout,
QLabel,
QVBoxLayout,
QWidget,
@@ -35,35 +36,28 @@ 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 e151f59..2c3a155 100644
--- a/src/otko/views/dialogs/material_forms.py
+++ b/src/otko/views/dialogs/material_forms.py
@@ -33,16 +33,10 @@ 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))
@@ -116,11 +110,8 @@ 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(),
)
@@ -136,34 +127,19 @@ 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(),
)
@@ -180,28 +156,19 @@ 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(),
)
@@ -220,36 +187,23 @@ 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()},
)
@@ -289,12 +243,9 @@ 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,
)
@@ -312,15 +263,12 @@ 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(),
)
@@ -338,17 +286,12 @@ 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(),
)
@@ -364,15 +307,12 @@ 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 371a507..93ad07a 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,6 +23,7 @@ from PySide6.QtWidgets import (
QVBoxLayout,
QWidget,
)
+from pydantic import ValidationError
from otko.commands import (
AddMaterialsCommand,
@@ -149,12 +150,7 @@ 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
@@ -183,10 +179,9 @@ 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 7be07f6..d5131e7 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 66a6b34..af5df29 100644
--- a/src/otko/views/dialogs/path_time_series.py
+++ b/src/otko/views/dialogs/path_time_series.py
@@ -44,13 +44,11 @@ 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")
@@ -67,7 +65,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 "
@@ -86,7 +84,9 @@ 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,7 +102,8 @@ 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)
@@ -111,16 +112,14 @@ 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:
+ except Exception as exc: # noqa: BLE001
QMessageBox.critical(self, "PEER import failed", str(exc))
return
self._values = vals
@@ -133,21 +132,20 @@ 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:
+ except Exception as exc: # noqa: BLE001
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()
@@ -169,9 +167,7 @@ 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/plot_properties.py b/src/otko/views/dialogs/plot_properties.py
deleted file mode 100644
index ae249ef..0000000
--- a/src/otko/views/dialogs/plot_properties.py
+++ /dev/null
@@ -1,150 +0,0 @@
-"""Plot Properties dialog — the user-facing subset of :class:`RenderStyle`.
-
-The field table lives in :mod:`otko.views.canvas3d.style` (``STYLE_FIELDS``),
-so adding an editable property is a one-line change there plus a default on
-the dataclass. Edits are emitted through :attr:`PlotPropertiesDialog.changed`
-so the canvas can preview them live; ``style()`` returns the composed result.
-"""
-
-from __future__ import annotations
-
-from PySide6.QtCore import Signal
-from PySide6.QtGui import QColor
-from PySide6.QtWidgets import (
- QCheckBox,
- QColorDialog,
- QDialog,
- QDialogButtonBox,
- QDoubleSpinBox,
- QFormLayout,
- QLabel,
- QPushButton,
- QSpinBox,
- QVBoxLayout,
- QWidget,
-)
-
-from otko.views.canvas3d.style import STYLE_FIELDS, RenderStyle
-
-
-class _ColorButton(QPushButton):
- """A swatch that opens :class:`QColorDialog` and reports the hex value."""
-
- colorChanged = Signal(str)
-
- def __init__(self, color: str, parent: QWidget | None = None) -> None:
- super().__init__(parent)
- self._color = color
- self.setFixedWidth(96)
- self._refresh()
- self.clicked.connect(self._pick)
-
- def color(self) -> str:
- """Current colour as a ``#rrggbb`` string."""
- return self._color
-
- def set_color(self, color: str) -> None:
- """Set the swatch colour (does not emit :attr:`colorChanged`)."""
- self._color = color
- self._refresh()
-
- def _refresh(self) -> None:
- self.setText(self._color.upper())
- self.setStyleSheet(f"background-color: {self._color}; color: {self._text_color()};")
-
- def _text_color(self) -> str:
- color = QColor(self._color)
- luminance = 0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()
- return "#000000" if luminance > 140 else "#ffffff"
-
- def _pick(self) -> None:
- chosen = QColorDialog.getColor(QColor(self._color), self, "Select colour")
- if chosen.isValid():
- self.set_color(chosen.name())
- self.colorChanged.emit(self._color)
-
-
-class PlotPropertiesDialog(QDialog):
- """Modeless-free editor for the persisted plot style."""
-
- #: Emitted with the composed :class:`RenderStyle` on every edit.
- changed = Signal(object)
-
- def __init__(self, style: RenderStyle, parent: QWidget | None = None) -> None:
- super().__init__(parent)
- self.setWindowTitle("Plot Properties")
- self._base = style
- self._colors: dict[str, _ColorButton] = {}
- self._numbers: dict[str, QDoubleSpinBox | QSpinBox] = {}
- self._checks: dict[str, QCheckBox] = {}
-
- layout = QVBoxLayout(self)
- form = QFormLayout()
- for name, label, kind in STYLE_FIELDS:
- widget = self._build_widget(name, getattr(style, name), kind)
- form.addRow(QLabel(f"{label}:"), widget)
- layout.addLayout(form)
-
- buttons = QDialogButtonBox(
- QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
- )
- reset = buttons.addButton("Reset to Defaults", QDialogButtonBox.ButtonRole.ResetRole)
- assert reset is not None
- reset.clicked.connect(self._reset)
- buttons.accepted.connect(self.accept)
- buttons.rejected.connect(self.reject)
- layout.addWidget(buttons)
-
- # ── public ──────────────────────────────────────────────────────
- def plot_style(self) -> RenderStyle:
- """The composed style for the current widget values.
-
- Named ``plot_style`` (not ``style``) because :meth:`QWidget.style`
- already exists and must keep its Qt meaning.
- """
- updates: dict[str, object] = {name: w.color() for name, w in self._colors.items()}
- updates.update({name: w.value() for name, w in self._numbers.items()})
- updates.update({name: w.isChecked() for name, w in self._checks.items()})
- return self._base.with_updates(**updates)
-
- # ── internals ───────────────────────────────────────────────────
- def _build_widget(self, name: str, value: object, kind: str) -> QWidget:
- if kind == "color":
- button = _ColorButton(str(value))
- button.colorChanged.connect(self._emit_changed)
- self._colors[name] = button
- return button
- if kind == "float":
- spin = QDoubleSpinBox()
- spin.setRange(0.0, 1.0)
- spin.setSingleStep(0.05)
- spin.setDecimals(2)
- spin.setValue(float(value)) # type: ignore[arg-type]
- spin.valueChanged.connect(self._emit_changed)
- self._numbers[name] = spin
- return spin
- if kind == "bool":
- check = QCheckBox()
- check.setChecked(bool(value))
- check.toggled.connect(self._emit_changed)
- self._checks[name] = check
- return check
- int_spin = QSpinBox()
- int_spin.setRange(6, 32)
- int_spin.setValue(int(value)) # type: ignore[call-overload]
- int_spin.valueChanged.connect(self._emit_changed)
- self._numbers[name] = int_spin
- return int_spin
-
- def _emit_changed(self, *_args: object) -> None:
- self.changed.emit(self.plot_style())
-
- def _reset(self) -> None:
- defaults = RenderStyle()
- for name, button in self._colors.items():
- button.set_color(getattr(defaults, name))
- for name, spin in self._numbers.items():
- spin.setValue(getattr(defaults, name))
- for name, check in self._checks.items():
- check.setChecked(getattr(defaults, name))
- self._emit_changed()
diff --git a/src/otko/views/dialogs/quick_grid_lines.py b/src/otko/views/dialogs/quick_grid_lines.py
index 7928d60..876a2f3 100644
--- a/src/otko/views/dialogs/quick_grid_lines.py
+++ b/src/otko/views/dialogs/quick_grid_lines.py
@@ -11,6 +11,7 @@ from PySide6.QtWidgets import (
QDialog,
QDialogButtonBox,
QDoubleSpinBox,
+ QFormLayout,
QGroupBox,
QHBoxLayout,
QLabel,
@@ -30,56 +31,39 @@ 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)
@@ -92,10 +76,8 @@ 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 9b0c424..85daf78 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 (top): Select, Draw Node (F1),
+
Tools toolbar (left): 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 f627ec3..2c63cef 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 b1beb12..4833400 100644
--- a/src/otko/views/dialogs/run_analysis.py
+++ b/src/otko/views/dialogs/run_analysis.py
@@ -57,18 +57,6 @@ 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)
@@ -91,14 +79,12 @@ 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()
@@ -112,8 +98,7 @@ 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)
@@ -121,7 +106,6 @@ 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)
@@ -149,21 +133,15 @@ 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.
@@ -179,13 +157,11 @@ 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
@@ -193,52 +169,9 @@ 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:
+ except Exception as exc: # noqa: BLE001
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 ---")
@@ -258,6 +191,4 @@ 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 fdb9031..ba2937c 100644
--- a/src/otko/views/dialogs/section_editor.py
+++ b/src/otko/views/dialogs/section_editor.py
@@ -12,6 +12,7 @@ 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 (
@@ -44,31 +45,22 @@ 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 ""
@@ -114,37 +106,25 @@ 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)
@@ -165,16 +145,12 @@ 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)
@@ -221,7 +197,8 @@ 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)
@@ -237,7 +214,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:
@@ -249,38 +226,28 @@ 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:
@@ -289,17 +256,13 @@ 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:
@@ -317,12 +280,14 @@ 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:
@@ -330,9 +295,8 @@ 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.
@@ -343,13 +307,9 @@ 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])
@@ -358,24 +318,16 @@ 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(
@@ -389,22 +341,16 @@ 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 ec386e4..7276efe 100644
--- a/src/otko/views/dialogs/section_forms.py
+++ b/src/otko/views/dialogs/section_forms.py
@@ -19,23 +19,15 @@ 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)
@@ -97,13 +89,14 @@ class SectionFormBase(QWidget):
class ElasticSectionForm(SectionFormBase):
type_label = "Elastic Section"
- #: Shape kind → visible display-only dimensions (see the core
- #: ``SectionShape`` union). ``None`` hides them all.
+ #: Shape → visible display-only dimensions. ``None`` hides them all.
_SHAPE_DIMS: ClassVar[dict[str | None, tuple[str, ...]]] = {
None: (),
- "rect": ("b", "d"),
- "angle": ("b", "d", "t"),
- "pipe": ("od", "t"),
+ "rect": ("b", "h"),
+ "I": ("b", "d", "tw", "tf"),
+ "H": ("b", "d", "tw", "tf"),
+ "pipe": ("diameter",),
+ "circle": ("diameter",),
}
def __init__(self, parent: QWidget | None = None) -> None:
@@ -114,14 +107,9 @@ 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."))
@@ -129,14 +117,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", "angle", "pipe"):
+ for key in ("rect", "I", "H", "pipe", "circle"):
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", "d", "t", "od"):
+ for key in ("b", "h", "d", "tw", "tf", "diameter"):
spin = _opt_spin()
lbl = QLabel(f"{key}:")
self._dim_spins[key] = spin
@@ -162,15 +150,20 @@ class ElasticSectionForm(SectionFormBase):
self._g.setValue(s.G)
if s.J is not None:
self._j.setValue(s.J)
- kind = getattr(s.shape, "kind", None) if s.shape is not None else None
- idx = self._shape.findData(kind)
+ idx = self._shape.findData(s.shape)
self._shape.setCurrentIndex(idx if idx >= 0 else 0)
for key, spin in self._dim_spins.items():
- v = getattr(s.shape, key, None) if s.shape is not None else None
+ v = getattr(s, key, 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(),
@@ -180,38 +173,10 @@ class ElasticSectionForm(SectionFormBase):
Iy=self._iy.value(),
G=self._g.value(),
J=self._j.value(),
- shape=self._read_shape(),
+ shape=shape,
+ **dims, # type: ignore[arg-type]
)
- 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`.
@@ -231,12 +196,10 @@ 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:
@@ -277,9 +240,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}
"
@@ -305,9 +268,10 @@ 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 198c821..0647580 100644
--- a/src/otko/views/dialogs/section_library.py
+++ b/src/otko/views/dialogs/section_library.py
@@ -2,7 +2,6 @@
from __future__ import annotations
-from pydantic import ValidationError
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QDialog,
@@ -18,6 +17,7 @@ from PySide6.QtWidgets import (
QVBoxLayout,
QWidget,
)
+from pydantic import ValidationError
from otko.commands import (
AddSectionsCommand,
@@ -113,7 +113,6 @@ 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
@@ -157,12 +156,7 @@ 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
@@ -182,7 +176,6 @@ 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)
@@ -203,10 +196,9 @@ 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 b7cc471..f5c7cd7 100644
--- a/src/otko/views/dialogs/uniform_excitation.py
+++ b/src/otko/views/dialogs/uniform_excitation.py
@@ -23,6 +23,7 @@ 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)"),
@@ -50,12 +51,10 @@ 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")
@@ -85,13 +84,16 @@ 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 cd34449..b8876ae 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, QTreeWidget, QTreeWidgetItem
+from PySide6.QtWidgets import QDockWidget, QMessageBox, QPlainTextEdit, QTreeWidget, QTreeWidgetItem
from otko.services.deformation import (
linear_static_auto_scale,
@@ -34,7 +34,6 @@ from otko.services.results import (
)
from otko.views.canvas3d.model_renderer import RendererMode
from otko.views.docks import (
- ConsoleDock,
DeformedShapeView,
ForceDiagramView,
HysteresisView,
@@ -104,9 +103,13 @@ class DockManager:
self.tabifyDockWidget(props_dock, tree_dock)
tree_dock.raise_()
- self._console_dock = ConsoleDock(self)
- self._console = self._console_dock.view
- self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self._console_dock)
+ 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._table = TableDock()
table_dock = QDockWidget("Table", self)
@@ -119,7 +122,7 @@ class DockManager:
results_dock.setWidget(self._results_panel)
self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, results_dock)
self._results_dock = results_dock
- self.tabifyDockWidget(self._console_dock, table_dock)
+ self.tabifyDockWidget(console_dock, table_dock)
self.tabifyDockWidget(table_dock, results_dock)
table_dock.raise_()
@@ -210,14 +213,6 @@ class DockManager:
because the off-screen renderer must be touched from the GUI
thread (VTK's Qt-backed render window isn't thread-safe).
"""
- if not self._canvas.capabilities.animation_export:
- QMessageBox.information(
- self,
- "Export Mode Shape Animation",
- "Animation export needs the PyVista backend "
- "(Options → Canvas Backend → PyVista (Native)).",
- )
- return
from PySide6.QtWidgets import QFileDialog
path, _sel = QFileDialog.getSaveFileName(
@@ -268,14 +263,6 @@ class DockManager:
"Run a Static analysis first; force diagrams visualise its element-force output.",
)
return
- if not self._canvas.capabilities.diagrams:
- QMessageBox.information(
- self,
- "Force Diagram",
- "Force diagrams are not available on the Plotly backend yet.\n\n"
- "Switch back via Options → Canvas Backend → PyVista (Native).",
- )
- return
self._tear_down_post_dock()
# Pick the component with the largest abs_max as the initial choice
@@ -380,14 +367,6 @@ class DockManager:
"Run a Transient (time-history) analysis first.",
)
return
- if not self._canvas.capabilities.animation_export:
- QMessageBox.information(
- self,
- "Export Time-History Animation",
- "Animation export needs the PyVista backend "
- "(Options → Canvas Backend → PyVista (Native)).",
- )
- return
from pathlib import Path
from PySide6.QtWidgets import QFileDialog, QInputDialog
diff --git a/src/otko/views/docks/__init__.py b/src/otko/views/docks/__init__.py
index c479a97..5b292c3 100644
--- a/src/otko/views/docks/__init__.py
+++ b/src/otko/views/docks/__init__.py
@@ -1,6 +1,5 @@
"""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
@@ -13,15 +12,8 @@ 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",
- "ConsoleDock",
+ "PropertyEditorDock", "ResultsPanel", "TableDock",
+ "DeformedShapeView", "ModeShapeAnimator",
+ "ForceDiagramView", "TimeHistoryView", "HysteresisView",
+ "PushoverCurveView", "ResponseSpectrumView",
]
diff --git a/src/otko/views/docks/console.py b/src/otko/views/docks/console.py
deleted file mode 100644
index e00308c..0000000
--- a/src/otko/views/docks/console.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# 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 de89664..3745c64 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
+from typing import TYPE_CHECKING, Callable
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (
@@ -31,7 +31,8 @@ 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()
@@ -40,9 +41,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()
@@ -52,8 +53,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 a19e99d..855f822 100644
--- a/src/otko/views/docks/force_diagram.py
+++ b/src/otko/views/docks/force_diagram.py
@@ -10,6 +10,8 @@ Exposes two signals:
from __future__ import annotations
+import math
+
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (
QComboBox,
@@ -29,11 +31,12 @@ 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
@@ -84,7 +87,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 c8f0a79..cc56c3e 100644
--- a/src/otko/views/docks/hysteresis.py
+++ b/src/otko/views/docks/hysteresis.py
@@ -15,7 +15,6 @@ For 2D it's [N1, Vy1, Mz1, N2, Vy2, Mz2].
from __future__ import annotations
-import contextlib
from typing import Any
import pyqtgraph as pg
@@ -338,6 +337,8 @@ class HysteresisView(QWidget):
def _clear_curve(self) -> None:
if self._curve is not None:
- with contextlib.suppress(Exception):
+ try:
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 b6c5ed0..5d42684 100644
--- a/src/otko/views/docks/property_editor.py
+++ b/src/otko/views/docks/property_editor.py
@@ -8,8 +8,9 @@ dumb: no direct Project mutation, no Qt ↔ OpenSees coupling.
from __future__ import annotations
-from collections.abc import Callable
+from typing import Callable
+from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QComboBox,
QDoubleSpinBox,
@@ -25,16 +26,14 @@ 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",
]
@@ -54,9 +53,7 @@ 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
@@ -124,12 +121,9 @@ 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)
@@ -185,7 +179,6 @@ 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:
@@ -195,19 +188,16 @@ 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)
@@ -215,23 +205,20 @@ 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)
@@ -239,7 +226,6 @@ 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"):
@@ -250,12 +236,9 @@ 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).
@@ -272,7 +255,6 @@ 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"):
@@ -286,17 +268,13 @@ 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
@@ -310,6 +288,4 @@ 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 86beff2..3dc0e11 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 UnitSystem, labels_for
+from otko.core import UnitLabels, 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 7664171..56d8d6e 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 Qt, Signal
+from PySide6.QtCore import Signal
from PySide6.QtWidgets import (
QHBoxLayout,
QHeaderView,
@@ -20,6 +20,7 @@ from PySide6.QtWidgets import (
QVBoxLayout,
QWidget,
)
+from PySide6.QtCore import Qt
from otko.core import ResponseSpectrum
from otko.services.results import ResponseSpectrumResults
@@ -35,8 +36,7 @@ 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,13 +59,9 @@ 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",
)
@@ -81,19 +77,14 @@ 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)
@@ -142,17 +133,9 @@ 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 9384e9a..65d0866 100644
--- a/src/otko/views/docks/results_panel.py
+++ b/src/otko/views/docks/results_panel.py
@@ -20,7 +20,6 @@ from PySide6.QtWidgets import (
QWidget,
)
-from otko.services.combinations import EnvelopeResults
from otko.services.results import (
ModalResults,
StaticResults,
@@ -60,18 +59,7 @@ class ResultsPanel(QWidget):
"""
self._ndm, self._ndf = ndm, ndf
self._tabs.clear()
- 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):
+ if isinstance(results, StaticResults):
self._title.setText(
f"Static — case #{results.case_id} '{results.case_name}' "
f"({results.n_steps} step(s))"
@@ -79,7 +67,9 @@ 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(
@@ -119,28 +109,6 @@ 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)"]
@@ -156,17 +124,15 @@ 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 3af8383..23a2592 100644
--- a/src/otko/views/docks/table_dock.py
+++ b/src/otko/views/docks/table_dock.py
@@ -35,7 +35,6 @@ from PySide6.QtWidgets import (
from otko.commands import (
AddAnalysisCasesCommand,
- AddCombinationsCommand,
AddElementLoadsCommand,
AddElementsCommand,
AddEqualDOFConstraintCommand,
@@ -48,7 +47,6 @@ from otko.commands import (
AssignMaterialCommand,
AssignSectionCommand,
DeleteAnalysisCasesCommand,
- DeleteCombinationsCommand,
DeleteElementLoadsCommand,
DeleteElementsCommand,
DeleteEqualDOFConstraintCommand,
@@ -61,7 +59,6 @@ from otko.commands import (
SetMassCommand,
SetRestraintCommand,
UpdateAnalysisCaseCommand,
- UpdateCombinationCommand,
UpdateElementFieldsCommand,
UpdateElementLoadCommand,
UpdateEqualDOFConstraintCommand,
@@ -78,8 +75,6 @@ from otko.core import (
ElasticUniaxial,
EqualDOFConstraint,
LinearTimeSeries,
- LoadCombination,
- LoadCombinationItem,
NodalLoad,
Node,
PlainLoadPattern,
@@ -174,7 +169,6 @@ _ANALYSIS_ATTRS = [
"tolerance",
"max_iter",
]
-_COMBO_HEADERS = ["id", "name", "kind", "cases", "factors"]
_TAB_ORDER = (
"Nodes",
@@ -187,7 +181,6 @@ _TAB_ORDER = (
"Element Loads",
"Constraints",
"Analyses",
- "Combinations",
)
@@ -197,7 +190,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)
@@ -277,7 +270,6 @@ 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()
@@ -425,7 +417,6 @@ 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:
@@ -1035,7 +1026,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}")
+ parts.append(f"{label}×{float(factor):g}") # noqa: RUF001
except (TypeError, ValueError):
continue
return ", ".join(parts) if parts else "—"
@@ -1104,49 +1095,6 @@ 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:
@@ -1244,16 +1192,6 @@ 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}")
@@ -1326,8 +1264,6 @@ 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 560dba0..ba285fe 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,7 +208,9 @@ class TimeHistoryView(QWidget):
def _clear_traces(self) -> None:
for _, _, item in self._traces:
- with contextlib.suppress(Exception):
+ try:
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
deleted file mode 100644
index 1123a08..0000000
--- a/src/otko/views/formatting.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# 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 5241869..5577d8b 100644
--- a/src/otko/views/main_window.py
+++ b/src/otko/views/main_window.py
@@ -13,21 +13,15 @@ The ``projectChanged`` / ``modelMutated`` signals live on the
from __future__ import annotations
-import json
from pathlib import Path
-from typing import Any
-from PySide6.QtCore import QSettings
-from PySide6.QtGui import QCloseEvent, QIcon
-from PySide6.QtWidgets import QMainWindow, QMessageBox, QStackedWidget
+from PySide6.QtGui import QIcon
+from PySide6.QtWidgets import QMainWindow
from otko.viewmodels import AnalysisRunner, ProjectViewModel
from otko.views.action_handlers import ActionHandlers
from otko.views.canvas3d import ModelCanvas
from otko.views.canvas3d.diagram_renderer import DiagramRenderer
-from otko.views.canvas3d.selection import SelectionState
-from otko.views.canvas3d.style import RenderStyle
-from otko.views.canvas_base import plotly_available
from otko.views.dock_manager import DockManager
from otko.views.menu_builder import MenuBuilder
from otko.views.render_controls import RenderControls
@@ -39,9 +33,6 @@ from otko.views.tools import (
ToolController,
)
-#: Canvas backends, in menu order.
-CANVAS_BACKENDS = ("pyvista", "plotly")
-
class MainWindow(
QMainWindow,
@@ -52,7 +43,7 @@ class MainWindow(
):
"""Top-level application shell."""
- def __init__(self, settings: QSettings | None = None) -> None:
+ def __init__(self) -> None:
super().__init__()
self.setWindowTitle("OTKO")
self.setWindowIcon(
@@ -68,22 +59,12 @@ class MainWindow(
self._show_node_labels = False
self._show_element_labels = False
- # One selection state shared by every canvas backend so a live swap
- # keeps the current selection (canvas, tree, table, properties dock).
- self._selection = SelectionState(self)
-
- self._settings = settings if settings is not None else QSettings("OTKO", "OTKO")
- self._canvas_backend = self._stored_canvas_backend()
- #: Shared, user-editable visual style (both backends read it).
- self._style = self._load_plot_style()
- self._canvases: dict[str, Any] = {}
- self._diagram_renderers: dict[str, DiagramRenderer] = {}
- # A stack holds every backend's canvas: swapping is just
- # setCurrentWidget, so no backend widget is ever destroyed mid-session.
- self._canvas_stack = QStackedWidget(self)
- self.setCentralWidget(self._canvas_stack)
- self._activate_canvas(self._canvas_backend)
- self._build_tooling()
+ self._build_central_canvas()
+ self._tool_controller = ToolController(self._canvas, self._vm, self)
+ self._select_tool = SelectTool(self._canvas, self._vm, self)
+ self._draw_frame_tool: DrawFrameTool | None = None # lazy-created on activation
+ self._draw_node_tool: DrawNodeTool | None = None
+ self._draw_truss_tool: DrawTrussTool | None = None
self._build_docks()
self._build_actions()
@@ -93,206 +74,13 @@ class MainWindow(
self._build_status_bar()
self._wire()
self._refresh_action_enablement()
- self.restore_layout()
- # ── canvas backend ───────────────────────────────────────────────
- def _stored_canvas_backend(self) -> str:
- """The backend chosen last session, falling back to PyVista."""
- stored = self._settings.value("canvas/backend", "pyvista")
- name = str(stored) if stored is not None else "pyvista"
- if name == "plotly" and plotly_available():
- return "plotly"
- return "pyvista"
-
- def _ensure_canvas(self, backend: str) -> Any:
- """Create a backend's canvas on first use and cache it.
-
- Both canvases are kept alive and swapped with ``setCentralWidget``
- rather than destroyed: tearing a VTK render window down mid-session
- leaves dangling make-current callbacks, and the widgets are cheap to
- retain once built.
- """
- canvas = self._canvases.get(backend)
- if canvas is not None:
- return canvas
- if backend == "plotly":
- # Imported lazily so a plotly-less install still boots PyVista.
- from otko.views.canvas_plotly import PlotlyCanvas
-
- canvas = PlotlyCanvas(
- self,
- style=self._style,
- selection=self._selection,
- log=self._log_canvas_message,
- )
- else:
- canvas = ModelCanvas(self, style=self._style, selection=self._selection)
- self._canvases[backend] = canvas
- return canvas
-
- def _activate_canvas(self, backend: str) -> None:
- """Make the backend's canvas the central widget and rewire overlays."""
- if backend not in CANVAS_BACKENDS:
- backend = "pyvista"
- canvas = self._ensure_canvas(backend)
- if self._canvas_stack.indexOf(canvas) < 0:
- self._canvas_stack.addWidget(canvas)
- self._canvas_stack.setCurrentWidget(canvas)
- self._canvas = canvas
- self._canvas_backend = backend
- if backend == "pyvista":
- renderer = self._diagram_renderers.get("pyvista")
- if renderer is None:
- # Diagram overlay paints onto the same plotter as the model.
- renderer = DiagramRenderer(canvas)
- self._diagram_renderers["pyvista"] = renderer
- self._diagram_renderer = renderer
- else:
- self._diagram_renderer = None
-
- def canvas_backend(self) -> str:
- """Name of the active canvas backend (``"pyvista"`` or ``"plotly"``)."""
- return self._canvas_backend
-
- def _log_canvas_message(self, message: str, severity: str = "info") -> None:
- """Route a JS console line from the Plotly page to the Console dock.
-
- The dock may not exist yet while canvases are being constructed, so
- the lookup is guarded.
- """
- dock = getattr(self, "_console_dock", None)
- if dock is not None:
- dock.log(message, severity=severity)
-
- # ── plot style ───────────────────────────────────────────────────
- def _load_plot_style(self) -> RenderStyle:
- """Style from the last session, ignoring anything unrecognised."""
- raw = self._settings.value("plot/props")
- if not raw:
- return RenderStyle()
- try:
- data = json.loads(str(raw))
- except ValueError:
- return RenderStyle()
- if not isinstance(data, dict):
- return RenderStyle()
- allowed = {name: value for name, value in data.items() if name in RenderStyle.EDITABLE}
- try:
- return RenderStyle(**allowed)
- except (TypeError, ValueError):
- return RenderStyle()
-
- def _save_plot_style(self) -> None:
- self._settings.setValue("plot/props", json.dumps(self._style.editable_values()))
-
- def set_plot_style(self, style: RenderStyle, *, persist: bool = True) -> None:
- """Apply a style to every canvas (live preview when ``persist`` is off)."""
- self._style = style
- for canvas in self._canvases.values():
- canvas.set_style(style)
- if persist:
- self._save_plot_style()
-
- def _build_tooling(self) -> None:
- """(Re)build the tool controller and tools against the current canvas."""
- self._tool_controller = ToolController(self._canvas, self._vm, self)
- self._select_tool = SelectTool(self._canvas, self._vm, self)
- self._draw_frame_tool: DrawFrameTool | None = None # lazy-created on activation
- self._draw_node_tool: DrawNodeTool | None = None
- self._draw_truss_tool: DrawTrussTool | None = None
- self._tool_controller.toolChanged.connect(self._on_tool_changed)
- self._tool_controller.toolChanged.connect(self._sync_tool_actions)
-
- def swap_canvas_backend(self, backend: str) -> None:
- """Switch the live canvas backend, preserving model/selection state.
-
- The camera pose is intentionally re-framed rather than transferred:
- the two engines have different camera models, and a wrong pose is
- worse than a clean isometric reset.
- """
- if backend not in CANVAS_BACKENDS or backend == self._canvas_backend:
- return
- if backend == "plotly" and not plotly_available():
- return
-
- project = self._vm.project
- plane = self._canvas.working_plane_type()
- offset = self._canvas.working_plane_offset()
- active_name = getattr(self._tool_controller.active, "name", None)
-
- self._activate_canvas(backend)
- self._build_tooling()
-
- # Project → re-frames the camera; working plane rebuilds the grid.
- self._canvas.show_project(project)
- if plane is not None and offset is not None:
- self._canvas.set_working_plane(plane, offset)
- # Re-apply the overlays whose state lives on the toolbar actions.
- self._canvas.set_show_section_extrusions(self._act_show_extruded.isChecked())
- self._canvas.set_show_local_axes(self._act_show_local_axes.isChecked())
- self._canvas.set_display_options(
- show_node_labels=self._show_node_labels,
- show_element_labels=self._show_element_labels,
- )
- # Keep the active tool (and its snap preview) alive across the swap.
- # Re-entering through the action handlers recreates the tool lazily
- # against the new canvas.
- if active_name == "Draw Node":
- self._on_draw_node_tool()
- elif active_name == "Draw Frame":
- self._on_draw_frame_tool()
- elif active_name == "Draw Truss":
- self._on_draw_truss_tool()
-
- self._settings.setValue("canvas/backend", self._canvas_backend)
- self._sync_canvas_backend_actions()
- self._refresh_action_enablement()
-
- # ── layout persistence ───────────────────────────────────────────
- def save_layout(self) -> None:
- """Persist window geometry and dock/toolbar state for next launch."""
- self._settings.setValue("geometry", self.saveGeometry())
- self._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.
- """
- geometry = self._settings.value("geometry")
- if geometry is not None:
- self.restoreGeometry(geometry)
- state = self._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()
+ # ── construction ─────────────────────────────────────────────────
+ def _build_central_canvas(self) -> None:
+ self._canvas = ModelCanvas(self)
+ self.setCentralWidget(self._canvas)
+ # Diagram overlay paints onto the same plotter as the model.
+ self._diagram_renderer = DiagramRenderer(self._canvas)
# ── wiring ────────────────────────────────────────────────────────
def _wire(self) -> None:
@@ -309,7 +97,6 @@ class MainWindow(
self._act_about.triggered.connect(self._on_about)
self._act_quick_guide.triggered.connect(self._on_quick_guide)
self._act_set_units.triggered.connect(self._on_set_units)
- self._act_plot_properties.triggered.connect(self._on_plot_properties)
# Edit
self._act_delete.triggered.connect(self._on_delete)
@@ -324,12 +111,9 @@ class MainWindow(
self._act_tool_draw_node.triggered.connect(self._on_draw_node_tool)
self._act_tool_draw_frame.triggered.connect(self._on_draw_frame_tool)
self._act_tool_draw_truss.triggered.connect(self._on_draw_truss_tool)
- # toolChanged → toolbar/menu sync is wired in _build_tooling(), which
- # also runs on a canvas-backend swap (then a fresh controller exists).
-
- # Canvas backend
- self._act_canvas_pyvista.triggered.connect(lambda: self.swap_canvas_backend("pyvista"))
- self._act_canvas_plotly.triggered.connect(lambda: self.swap_canvas_backend("plotly"))
+ self._tool_controller.toolChanged.connect(self._on_tool_changed)
+ # Keep the Tools toolbar checked state in sync with the controller.
+ self._tool_controller.toolChanged.connect(self._sync_tool_actions)
# Define
self._act_grid.triggered.connect(self._on_grid_system)
@@ -357,7 +141,6 @@ 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
@@ -373,7 +156,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_dock.log)
+ self._runner.log.connect(self._console.appendPlainText)
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 5e7a271..30450f9 100644
--- a/src/otko/views/menu_builder.py
+++ b/src/otko/views/menu_builder.py
@@ -155,7 +155,6 @@ 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
@@ -192,46 +191,9 @@ class MenuBuilder:
# No shortcut: F1/F2/F3/F5 are taken by the draw tools and Run.
self._act_quick_guide = QAction("&Quick Guide", self)
self._act_set_units = QAction("Set Display &Units…", self)
- self._act_plot_properties = QAction("&Plot Properties…", self)
- self._act_plot_properties.setToolTip(
- "Colours and sizes used by both canvas backends (persisted)."
- )
-
- # Canvas backend — exclusive radio pair live-switching the 3D view.
- from otko.views.canvas_base import plotly_available
-
- self._canvas_backend_group = QActionGroup(self)
- self._canvas_backend_group.setExclusive(True)
- self._act_canvas_pyvista = QAction("&PyVista (Native)", self, checkable=True)
- self._act_canvas_pyvista.setToolTip(
- "Native VTK/OpenGL viewport — fastest for large models."
- )
- self._act_canvas_plotly = QAction("&Plotly (WebGL)", self, checkable=True)
- self._act_canvas_plotly.setToolTip(
- "plotly.js rendered in a WebEngine view — richer graphics, higher overhead."
- )
- self._canvas_backend_group.addAction(self._act_canvas_pyvista)
- self._canvas_backend_group.addAction(self._act_canvas_plotly)
- if not plotly_available():
- self._act_canvas_plotly.setEnabled(False)
- self._act_canvas_plotly.setToolTip(
- 'Install the optional GUI extra (pip install -e ".[gui]") to enable plotly.'
- )
self._apply_toolbar_icons()
- def _sync_canvas_backend_actions(self) -> None:
- """Mirror the active canvas backend onto the Options radio pair."""
- active = getattr(
- self,
- "_act_canvas_plotly" if self._canvas_backend == "plotly" else "_act_canvas_pyvista",
- )
- self._canvas_backend_group.blockSignals(True)
- try:
- active.setChecked(True)
- finally:
- self._canvas_backend_group.blockSignals(False)
-
def _apply_toolbar_icons(self) -> None:
"""Assign icons + self-documenting tips to every toolbar action.
@@ -323,11 +285,6 @@ 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",
@@ -463,7 +420,6 @@ 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)
@@ -516,11 +472,6 @@ class MenuBuilder:
m_options = mb.addMenu("&Options")
m_options.addAction(self._act_set_units)
- m_options.addAction(self._act_plot_properties)
- m_backend = m_options.addMenu("Canvas &Backend")
- m_backend.addAction(self._act_canvas_pyvista)
- m_backend.addAction(self._act_canvas_plotly)
- self._sync_canvas_backend_actions()
m_help = mb.addMenu("&Help")
m_help.addAction(self._act_quick_guide)
@@ -531,7 +482,7 @@ class MenuBuilder:
tb.setMovable(True)
tb.setIconSize(QSize(24, 24))
tb.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
- # Top edge; Tools shares the top area (built next).
+ # Top edge; Tools stays in the left area independently (built next).
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb)
self._view_toolbar = tb
tb.addAction(self._act_zoom_extents)
@@ -577,8 +528,9 @@ class MenuBuilder:
tb.setMovable(True)
tb.setIconSize(QSize(24, 24))
tb.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
- # Top edge alongside the View toolbar (built just before).
- self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb)
+ # View lives on the top edge, so Tools stays left independently
+ # in its own left-area column.
+ self.addToolBar(Qt.ToolBarArea.LeftToolBarArea, 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 06db479..fecf2d7 100644
--- a/src/otko/views/render_controls.py
+++ b/src/otko/views/render_controls.py
@@ -50,23 +50,6 @@ class RenderControls:
)
# ── SAP2000-style view switching + working level ───────────────
- def _on_plot_properties(self) -> None:
- """Edit the shared visual style.
-
- Edits preview live on both canvases; Cancel restores the style the
- dialog opened with, and only OK persists to ``QSettings``.
- """
- from otko.views.dialogs import PlotPropertiesDialog
-
- original = self._style
- dialog = PlotPropertiesDialog(original, self)
- dialog.changed.connect(lambda style: self.set_plot_style(style, persist=False))
- if dialog.exec() == QDialog.DialogCode.Accepted:
- self.set_plot_style(dialog.plot_style())
- self._log("Plot properties updated.")
- else:
- self.set_plot_style(original, persist=False)
-
def _on_toggle_parallel(self, on: bool) -> None:
cam = self._canvas.camera
cam.parallel_projection = on
@@ -476,7 +459,6 @@ class RenderControls:
has_pushover = isinstance(self._latest_results, PushoverResults)
has_rs = isinstance(self._latest_results, ResponseSpectrumResults)
n_sel = len(self._canvas.selection.nodes)
- caps = self._canvas.capabilities
self._act_save.setEnabled(has_project)
self._act_save_as.setEnabled(has_project)
self._act_export_opspy.setEnabled(has_project)
@@ -500,11 +482,9 @@ class RenderControls:
self._act_tool_draw_truss.setEnabled(has_project)
self._act_show_deformed.setEnabled(has_static)
self._act_show_mode_shape.setEnabled(has_modal)
- # Force diagrams + video export are backend capabilities, not just
- # data availability (Plotly cannot do either yet).
- self._act_show_force_diagram.setEnabled(has_static and caps.diagrams)
+ self._act_show_force_diagram.setEnabled(has_static)
self._act_show_time_history.setEnabled(has_transient)
- self._act_export_th_animation.setEnabled(has_transient and caps.animation_export)
+ self._act_export_th_animation.setEnabled(has_transient)
self._act_show_hysteresis.setEnabled(has_transient)
self._act_show_pushover.setEnabled(has_pushover)
self._act_show_response_spectrum.setEnabled(has_rs)
@@ -516,5 +496,5 @@ class RenderControls:
self._act_clear_display.setEnabled(has_project)
def _log(self, message: str) -> None:
- self._console_dock.log(message)
+ self._console.appendPlainText(message)
self.statusBar().showMessage(message, 5000)
diff --git a/src/otko/views/tools/__init__.py b/src/otko/views/tools/__init__.py
index 8c593d9..992e462 100644
--- a/src/otko/views/tools/__init__.py
+++ b/src/otko/views/tools/__init__.py
@@ -5,11 +5,5 @@ 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 bfb6072..2ffe593 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,11 +84,10 @@ 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 980716a..d0bd47a 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,9 +67,7 @@ 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 0f286be..b82660a 100644
--- a/src/otko/views/tools/draw_node.py
+++ b/src/otko/views/tools/draw_node.py
@@ -27,20 +27,14 @@ 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)
@@ -76,17 +70,16 @@ 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:
@@ -115,9 +108,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)
@@ -131,13 +124,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.
@@ -160,5 +153,9 @@ 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 4ee8254..1eeb69d 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 c91c064..930c400 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,52 +1,22 @@
-"""Shared pytest configuration.
+"""Shared pytest fixtures.
-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.
+`pytest-qt` automatically provides a `qtbot` fixture and a QApplication
+instance. We add convenience fixtures here as the suite grows.
"""
from __future__ import annotations
-import importlib.util
-import os
-from collections.abc import Iterator
-
import pytest
-os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
-
-
-@pytest.fixture(autouse=True, scope="session")
-def _isolate_qsettings(tmp_path_factory: pytest.TempPathFactory) -> Iterator[None]:
- """Redirect ``QSettings`` to a temp dir so tests never touch user state.
-
- ``MainWindow`` persists preferences (window layout, canvas backend) under
- ``QSettings("OTKO", "OTKO")``. Without redirection a developer who, say,
- switched to the Plotly backend would see PyVista-specific GUI tests fail
- against their real settings. Skipped entirely when Qt is not installed, so
- the headless job stays free of Qt.
- """
- if importlib.util.find_spec("PySide6") is None:
- yield
- return
- from PySide6.QtCore import QSettings
-
- settings_dir = tmp_path_factory.mktemp("qsettings")
- for fmt in (QSettings.Format.NativeFormat, QSettings.Format.IniFormat):
- QSettings.setPath(fmt, QSettings.Scope.UserScope, str(settings_dir))
- yield
-
@pytest.fixture(autouse=True)
-def _wipe_opensees_domain() -> Iterator[None]:
- """Wipe the OpenseesPy domain after a test if the solver was imported."""
- yield
+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.
+ """
try:
- from openseespy.opensees import wipe
- except Exception: # pragma: no cover - solver not installed / import side effects
+ import openseespy.opensees as ops
+ except ImportError:
return
- wipe()
+ ops.wipe()
diff --git a/tests/gui/test_analysis_commands.py b/tests/gui/test_analysis_commands.py
index fed67e3..ca5ecf8 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 (
+from otko.commands import ( # noqa: E402
AddAnalysisCasesCommand,
DeleteAnalysisCasesCommand,
UpdateAnalysisCaseCommand,
)
-from otko.core import (
+from otko.core import ( # noqa: E402
LinearTimeSeries,
ModalCase,
NodalLoad,
@@ -20,7 +20,7 @@ from otko.core import (
StaticCase,
TransientCase,
)
-from otko.viewmodels import ProjectViewModel
+from otko.viewmodels import ProjectViewModel # noqa: E402
def _vm_with_pattern() -> ProjectViewModel:
@@ -29,9 +29,8 @@ 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
@@ -58,14 +57,9 @@ 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
@@ -80,14 +74,9 @@ 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
@@ -98,14 +87,9 @@ 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)
@@ -125,15 +109,10 @@ 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 14c100a..b2f982c 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 (
+from otko.commands import ( # noqa: E402
AddAnalysisCasesCommand,
AddElementsCommand,
AddNodalLoadsCommand,
AddNodesCommand,
AddSectionsCommand,
)
-from otko.core import (
+from otko.core import ( # noqa: E402
ElasticBeamColumn,
ElasticSection,
Node,
StaticCase,
)
-from otko.services.results import StaticResults
-from otko.viewmodels import AnalysisRunner, ProjectViewModel
+from otko.services.results import StaticResults # noqa: E402
+from otko.viewmodels import AnalysisRunner, ProjectViewModel # noqa: E402
@pytest.fixture
@@ -35,42 +35,21 @@ 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 b0b4fba..151b1d2 100644
--- a/tests/gui/test_assign_load_pattern_name.py
+++ b/tests/gui/test_assign_load_pattern_name.py
@@ -6,14 +6,15 @@ import pytest
pytest.importorskip("PySide6")
-from otko.commands import AddNodalLoadsCommand
-from otko.core import (
+from otko.commands import AddNodalLoadsCommand # noqa: E402
+from otko.core import ( # noqa: E402
LinearTimeSeries,
Node,
PlainLoadPattern,
+ Project,
)
-from otko.viewmodels import ProjectViewModel
-from otko.views.dialogs.assign_load import AssignLoadDialog
+from otko.viewmodels import ProjectViewModel # noqa: E402
+from otko.views.dialogs.assign_load import AssignLoadDialog # noqa: E402
@pytest.mark.gui
@@ -47,11 +48,10 @@ 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,23 +68,14 @@ 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
@@ -99,20 +90,13 @@ 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]
@@ -127,20 +111,11 @@ 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 3ba0dda..9501620 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
+from otko.views.dialogs.assign_masses import AssignMassesDialog # noqa: E402
@pytest.mark.gui
@@ -23,9 +23,7 @@ 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 9a8cb66..183e173 100644
--- a/tests/gui/test_assign_zls.py
+++ b/tests/gui/test_assign_zls.py
@@ -6,36 +6,27 @@ import pytest
pytest.importorskip("PySide6")
-from otko.core import (
+from otko.core import ( # noqa: E402
ElasticSection,
Node,
Project,
)
-from otko.views.dialogs.assign_zls import (
+from otko.views.dialogs.assign_zls import ( # noqa: E402
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,
+ )],
)
@@ -52,8 +43,7 @@ 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_backend_switch.py b/tests/gui/test_canvas_backend_switch.py
deleted file mode 100644
index ed77be1..0000000
--- a/tests/gui/test_canvas_backend_switch.py
+++ /dev/null
@@ -1,122 +0,0 @@
-"""GUI tests for the live canvas-backend switch (PyVista ⇄ Plotly).
-
-QSettings are injected per-test so a swap never leaks into the real user
-configuration or the next test.
-"""
-
-from __future__ import annotations
-
-import pytest
-
-pytest.importorskip("PySide6")
-pytest.importorskip("pyvistaqt")
-pytest.importorskip("plotly")
-
-from PySide6.QtCore import QSettings
-
-
-def _make_window(tmp_path, qtbot): # type: ignore[no-untyped-def]
- from otko.views.main_window import MainWindow
-
- settings = QSettings(str(tmp_path / "otko.ini"), QSettings.Format.IniFormat)
- window = MainWindow(settings=settings)
- qtbot.addWidget(window)
- return window
-
-
-@pytest.mark.gui
-def test_default_backend_is_pyvista(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
- from otko.views.canvas3d import ModelCanvas
-
- window = _make_window(tmp_path, qtbot)
- assert window.canvas_backend() == "pyvista"
- assert isinstance(window._canvas, ModelCanvas)
- assert window._diagram_renderer is not None
-
-
-@pytest.mark.gui
-def test_switch_to_plotly_preserves_selection(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
- from otko.views.canvas_plotly import PlotlyCanvas
-
- window = _make_window(tmp_path, qtbot)
- window._vm.new_project()
- window._canvas.selection.select_node(2)
-
- window.swap_canvas_backend("plotly")
-
- assert window.canvas_backend() == "plotly"
- assert isinstance(window._canvas, PlotlyCanvas)
- # One shared SelectionState: the tree/table/properties stay bound.
- assert window._canvas.selection is window._selection
- assert window._canvas.selection.nodes == frozenset({2})
- # Both canvases live in the stack; the PyVista one was not destroyed.
- assert window._canvas_stack.count() == 2
-
-
-@pytest.mark.gui
-def test_plotly_gates_pyvista_only_features(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
- window = _make_window(tmp_path, qtbot)
- assert window._canvas.capabilities.diagrams is True
-
- window.swap_canvas_backend("plotly")
- caps = window._canvas.capabilities
- assert caps.diagrams is False
- assert caps.animation_export is False
- assert caps.extrusions is True and caps.labels is True
-
- window._refresh_action_enablement()
- assert window._act_show_force_diagram.isEnabled() is False
- assert window._act_export_th_animation.isEnabled() is False
-
-
-@pytest.mark.gui
-def test_swapping_back_reuses_the_same_canvases(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
- window = _make_window(tmp_path, qtbot)
- pyvista_canvas = window._canvas
-
- window.swap_canvas_backend("plotly")
- plotly_canvas = window._canvas
- window.swap_canvas_backend("pyvista")
-
- assert window._canvas is pyvista_canvas
- assert window._diagram_renderer is not None
- # No duplicate widgets accumulate across repeated swaps.
- window.swap_canvas_backend("plotly")
- assert window._canvas is plotly_canvas
- assert window._diagram_renderer is None # diagrams are PyVista-only
- assert window._canvas_stack.count() == 2
-
-
-@pytest.mark.gui
-def test_backend_choice_is_persisted(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
- window = _make_window(tmp_path, qtbot)
- window.swap_canvas_backend("plotly")
- assert window._settings.value("canvas/backend") == "plotly"
-
- # A fresh window with the same settings boots straight into Plotly.
- from otko.views.canvas_plotly import PlotlyCanvas
- from otko.views.main_window import MainWindow
-
- settings = QSettings(str(tmp_path / "otko.ini"), QSettings.Format.IniFormat)
- second = MainWindow(settings=settings)
- qtbot.addWidget(second)
- assert second.canvas_backend() == "plotly"
- assert isinstance(second._canvas, PlotlyCanvas)
-
-
-@pytest.mark.gui
-def test_working_plane_survives_the_swap(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
- window = _make_window(tmp_path, qtbot)
- window._vm.open(_example("basic_truss"))
- window._canvas.set_working_plane("XY", 0.0)
-
- window.swap_canvas_backend("plotly")
-
- assert window._canvas.working_plane_type() == "XY"
- assert window._canvas.working_plane_offset() == 0.0
-
-
-def _example(name: str) -> str:
- from pathlib import Path
-
- return str(Path(__file__).resolve().parents[2] / "examples" / f"{name}.osmodel")
diff --git a/tests/gui/test_canvas_grid_snap.py b/tests/gui/test_canvas_grid_snap.py
index 02ddb4b..81e8bd3 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 (
+from otko.core import ( # noqa: E402
CoordinateGridSystem,
CoordinateSystem,
GridSystem,
@@ -23,8 +23,7 @@ from otko.core import (
# ────────────────────── 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,
@@ -34,7 +33,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
@@ -66,7 +65,6 @@ 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)
@@ -103,17 +101,15 @@ 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)
@@ -138,23 +134,20 @@ 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))
@@ -165,23 +158,20 @@ 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
@@ -193,7 +183,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
@@ -214,21 +204,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]
@@ -236,7 +226,6 @@ 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 6672806..c911141 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 (
+from otko.core import ( # noqa: E402
NodalLoad,
PlainLoadPattern,
StaticCase,
TransientCase,
)
-from otko.views.dialogs.case_forms import TransientCaseForm
+from otko.views.dialogs.case_forms import TransientCaseForm # noqa: E402
def _patterns(): # type: ignore[no-untyped-def]
diff --git a/tests/gui/test_commands.py b/tests/gui/test_commands.py
index 0457390..a02bdc0 100644
--- a/tests/gui/test_commands.py
+++ b/tests/gui/test_commands.py
@@ -11,14 +11,15 @@ import pytest
pytest.importorskip("PySide6")
-from otko.commands import (
+from otko.commands import ( # noqa: E402
AddElementsCommand,
AddNodalLoadsCommand,
AddNodesCommand,
+ DeleteElementsCommand,
DeleteNodesCommand,
SetRestraintCommand,
)
-from otko.core import (
+from otko.core import ( # noqa: E402
LinearTimeSeries,
NodalLoad,
Node,
@@ -26,7 +27,7 @@ from otko.core import (
Steel01,
TrussElement,
)
-from otko.viewmodels import ProjectViewModel
+from otko.viewmodels import ProjectViewModel # noqa: E402
# ─────────────────────────── helpers ────────────────────────────────
@@ -76,27 +77,17 @@ 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}
@@ -111,15 +102,10 @@ 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 426c23b..fa14511 100644
--- a/tests/gui/test_convert_element_type.py
+++ b/tests/gui/test_convert_element_type.py
@@ -6,82 +6,52 @@ import pytest
pytest.importorskip("PySide6")
-from otko.commands import (
+from otko.commands import ( # noqa: E402
AddElementsCommand,
AddMaterialsCommand,
AddNodesCommand,
AddSectionsCommand,
ConvertElementTypeCommand,
)
-from otko.core import (
+from otko.core import ( # noqa: E402
ElasticBeamColumn,
ElasticSection,
ElasticUniaxial,
Node,
TrussElement,
)
-from otko.viewmodels import ProjectViewModel
+from otko.viewmodels import ProjectViewModel # noqa: E402
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)
@@ -92,22 +62,13 @@ 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)
@@ -117,22 +78,12 @@ 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()
@@ -142,23 +93,13 @@ 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
@@ -166,20 +107,10 @@ 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 4b6f9b3..c671a1e 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
-from otko.views.dialogs.linear_time_series import LinearTimeSeriesDialog
-from otko.views.dialogs.plain_pattern import PlainPatternDialog
+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
@pytest.mark.gui
@@ -27,12 +27,10 @@ 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 ff6370c..b98bb40 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
-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
+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
class _CanvasStub:
@@ -35,15 +35,10 @@ 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
@@ -91,7 +86,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
@@ -155,7 +150,6 @@ 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]
@@ -171,9 +165,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]
@@ -185,21 +179,18 @@ 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}
@@ -207,22 +198,19 @@ 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 aab389d..26a9edb 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
-from otko.viewmodels import ProjectViewModel
-from otko.views.canvas3d.selection import SelectionState
-from otko.views.tools.draw_node import (
+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
DrawNodeTool,
_snap_to_grid,
)
@@ -46,12 +46,8 @@ 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)
@@ -103,11 +99,10 @@ 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 bcc2879..3cc5d52 100644
--- a/tests/gui/test_draw_truss_tool.py
+++ b/tests/gui/test_draw_truss_tool.py
@@ -6,17 +6,18 @@ import pytest
pytest.importorskip("PySide6")
-from otko.core import (
+from otko.core import ( # noqa: E402
CoordinateGridSystem,
+ CoordinateSystem,
ElasticUniaxial,
GridSystem,
Node,
TrussElement,
make_grid_lines,
)
-from otko.viewmodels import ProjectViewModel
-from otko.views.canvas3d.selection import SelectionState
-from otko.views.tools.draw_truss import DrawTrussTool
+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
class _CanvasStub:
@@ -33,12 +34,10 @@ 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",
@@ -61,13 +60,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)
@@ -79,9 +78,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
@@ -105,8 +104,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)
@@ -116,5 +115,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 0013ad4..489d64b 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 (
+from otko.core import ( # noqa: E402
CoordinateGridSystem,
CoordinateSystem,
GridLine,
@@ -19,18 +19,11 @@ from otko.core import (
@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]
@@ -40,7 +33,6 @@ 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)
@@ -56,7 +48,6 @@ 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),
@@ -74,7 +65,6 @@ 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)),
@@ -106,7 +96,6 @@ 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])),
@@ -128,7 +117,6 @@ 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"
@@ -142,7 +130,6 @@ 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 a57a00f..8ce4d0b 100644
--- a/tests/gui/test_main_window.py
+++ b/tests/gui/test_main_window.py
@@ -20,22 +20,3 @@ 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 23523bc..4671343 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
-from otko.views.dialogs.material_forms import (
- FORM_REGISTRY,
+from otko.core import Concrete04 # noqa: E402
+from otko.views.dialogs.material_forms import ( # noqa: E402
Concrete04Form,
+ FORM_REGISTRY,
)
@@ -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,14 +77,9 @@ 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()
@@ -107,8 +102,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 54c1899..13a6eb3 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 (
+from otko.views.dialogs.material_forms import ( # noqa: E402
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,7 +83,6 @@ 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 5f1e132..1571fc3 100644
--- a/tests/gui/test_path_ts_dialogs.py
+++ b/tests/gui/test_path_ts_dialogs.py
@@ -8,16 +8,17 @@ import pytest
pytest.importorskip("PySide6")
-from otko.commands import (
+from otko.commands import ( # noqa: E402
AddLoadPatternCommand,
AddTimeSeriesCommand,
)
-from otko.core import (
+from otko.core import ( # noqa: E402
+ Node,
PathTimeSeries,
Project,
UniformExcitationPattern,
)
-from otko.viewmodels import ProjectViewModel
+from otko.viewmodels import ProjectViewModel # noqa: E402
# ─────────────── AddTimeSeriesCommand / AddLoadPatternCommand ──────────────
@@ -27,9 +28,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]
@@ -38,14 +39,9 @@ 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))
@@ -55,31 +51,22 @@ 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
@@ -100,24 +87,20 @@ 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)
@@ -131,7 +114,6 @@ 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)
@@ -141,12 +123,9 @@ 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_plot_properties.py b/tests/gui/test_plot_properties.py
deleted file mode 100644
index 3ef3601..0000000
--- a/tests/gui/test_plot_properties.py
+++ /dev/null
@@ -1,131 +0,0 @@
-"""GUI tests for Options → Plot Properties.
-
-Covers the dialog itself plus MainWindow's live-apply / persist / revert
-wiring. QSettings are injected so nothing touches real user state.
-"""
-
-from __future__ import annotations
-
-import pytest
-
-pytest.importorskip("PySide6")
-pytest.importorskip("pyvistaqt")
-
-from PySide6.QtCore import QObject, QSettings, Signal
-from PySide6.QtWidgets import QDialog
-
-
-def _settings(tmp_path): # type: ignore[no-untyped-def]
- return QSettings(str(tmp_path / "otko.ini"), QSettings.Format.IniFormat)
-
-
-def _window(tmp_path, qtbot): # type: ignore[no-untyped-def]
- from otko.views.main_window import MainWindow
-
- window = MainWindow(settings=_settings(tmp_path))
- qtbot.addWidget(window)
- return window
-
-
-def test_default_style_is_the_documented_palette(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
- from otko.views.canvas3d.style import RenderStyle
-
- window = _window(tmp_path, qtbot)
- assert window._style == RenderStyle()
-
-
-def test_set_plot_style_reaches_both_canvases_and_persists(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
- window = _window(tmp_path, qtbot)
- window.swap_canvas_backend("plotly") # build the second canvas too
-
- window.set_plot_style(
- window._style.with_updates(element_beam_color="#ff0000", label_font_size=20)
- )
-
- assert set(window._canvases) == {"pyvista", "plotly"}
- for canvas in window._canvases.values():
- assert canvas._style.element_beam_color == "#ff0000"
- assert canvas._style.label_font_size == 20
-
- # A fresh window sharing the settings file reloads the customisation.
- from otko.views.main_window import MainWindow
-
- fresh = MainWindow(settings=_settings(tmp_path))
- qtbot.addWidget(fresh)
- assert fresh._style.element_beam_color == "#ff0000"
- assert fresh._style.label_font_size == 20
-
-
-def test_preview_does_not_persist(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
- window = _window(tmp_path, qtbot)
- window.set_plot_style(window._style.with_updates(node_color="#010203"), persist=False)
-
- assert window._canvas._style.node_color == "#010203"
- assert window._settings.value("plot/props") is None
-
-
-def test_cancel_reverts_the_style(tmp_path, qtbot, monkeypatch) -> None: # type: ignore[no-untyped-def]
- window = _window(tmp_path, qtbot)
- original = window._style
- preview = original.with_updates(node_color="#010203")
-
- class _RejectingDialog(QObject):
- changed = Signal(object)
-
- def __init__(self, style: object, parent: QObject | None = None) -> None:
- super().__init__(parent)
- self._style = preview
-
- def exec(self) -> QDialog.DialogCode:
- self.changed.emit(preview) # live preview happens...
- return QDialog.DialogCode.Rejected # ...but the user cancels
-
- def plot_style(self): # type: ignore[no-untyped-def]
- return self._style
-
- monkeypatch.setattr("otko.views.dialogs.PlotPropertiesDialog", _RejectingDialog)
- window._on_plot_properties()
-
- assert window._style == original
- assert window._canvas._style.node_color == original.node_color
- assert window._settings.value("plot/props") is None
-
-
-def test_accept_applies_and_persists(tmp_path, qtbot, monkeypatch) -> None: # type: ignore[no-untyped-def]
- window = _window(tmp_path, qtbot)
- preview = window._style.with_updates(node_color="#010203")
-
- class _AcceptingDialog(QObject):
- changed = Signal(object)
-
- def __init__(self, style: object, parent: QObject | None = None) -> None:
- super().__init__(parent)
- self._style = preview
-
- def exec(self) -> QDialog.DialogCode:
- return QDialog.DialogCode.Accepted
-
- def plot_style(self): # type: ignore[no-untyped-def]
- return self._style
-
- monkeypatch.setattr("otko.views.dialogs.PlotPropertiesDialog", _AcceptingDialog)
- window._on_plot_properties()
-
- assert window._style.node_color == "#010203"
- assert window._canvas._style.node_color == "#010203"
- assert "#010203" in str(window._settings.value("plot/props"))
-
-
-def test_dialog_edits_and_reset(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
- from otko.views.canvas3d.style import RenderStyle
- from otko.views.dialogs import PlotPropertiesDialog
-
- dialog = PlotPropertiesDialog(RenderStyle())
- qtbot.addWidget(dialog)
- assert dialog.plot_style() == RenderStyle()
-
- dialog._colors["node_color"].set_color("#abcdef")
- assert dialog.plot_style().node_color == "#abcdef"
-
- dialog._reset()
- assert dialog.plot_style() == RenderStyle()
diff --git a/tests/gui/test_plotly_hover.py b/tests/gui/test_plotly_hover.py
deleted file mode 100644
index 78d5160..0000000
--- a/tests/gui/test_plotly_hover.py
+++ /dev/null
@@ -1,93 +0,0 @@
-"""Regression tests for the Plotly hover/snap-marker contract.
-
-``plotly_hover``/``plotly_unhover`` fire continuously while the mouse moves.
-Touching the plot on each one made it redraw per mouse move, re-firing hover
-until the stack blew (``RangeError: Maximum call stack size exceeded``) and the
-canvas froze. The marker is therefore coalesced and only touched when its
-shown/hidden state actually changes.
-"""
-
-from __future__ import annotations
-
-from pathlib import Path
-
-import pytest
-
-pytest.importorskip("PySide6")
-pytest.importorskip("plotly")
-
-from otko.services import load_project
-
-EXAMPLES = Path(__file__).resolve().parents[2] / "examples"
-
-#: 200 mouse-move events over a *non-snap* point with snapping armed.
-_CHURN_PROBE = """(function(){
- var gd = document.getElementById('plot');
- var n = 0; var original = Plotly.restyle;
- Plotly.restyle = function () { n++; return original.apply(Plotly, arguments); };
- var point = { data: gd.data[0], customdata: [0, 0, 0] };
- try {
- for (var i = 0; i < 200; i++) {
- gd.emit('plotly_hover', { points: [point] });
- gd.emit('plotly_unhover', {});
- }
- return n;
- } finally { Plotly.restyle = original; }
-})()"""
-
-_HOVER_MARKER_X = (
- "JSON.stringify((document.getElementById('plot').data"
- ".find(function(d){return d.meta && d.meta.kind === 'hover';}) || {}).x)"
-)
-
-_EMIT_SNAP_HOVER = """(function(){
- var gd = document.getElementById('plot');
- var snap = gd.data.filter(function(d){return d.meta && d.meta.kind === 'snap';})[0];
- var point = { data: snap, customdata: [snap.x[0], snap.y[0], snap.z[0]] };
- gd.emit('plotly_hover', { points: [point] });
- return true;
-})()"""
-
-
-def _open_canvas(qtbot, name: str): # type: ignore[no-untyped-def]
- from otko.views.canvas_plotly import PlotlyCanvas
-
- canvas = PlotlyCanvas()
- qtbot.addWidget(canvas)
- canvas.show_project(load_project(EXAMPLES / name))
- qtbot.waitUntil(lambda: canvas._ready, timeout=30000)
- qtbot.wait(1500) # let the first Plotly.react settle
- return canvas
-
-
-def _run_js(canvas, qtbot, script: str, timeout: int = 15000): # type: ignore[no-untyped-def]
- box: dict[str, object] = {}
- canvas._web.page().runJavaScript(script, lambda value: box.update(value=value))
- qtbot.waitUntil(lambda: "value" in box, timeout=timeout)
- return box["value"]
-
-
-@pytest.mark.gui
-def test_ordinary_mouse_movement_does_not_redraw(qtbot) -> None: # type: ignore[no-untyped-def]
- """The unfixed version restyled once per hover/unhover event."""
- canvas = _open_canvas(qtbot, "space_frame_3d.osmodel")
-
- restyles = _run_js(canvas, qtbot, _CHURN_PROBE)
-
- assert restyles == 0, f"mouse movement caused {restyles} plot restyles"
-
-
-@pytest.mark.gui
-def test_snap_marker_shows_and_clears(qtbot) -> None: # type: ignore[no-untyped-def]
- canvas = _open_canvas(qtbot, "basic_truss.osmodel")
- assert _run_js(canvas, qtbot, _HOVER_MARKER_X) == "[]"
-
- canvas.set_snap_preview_enabled(True)
- qtbot.wait(100)
- _run_js(canvas, qtbot, _EMIT_SNAP_HOVER)
- qtbot.wait(250)
- assert _run_js(canvas, qtbot, _HOVER_MARKER_X) != "[]", "snap target not shown"
-
- _run_js(canvas, qtbot, "document.getElementById('plot').emit('plotly_unhover',{})")
- qtbot.wait(250)
- assert _run_js(canvas, qtbot, _HOVER_MARKER_X) == "[]", "snap target not cleared"
diff --git a/tests/gui/test_plotly_view_preservation.py b/tests/gui/test_plotly_view_preservation.py
deleted file mode 100644
index 97ef3ee..0000000
--- a/tests/gui/test_plotly_view_preservation.py
+++ /dev/null
@@ -1,123 +0,0 @@
-"""Regression tests for the Plotly canvas push contract.
-
-``Plotly.react`` resets any scene attribute the incoming layout omits, so a
-data-only push must be flagged ``preserveView`` (the JS side then carries the
-live camera and axis ranges forward). Only explicit re-frames may send the
-computed framing. These tests capture the JavaScript the canvas emits instead
-of driving the browser, so they stay fast and deterministic.
-"""
-
-from __future__ import annotations
-
-import json
-from pathlib import Path
-
-import pytest
-
-pytest.importorskip("PySide6")
-pytest.importorskip("plotly")
-
-from otko.services import load_project
-
-EXAMPLES = Path(__file__).resolve().parents[2] / "examples"
-
-
-def _canvas_with_captured_js(qtbot): # type: ignore[no-untyped-def]
- from otko.views.canvas_plotly import PlotlyCanvas
-
- canvas = PlotlyCanvas()
- qtbot.addWidget(canvas)
- # Pretend the page finished loading, then capture instead of running JS.
- canvas._ready = True
- calls: list[str] = []
- canvas._eval = lambda js: calls.append(js) # type: ignore[method-assign]
- return canvas, calls
-
-
-def _parse_call(call: str) -> tuple[dict, bool]:
- """Split a captured ``window.otkoUpdate(, )`` call."""
- body = call[len("window.otkoUpdate(") : -1]
- literal, _sep, flag = body.rpartition(", ")
- return json.loads(json.loads(literal)), flag == "true"
-
-
-@pytest.mark.gui
-def test_framing_push_is_not_marked_preserve_view(qtbot) -> None: # type: ignore[no-untyped-def]
- canvas, calls = _canvas_with_captured_js(qtbot)
-
- canvas.show_project(load_project(EXAMPLES / "cantilever.osmodel"))
-
- assert calls, "show_project must push a figure"
- payload, preserve = _parse_call(calls[-1])
- assert preserve is False, "initial push must be a re-frame"
- # The framing push carries the camera and the padded ranges.
- scene = payload["layout"]["scene"]
- assert "camera" in scene
- assert "range" in scene["xaxis"]
- assert scene["xaxis"]["autorange"] is False
-
-
-@pytest.mark.gui
-def test_data_update_preserves_the_view(qtbot) -> None: # type: ignore[no-untyped-def]
- canvas, calls = _canvas_with_captured_js(qtbot)
-
- canvas.show_project(load_project(EXAMPLES / "cantilever.osmodel"))
- calls.clear()
-
- canvas.selection.select_node(1) # data-only update
-
- assert calls, "selection change must push"
- payload, preserve = _parse_call(calls[-1])
- assert preserve is True, "data update must preserve the view"
- assert "camera" not in payload["layout"]["scene"], "must not re-send the camera"
-
-
-@pytest.mark.gui
-def test_view_presets_and_reset_re_frame(qtbot) -> None: # type: ignore[no-untyped-def]
- canvas, calls = _canvas_with_captured_js(qtbot)
-
- canvas.show_project(load_project(EXAMPLES / "cantilever.osmodel"))
-
- for action in (canvas.view_xy, canvas.view_xz, canvas.view_yz, canvas.reset_camera):
- calls.clear()
- action()
- assert calls[-1].endswith(", false)"), f"{action.__name__} must re-frame"
-
-
-@pytest.mark.gui
-def test_style_change_keeps_the_view_but_updates_the_layout(qtbot) -> None: # type: ignore[no-untyped-def]
- """Re-colouring must not snap the camera back to the preset."""
- from otko.views.canvas3d.style import RenderStyle
-
- canvas, calls = _canvas_with_captured_js(qtbot)
- canvas.show_project(load_project(EXAMPLES / "cantilever.osmodel"))
- calls.clear()
-
- canvas.set_style(RenderStyle(show_axis_outline=True))
-
- assert calls, "a style change must push"
- payload, preserve = _parse_call(calls[-1])
- assert preserve is True, "colour changes must not re-frame the view"
- assert payload["layout"]["scene"]["xaxis"]["showgrid"] is True # layout applied
-
-
-@pytest.mark.gui
-def test_js_console_messages_reach_the_log(qtbot) -> None: # type: ignore[no-untyped-def]
- """A plotly failure inside the page must not be invisible from Python."""
- from PySide6.QtWebEngineCore import QWebEnginePage
-
- from otko.views.canvas_plotly.plotly_canvas import _LoggingPage
-
- messages: list[tuple[str, str]] = []
- page = _LoggingPage(lambda msg, severity="info": messages.append((severity, msg)))
-
- levels = QWebEnginePage.JavaScriptConsoleMessageLevel
- page.javaScriptConsoleMessage(levels.ErrorMessageLevel, "boom", 7, "file:///tmp/plot.html")
- page.javaScriptConsoleMessage(levels.WarningMessageLevel, "careful", 8, "file:///tmp/plot.html")
- page.javaScriptConsoleMessage(levels.InfoMessageLevel, "hello", 9, "")
-
- assert messages == [
- ("error", "[web] plot.html:7 boom"),
- ("warning", "[web] plot.html:8 careful"),
- ("info", "[web] plotly:9 hello"),
- ]
diff --git a/tests/gui/test_property_commands.py b/tests/gui/test_property_commands.py
index 8a49e26..117c445 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 (
+from otko.commands import ( # noqa: E402
AddElementsCommand,
AddMaterialsCommand,
AddNodesCommand,
@@ -16,7 +16,7 @@ from otko.commands import (
UpdateMaterialCommand,
UpdateSectionCommand,
)
-from otko.core import (
+from otko.core import ( # noqa: E402
ElasticBeamColumn,
ElasticSection,
Node,
@@ -24,40 +24,24 @@ from otko.core import (
Steel02,
TrussElement,
)
-from otko.viewmodels import ProjectViewModel
+from otko.viewmodels import ProjectViewModel # noqa: E402
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
@@ -107,7 +91,8 @@ 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
@@ -121,23 +106,14 @@ 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
@@ -150,15 +126,10 @@ 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
@@ -169,23 +140,13 @@ 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
@@ -196,15 +157,10 @@ 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 aed5ece..7a5d74c 100644
--- a/tests/gui/test_property_forms.py
+++ b/tests/gui/test_property_forms.py
@@ -10,10 +10,13 @@ import pytest
pytest.importorskip("PySide6")
-from otko.core import (
+from otko.core import ( # noqa: E402
+ Concrete01,
Concrete02,
ElasticIsotropic,
+ ElasticPP,
ElasticSection,
+ ElasticUniaxial,
Steel01,
Steel02,
)
@@ -34,7 +37,8 @@ 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()
@@ -46,15 +50,8 @@ 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)
@@ -78,14 +75,8 @@ 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)
@@ -97,14 +88,10 @@ 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 9aee331..09c03b6 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
-from otko.services.results import PushoverResults
-from otko.views.docks.pushover_curve import (
+from otko.core import UnitSystem # noqa: E402
+from otko.services.results import PushoverResults # noqa: E402
+from otko.views.docks.pushover_curve import ( # noqa: E402
PushoverCurveView,
_is_rotation_dof,
)
@@ -17,11 +17,8 @@ from otko.views.docks.pushover_curve import (
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]),
)
@@ -37,10 +34,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
@@ -99,7 +96,8 @@ 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 5281fd8..0e40752 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
+from PySide6.QtCore import QObject, Signal # noqa: E402
-from otko.core import LinearTimeSeries, PlainLoadPattern, TransientCase
-from otko.viewmodels import ProjectViewModel
-from otko.views.dialogs.run_analysis import RunAnalysisDialog
+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
class _FakeRunner(QObject):
@@ -41,18 +41,16 @@ 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 3f66eb4..449b24e 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 (
+from otko.core import ( # noqa: E402
ElasticBeamColumn,
ElasticSection,
Node,
@@ -18,25 +18,18 @@ 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),
],
@@ -46,7 +39,6 @@ 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())
@@ -66,7 +58,6 @@ 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 4747cec..9e68a75 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 (
+from otko.core import ( # noqa: E402
AggregatorDOF,
ElasticSection,
FiberSection,
RectangularPatch,
SectionAggregator,
)
-from otko.views.dialogs.section_forms import (
+from otko.views.dialogs.section_forms import ( # noqa: E402
FORM_REGISTRY,
form_for,
)
@@ -31,14 +31,7 @@ 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)
@@ -48,19 +41,11 @@ 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)
@@ -74,9 +59,7 @@ 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)
@@ -88,12 +71,10 @@ 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 365c03c..c795493 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
+from otko.views.canvas3d import SelectionState # noqa: E402
@pytest.mark.gui
diff --git a/tests/gui/test_transforms.py b/tests/gui/test_transforms.py
index fe13b70..120cc64 100644
--- a/tests/gui/test_transforms.py
+++ b/tests/gui/test_transforms.py
@@ -6,44 +6,34 @@ import pytest
pytest.importorskip("PySide6")
-from otko.commands import (
+from otko.commands import ( # noqa: E402
AddElementsCommand,
AddNodesCommand,
MirrorCommand,
MoveNodesCommand,
ReplicateCommand,
)
-from otko.core import Node, Steel01, TrussElement
-from otko.viewmodels import ProjectViewModel
+from otko.core import Node, Steel01, TrussElement # noqa: E402
+from otko.viewmodels import ProjectViewModel # noqa: E402
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
@@ -54,7 +44,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
@@ -78,9 +68,8 @@ 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
@@ -92,9 +81,8 @@ 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
@@ -106,7 +94,8 @@ 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 235ee7e..9b5e288 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.QtWidgets import QTreeWidget
+from PySide6.QtCore import Qt # noqa: E402
+from PySide6.QtWidgets import QTreeWidget # noqa: E402
-from otko.core import Node
+from otko.core import Node # noqa: E402
@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,17 +27,14 @@ 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 4eaaae1..b3c27e7 100644
--- a/tests/gui/test_units_status_bar.py
+++ b/tests/gui/test_units_status_bar.py
@@ -6,13 +6,12 @@ import pytest
pytest.importorskip("PySide6")
-from otko.core import UnitSystem
+from otko.core import UnitSystem # noqa: E402
@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))
@@ -21,7 +20,6 @@ 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()
@@ -34,7 +32,6 @@ 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()
@@ -49,7 +46,6 @@ 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 f83cd67..e684e5a 100644
--- a/tests/gui/test_update_element_fields.py
+++ b/tests/gui/test_update_element_fields.py
@@ -6,48 +6,33 @@ import pytest
pytest.importorskip("PySide6")
-from otko.commands import (
+from otko.commands import ( # noqa: E402
AddElementsCommand,
AddMaterialsCommand,
AddNodesCommand,
UpdateElementFieldsCommand,
)
-from otko.core import (
+from otko.core import ( # noqa: E402
ElasticUniaxial,
Node,
TrussElement,
)
-from otko.viewmodels import ProjectViewModel
+from otko.viewmodels import ProjectViewModel # noqa: E402
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
@@ -72,13 +57,9 @@ 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
@@ -87,13 +68,9 @@ 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 012c1c0..b0fc2c8 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 (
+from otko.core import ( # noqa: E402
CoordinateGridSystem,
GridSystem,
Project,
@@ -33,7 +33,6 @@ 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())
@@ -46,7 +45,6 @@ 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())
@@ -63,7 +61,6 @@ 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())
@@ -77,7 +74,6 @@ 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())
@@ -91,7 +87,6 @@ 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())
@@ -105,7 +100,6 @@ 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):
@@ -118,11 +112,10 @@ 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
@@ -133,7 +126,6 @@ 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())
@@ -141,7 +133,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)
@@ -170,7 +162,6 @@ 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 20be03c..db4b471 100644
--- a/tests/integration/test_basic_truss.py
+++ b/tests/integration/test_basic_truss.py
@@ -11,9 +11,7 @@ import pytest
pytest.importorskip("openseespy")
-from otko.services.opensees_runner import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
def test_basic_truss_matches_opensees_tcl_reference() -> None:
@@ -25,7 +23,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 IN_TO_M, build_basic_truss
+ from examples.basic_truss import build_basic_truss, IN_TO_M
proj = build_basic_truss()
runner = OpenSeesRunner(proj)
@@ -45,7 +43,6 @@ 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 4dc4a60..6ca31a5 100644
--- a/tests/integration/test_beam_quad_2d.py
+++ b/tests/integration/test_beam_quad_2d.py
@@ -9,10 +9,8 @@ import pytest
pytest.importorskip("openseespy")
-from otko.services import load_project, save_project
-from otko.services.opensees_runner import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services import load_project, save_project # noqa: E402
+from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
def test_beam_quad_2d_midspan_deflection(tmp_path) -> None: # type: ignore[no-untyped-def]
@@ -25,7 +23,6 @@ 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()
@@ -52,10 +49,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)
@@ -72,7 +69,6 @@ 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()
@@ -85,7 +81,6 @@ 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
deleted file mode 100644
index 8ba5ca7..0000000
--- a/tests/integration/test_combinations.py
+++ /dev/null
@@ -1,90 +0,0 @@
-"""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 4293dea..a3aa8e1 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 (
+from otko.core import ( # noqa: E402
Concrete04,
FiberSection,
ForceBeamColumn,
@@ -32,27 +32,24 @@ from otko.core import (
StaticCase,
UnitSystem,
)
-from otko.services.opensees_runner import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
-
+from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
# ── 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:
@@ -64,45 +61,32 @@ 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,
@@ -112,9 +96,7 @@ 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)),
],
@@ -122,8 +104,7 @@ 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,
@@ -158,14 +139,9 @@ 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 7e9e37e..087b461 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 (
+from otko.core import ( # noqa: E402
ElasticUniaxial,
LinearTimeSeries,
NodalLoad,
@@ -21,18 +21,17 @@ from otko.core import (
StaticCase,
TrussElement,
)
-from otko.services.opensees_runner import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
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)],
@@ -41,13 +40,10 @@ 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 83cd67e..5f76282 100644
--- a/tests/integration/test_eigen_two_storey_one_bay_frame.py
+++ b/tests/integration/test_eigen_two_storey_one_bay_frame.py
@@ -8,11 +8,9 @@ import pytest
pytest.importorskip("openseespy")
-from otko.core import ModalCase
-from otko.services import load_project, save_project
-from otko.services.opensees_runner import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+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
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 793f98d..c0cc4db 100644
--- a/tests/integration/test_eigen_two_storey_shear_frame.py
+++ b/tests/integration/test_eigen_two_storey_shear_frame.py
@@ -8,11 +8,9 @@ import pytest
pytest.importorskip("openseespy")
-from otko.core import ModalCase
-from otko.services import load_project, save_project
-from otko.services.opensees_runner import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+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
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 6f04163..54edec4 100644
--- a/tests/integration/test_elastic_frame.py
+++ b/tests/integration/test_elastic_frame.py
@@ -8,11 +8,9 @@ import pytest
pytest.importorskip("openseespy")
-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
+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
BASE_NODES = (1, 2, 3, 4)
@@ -29,17 +27,13 @@ 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 (
- LOAD_F1,
- LOAD_F2,
- LOAD_F3,
- N_BAYS,
- build_elastic_frame,
+ BAY, 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)
@@ -57,32 +51,24 @@ 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 (
- LOAD_F1,
- LOAD_F2,
- LOAD_F3,
- N_BAYS,
- P_F1,
- P_F2,
- P_F3,
- build_elastic_frame,
+ BAY, 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)
@@ -101,7 +87,6 @@ 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))
@@ -118,5 +103,7 @@ 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, strict=False), start=1):
- assert pytest.approx(T_ref, rel=0.02) == T, f"T{i} = {T:.4f} s, reference {T_ref:.4f} s"
+ 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"
+ )
diff --git a/tests/integration/test_ex1a_canti2d.py b/tests/integration/test_ex1a_canti2d.py
index a20d589..660810a 100644
--- a/tests/integration/test_ex1a_canti2d.py
+++ b/tests/integration/test_ex1a_canti2d.py
@@ -9,11 +9,9 @@ import pytest
pytest.importorskip("openseespy")
-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
+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
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 264d30c..246af57 100644
--- a/tests/integration/test_ex1a_canti2d_eq.py
+++ b/tests/integration/test_ex1a_canti2d_eq.py
@@ -9,10 +9,8 @@ import pytest
pytest.importorskip("openseespy")
-from otko.services import load_project, save_project
-from otko.services.opensees_runner import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services import load_project, save_project # noqa: E402
+from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
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 f47bdd2..8e3f2a4 100644
--- a/tests/integration/test_ex1b_portal2d.py
+++ b/tests/integration/test_ex1b_portal2d.py
@@ -9,11 +9,9 @@ import pytest
pytest.importorskip("openseespy")
-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
+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
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 71bfb6d..5a79a65 100644
--- a/tests/integration/test_ex2a_canti2d_elastic_element.py
+++ b/tests/integration/test_ex2a_canti2d_elastic_element.py
@@ -9,11 +9,9 @@ import pytest
pytest.importorskip("openseespy")
-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
+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
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 3766581..4afae36 100644
--- a/tests/integration/test_ex2b_canti2d_inelastic_section.py
+++ b/tests/integration/test_ex2b_canti2d_inelastic_section.py
@@ -9,11 +9,9 @@ import pytest
pytest.importorskip("openseespy")
-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
+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
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 1cae060..a9caf9c 100644
--- a/tests/integration/test_ex2c_canti2d_inelastic_fiber_section.py
+++ b/tests/integration/test_ex2c_canti2d_inelastic_fiber_section.py
@@ -9,11 +9,9 @@ import pytest
pytest.importorskip("openseespy")
-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
+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
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 2486519..273610d 100644
--- a/tests/integration/test_ex3_canti2d_variants.py
+++ b/tests/integration/test_ex3_canti2d_variants.py
@@ -9,11 +9,9 @@ import pytest
pytest.importorskip("openseespy")
-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
+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
def _reload(proj, tmp_path, stem: str): # type: ignore[no-untyped-def]
@@ -29,16 +27,10 @@ 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))
@@ -49,12 +41,8 @@ def test_ex3_variant_pushover_runs(
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:
@@ -66,10 +54,7 @@ def test_ex3_variant_pushover_runs(
[
("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 d4943cb..875942f 100644
--- a/tests/integration/test_ex4_portal2d_variants.py
+++ b/tests/integration/test_ex4_portal2d_variants.py
@@ -9,11 +9,9 @@ import pytest
pytest.importorskip("openseespy")
-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
+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
def _reload(proj, tmp_path, stem: str): # type: ignore[no-untyped-def]
@@ -29,16 +27,10 @@ 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))
@@ -53,12 +45,8 @@ def test_ex4_variant_pushover_runs(
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:
@@ -70,10 +58,7 @@ def test_ex4_variant_pushover_runs(
[
("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 6463b21..0711df4 100644
--- a/tests/integration/test_material_tester.py
+++ b/tests/integration/test_material_tester.py
@@ -34,9 +34,6 @@ from otko.services.material_tester import (
test_uniaxial_material,
)
-pytestmark = pytest.mark.slow
-
-
# ---- helpers ---------------------------------------------------------------
@@ -48,8 +45,7 @@ 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).
@@ -65,25 +61,17 @@ 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,
),
],
)
@@ -122,8 +110,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(
@@ -141,9 +129,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 -------------------------------------------------
@@ -159,9 +147,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(
@@ -174,28 +162,30 @@ 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}"
)
@@ -217,7 +207,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(
@@ -253,21 +243,23 @@ 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 ---------------------------------------------------
@@ -289,8 +281,12 @@ 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 -------------------------------------------------------
@@ -327,6 +323,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 12439d4..8d17046 100644
--- a/tests/integration/test_moment_curvature.py
+++ b/tests/integration/test_moment_curvature.py
@@ -8,11 +8,13 @@ behaviour is exercised by the Phase 9 pushover tests.
from __future__ import annotations
+import math
+
import pytest
pytest.importorskip("openseespy")
-from otko.core import (
+from otko.core import ( # noqa: E402
ElasticUniaxial,
FiberSection,
LinearTimeSeries,
@@ -24,9 +26,7 @@ from otko.core import (
StaticCase,
ZeroLengthSectionElement,
)
-from otko.services.opensees_runner import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
def _moment_curvature_project(moment: float) -> Project:
@@ -36,44 +36,32 @@ 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)],
)
@@ -84,16 +72,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:
@@ -106,39 +94,34 @@ 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, strict=False):
+ for kappa, moment in zip(result.control_disp, result.base_shear):
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)
@@ -162,7 +145,6 @@ def test_moment_curvature_with_constant_axial_preload() -> None:
Steel01,
StraightLayer,
)
-
colWidth = 15.0
colDepth = 24.0
cover = 1.5
@@ -171,104 +153,58 @@ 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"),
@@ -276,16 +212,14 @@ 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=[],
@@ -293,29 +227,25 @@ 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 7648e51..1c27358 100644
--- a/tests/integration/test_moment_curvature_example.py
+++ b/tests/integration/test_moment_curvature_example.py
@@ -6,30 +6,26 @@ import pytest
pytest.importorskip("openseespy")
-from otko.services import load_project
-from otko.services.opensees_runner import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services import load_project # noqa: E402
+from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
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)
@@ -43,14 +39,15 @@ 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 3c05c3e..f67c645 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 (
+from otko.core import ( # noqa: E402
ElasticBeamColumn,
ElasticSection,
LinearTimeSeries,
@@ -18,9 +18,7 @@ from otko.core import (
Project,
StaticCase,
)
-from otko.services import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services import OpenSeesRunner # noqa: E402
def _cantilever() -> Project:
diff --git a/tests/integration/test_rc_frame_earthquake.py b/tests/integration/test_rc_frame_earthquake.py
index 132535f..8f2a7a8 100644
--- a/tests/integration/test_rc_frame_earthquake.py
+++ b/tests/integration/test_rc_frame_earthquake.py
@@ -9,19 +9,17 @@ import pytest
pytest.importorskip("openseespy")
-from otko.services import load_project, save_project
-from otko.services.opensees_runner import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services import load_project, save_project # noqa: E402
+from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
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 (
- N_PTS,
build_rc_frame_earthquake,
+ DT,
+ N_PTS,
)
-
proj = build_rc_frame_earthquake()
proj.validate_references()
@@ -35,9 +33,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
@@ -52,4 +50,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"
+ assert max(abs(uy)) < 1.0, f"max |Uy| = {max(abs(uy)):.4f} in too large"
\ No newline at end of file
diff --git a/tests/integration/test_rc_frame_gravity.py b/tests/integration/test_rc_frame_gravity.py
index 0c91190..a213681 100644
--- a/tests/integration/test_rc_frame_gravity.py
+++ b/tests/integration/test_rc_frame_gravity.py
@@ -6,10 +6,8 @@ import pytest
pytest.importorskip("openseespy")
-from otko.services import load_project, save_project
-from otko.services.opensees_runner import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services import load_project, save_project # noqa: E402
+from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
def test_rc_frame_gravity_matches_opensees_reference(tmp_path) -> None: # type: ignore[no-untyped-def]
@@ -22,8 +20,7 @@ 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 P_LOAD, build_rc_frame_gravity
-
+ from examples.rc_frame_gravity import build_rc_frame_gravity, P_LOAD
proj = build_rc_frame_gravity()
proj.validate_references()
@@ -57,7 +54,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 5bd9ed0..d7d6df5 100644
--- a/tests/integration/test_rc_frame_pushover.py
+++ b/tests/integration/test_rc_frame_pushover.py
@@ -6,10 +6,8 @@ import pytest
pytest.importorskip("openseespy")
-from otko.services import load_project, save_project
-from otko.services.opensees_runner import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services import load_project, save_project # noqa: E402
+from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
def test_rc_frame_pushover_reaches_target_with_fallback(tmp_path) -> None: # type: ignore[no-untyped-def]
@@ -19,13 +17,11 @@ 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()
@@ -39,7 +35,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."
@@ -68,6 +64,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 33c352b..3565fb7 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 (
+from otko.core import ( # noqa: E402
ElasticUniaxial,
ImposedSupportMotionPattern,
Node,
@@ -33,10 +33,7 @@ from otko.core import (
UniformExcitationPattern,
ZeroLengthElement,
)
-from otko.services import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
-
+from otko.services import OpenSeesRunner # noqa: E402
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 bf2a843..753210b 100644
--- a/tests/integration/test_runner_modal.py
+++ b/tests/integration/test_runner_modal.py
@@ -15,18 +15,16 @@ import math
import pytest
-ops = pytest.importorskip("openseespy.opensees")
+ops = pytest.importorskip("openseespy.opensees") # noqa: F401
-from otko.core import (
+from otko.core import ( # noqa: E402
ElasticBeamColumn,
ElasticSection,
ModalCase,
Node,
Project,
)
-from otko.services import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services import OpenSeesRunner # noqa: E402
def test_sdof_pole_first_frequency_matches_kspring_over_m() -> None:
@@ -38,11 +36,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)],
@@ -56,9 +54,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:
@@ -66,11 +64,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 bd9cac1..da5aa9d 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 (
+from otko.core import ( # noqa: E402
ElasticBeamColumn,
ElasticSection,
LinearTimeSeries,
@@ -22,9 +22,7 @@ from otko.core import (
Project,
StaticCase,
)
-from otko.services import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services import OpenSeesRunner # noqa: E402
def test_cantilever_tip_deflection_matches_closed_form() -> None:
@@ -36,8 +34,7 @@ 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)),
@@ -47,8 +44,7 @@ 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))],
)
@@ -58,12 +54,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 a2be365..69c5f3b 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")
-h5py = pytest.importorskip("h5py")
+ops = pytest.importorskip("openseespy.opensees") # noqa: F401
+h5py = pytest.importorskip("h5py") # noqa: F401
-from otko.core import (
+from otko.core import ( # noqa: E402
ConstantTimeSeries,
ElasticBeamColumn,
ElasticSection,
@@ -27,9 +27,7 @@ from otko.core import (
StaticCase,
TransientCase,
)
-from otko.services import OpenSeesRunner
-
-pytestmark = pytest.mark.slow
+from otko.services import OpenSeesRunner # noqa: E402
def test_sdof_free_vibration_matches_cosine(tmp_path) -> None: # type: ignore[no-untyped-def]
@@ -51,29 +49,28 @@ 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))],
),
],
@@ -88,11 +85,8 @@ 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),
)
@@ -117,8 +111,7 @@ 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),
@@ -128,8 +121,7 @@ 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
deleted file mode 100644
index c495081..0000000
--- a/tests/services/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Service-layer tests: runner, persistence, export, results I/O."""
diff --git a/tests/tools/test_parse_schemas.py b/tests/tools/test_parse_schemas.py
index 506496c..cf9dc38 100644
--- a/tests/tools/test_parse_schemas.py
+++ b/tests/tools/test_parse_schemas.py
@@ -13,7 +13,6 @@ import json
from pathlib import Path
import pytest
-
from tools.gidopensees_import.parse_schemas import (
ParseError,
_parse_dependencies,
@@ -298,9 +297,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")
@@ -317,18 +316,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")
@@ -355,9 +354,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 5ea8311..ccec7eb 100644
--- a/tests/unit/core/materials/test_concrete04.py
+++ b/tests/unit/core/materials/test_concrete04.py
@@ -24,14 +24,9 @@ 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
@@ -39,15 +34,9 @@ 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)
@@ -94,14 +83,8 @@ 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,
)
@@ -118,14 +101,9 @@ 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)
@@ -136,7 +114,6 @@ 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
deleted file mode 100644
index 7b3f95c..0000000
--- a/tests/unit/test_architecture.py
+++ /dev/null
@@ -1,201 +0,0 @@
-"""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 backends are confined to the
-canvas packages (``views/canvas3d``, ``views/canvas_plotly``). 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_DIRS = (
- SRC / "views" / "canvas3d",
- SRC / "views" / "canvas_plotly",
-)
-
-# 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 the canvas packages 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_dirs: tuple[Path, ...] = (),
-) -> 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 any(_is_within(path, directory) for directory in exclude_dirs):
- 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 the canvas packages."""
- violations = _violations(SRC, VTK_ROOTS, exclude_dirs=CANVAS_DIRS)
- assert violations == [], f"backend imports outside the canvas packages: {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
deleted file mode 100644
index 063f9b3..0000000
--- a/tests/unit/test_ci_config.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""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
deleted file mode 100644
index 6a5133a..0000000
--- a/tests/unit/test_combinations.py
+++ /dev/null
@@ -1,180 +0,0 @@
-"""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 425606b..a1cd1d1 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
+import pyvista as pv # noqa: E402
-from otko.core import (
+from otko.core import ( # noqa: E402
ElasticBeamColumn,
ElasticSection,
Node,
Project,
)
-from otko.services.element_forces import (
+from otko.services.element_forces import ( # noqa: E402
DiagramData,
ForceComponent,
extract_diagram_data,
)
-from otko.services.results import StaticResults
-from otko.views.canvas3d.diagram_renderer import DiagramRenderer
+from otko.services.results import StaticResults # noqa: E402
+from otko.views.canvas3d.diagram_renderer import DiagramRenderer # noqa: E402
pv.OFF_SCREEN = True
@@ -59,9 +59,12 @@ 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]
@@ -90,17 +93,14 @@ 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,9 +108,7 @@ 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
deleted file mode 100644
index f8c78e0..0000000
--- a/tests/unit/test_docs.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""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/services/test_element_forces.py b/tests/unit/test_element_forces.py
similarity index 90%
rename from tests/services/test_element_forces.py
rename to tests/unit/test_element_forces.py
index 2f57387..3fc8dd8 100644
--- a/tests/services/test_element_forces.py
+++ b/tests/unit/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,10 +58,8 @@ 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
@@ -88,20 +86,22 @@ 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,10 +112,8 @@ 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.
@@ -129,9 +127,7 @@ 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
@@ -142,10 +138,8 @@ 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)),
@@ -169,9 +163,7 @@ 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)
@@ -186,9 +178,7 @@ 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_elements.py b/tests/unit/test_elements.py
index e3c971a..11987c6 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 (
- ElasticBeamColumn,
Element,
+ ElasticBeamColumn,
ForceBeamColumn,
TrussElement,
ZeroLengthElement,
diff --git a/tests/services/test_export.py b/tests/unit/test_export.py
similarity index 100%
rename from tests/services/test_export.py
rename to tests/unit/test_export.py
diff --git a/tests/unit/test_formatting.py b/tests/unit/test_formatting.py
deleted file mode 100644
index bc1c6db..0000000
--- a/tests/unit/test_formatting.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""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 6688dce..5461d9b 100644
--- a/tests/unit/test_grid_system.py
+++ b/tests/unit/test_grid_system.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+import math
from pathlib import Path
import pytest
@@ -85,7 +86,6 @@ 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,13 +102,12 @@ 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)
@@ -117,13 +116,10 @@ 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))
@@ -173,7 +169,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, strict=False):
+ for a, b in zip(p_local, p_back):
assert a == pytest.approx(b, abs=1e-9)
@@ -209,8 +205,7 @@ 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/services/test_imposed_support_motion.py b/tests/unit/test_imposed_support_motion.py
similarity index 100%
rename from tests/services/test_imposed_support_motion.py
rename to tests/unit/test_imposed_support_motion.py
diff --git a/tests/unit/test_materials.py b/tests/unit/test_materials.py
index 43bde52..36086a5 100644
--- a/tests/unit/test_materials.py
+++ b/tests/unit/test_materials.py
@@ -37,14 +37,8 @@ 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.
@@ -55,14 +49,8 @@ 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},
)
@@ -78,8 +66,7 @@ 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
deleted file mode 100644
index 67ee070..0000000
--- a/tests/unit/test_meta_gates.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""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 a2e9c74..fbfbe22 100644
--- a/tests/unit/test_node.py
+++ b/tests/unit/test_node.py
@@ -19,9 +19,8 @@ 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
deleted file mode 100644
index 7b16ea4..0000000
--- a/tests/unit/test_packaging.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""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/services/test_peer_record.py b/tests/unit/test_peer_record.py
similarity index 100%
rename from tests/services/test_peer_record.py
rename to tests/unit/test_peer_record.py
diff --git a/tests/services/test_persistence.py b/tests/unit/test_persistence.py
similarity index 90%
rename from tests/services/test_persistence.py
rename to tests/unit/test_persistence.py
index 93319ff..7416697 100644
--- a/tests/services/test_persistence.py
+++ b/tests/unit/test_persistence.py
@@ -27,8 +27,7 @@ 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)),
@@ -36,14 +35,8 @@ 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)],
@@ -54,8 +47,7 @@ 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_phase8a.py b/tests/unit/test_phase8a.py
index c9a3842..033ef00 100644
--- a/tests/unit/test_phase8a.py
+++ b/tests/unit/test_phase8a.py
@@ -8,6 +8,7 @@
from __future__ import annotations
+import numpy as np
import pytest
from otko.commands.nodes import SetMassCommand
@@ -66,8 +67,7 @@ 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,13 +77,10 @@ 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)
@@ -124,7 +121,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
@@ -139,21 +136,16 @@ 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)
@@ -173,20 +165,16 @@ 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)
@@ -199,13 +187,14 @@ 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)
@@ -228,7 +217,6 @@ class _RecordingOps:
def rec(*args, **kwargs): # type: ignore[no-untyped-def]
self.calls.append((name, args, kwargs))
return None
-
return rec
@@ -236,10 +224,8 @@ 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")],
@@ -256,10 +242,7 @@ 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)
@@ -270,11 +253,7 @@ 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
@@ -294,13 +273,14 @@ 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 41d50fb..c398b3f 100644
--- a/tests/unit/test_phase8b.py
+++ b/tests/unit/test_phase8b.py
@@ -29,61 +29,33 @@ 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
@@ -92,15 +64,10 @@ 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
@@ -110,72 +77,55 @@ 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)
@@ -195,29 +145,21 @@ 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 557ed34..c49f764 100644
--- a/tests/unit/test_phase8c.py
+++ b/tests/unit/test_phase8c.py
@@ -27,8 +27,7 @@ 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,
@@ -40,8 +39,7 @@ 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],
)
@@ -49,8 +47,7 @@ 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],
)
@@ -58,8 +55,7 @@ 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],
)
@@ -67,18 +63,16 @@ 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
@@ -94,26 +88,29 @@ 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
@@ -127,7 +124,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)
@@ -147,8 +144,10 @@ 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)
@@ -158,9 +157,12 @@ 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)
@@ -169,29 +171,21 @@ 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 24b38b9..1b6413e 100644
--- a/tests/unit/test_phase9.py
+++ b/tests/unit/test_phase9.py
@@ -10,7 +10,9 @@ import pytest
from otko.core import (
AggregatorDOF,
CircularPatch,
+ ElasticSection,
FiberSection,
+ Fibre,
Project,
RectangularPatch,
SectionAggregator,
@@ -27,17 +29,10 @@ 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)
@@ -48,17 +43,10 @@ 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)
@@ -70,18 +58,13 @@ 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)
@@ -92,17 +75,11 @@ 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)
@@ -114,8 +91,7 @@ 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")],
)
@@ -126,78 +102,50 @@ 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 ElasticBeamColumn, Node
-
+ from otko.core import Node, ElasticBeamColumn
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_plotly_trace_builder.py b/tests/unit/test_plotly_trace_builder.py
deleted file mode 100644
index 45b2b21..0000000
--- a/tests/unit/test_plotly_trace_builder.py
+++ /dev/null
@@ -1,262 +0,0 @@
-"""Unit tests for the pure Plotly trace builder (no Qt, no plotly import)."""
-
-from __future__ import annotations
-
-import json
-from pathlib import Path
-
-import numpy as np
-import pytest
-
-from otko.services import load_project
-from otko.views.canvas3d.style import RenderStyle
-from otko.views.canvas_plotly.trace_builder import (
- PlotlyTraceBuilder,
- SceneOptions,
-)
-
-EXAMPLES = Path(__file__).resolve().parents[2] / "examples"
-
-
-def _load(name: str): # type: ignore[no-untyped-def]
- return load_project(EXAMPLES / f"{name}.osmodel")
-
-
-def _traces(scene, name: str) -> list[dict]: # type: ignore[no-untyped-def]
- return [trace for trace in scene.data if trace.get("name") == name]
-
-
-def _kinds(scene) -> list[str]: # type: ignore[no-untyped-def]
- return [trace.get("meta", {}).get("kind", trace["type"]) for trace in scene.data]
-
-
-def test_builds_grid_nodes_and_frames() -> None:
- scene = PlotlyTraceBuilder().build(_load("basic_truss"), SceneOptions())
- names = {trace.get("name") for trace in scene.data}
- assert "grid-active" in names
- assert "nodes" in names
- assert "elements" in names
- assert scene.diagonal > 0
- # The hover-snap marker is always present so JS can restyle it.
- assert scene.data[scene.hover_trace]["meta"]["kind"] == "hover"
-
-
-def test_nodes_carry_ids_as_customdata() -> None:
- project = _load("basic_truss")
- scene = PlotlyTraceBuilder().build(project, SceneOptions())
- (nodes,) = _traces(scene, "nodes")
- assert nodes["meta"]["kind"] == "node"
- assert nodes["customdata"] == [node.id for node in project.nodes]
- assert len(nodes["marker"]["color"]) == len(project.nodes)
-
-
-def _color_for_index(trace: dict, index: float) -> str:
- """Map a ``line.color`` scalar index to its colour via the colorscale."""
- scale = sorted(trace["line"]["colorscale"], key=lambda item: item[0])
- return scale[int(index)][1]
-
-
-def test_frames_are_one_trace_with_per_element_colours() -> None:
- """A single line trace carries the palette through its ``line.color`` array."""
- project = _load("cantilever")
- style = RenderStyle()
- scene = PlotlyTraceBuilder(style).build(
- project, SceneOptions(selection_elements=frozenset({2}))
- )
- frames = _traces(scene, "elements")
- assert len(frames) == 1, "selection must not split the frame trace"
- line = frames[0]["line"]
- assert isinstance(line["color"], list)
- assert len(line["color"]) == len(frames[0]["x"])
- assert line["colorscale"]
-
- colors = line["color"]
- customdata = frames[0]["customdata"]
-
- def index_of(element_id: int) -> set[float]:
- return {colors[k] for k, value in enumerate(customdata) if value == element_id}
-
- selected = index_of(2)
- unselected = index_of(1)
- assert len(selected) == 1 and len(unselected) == 1
- assert selected != unselected
- assert _color_for_index(frames[0], next(iter(selected))) == style.selected_color
- assert _color_for_index(frames[0], next(iter(unselected))) == style.element_beam_color
-
-
-def test_element_palette_distinguishes_beam_from_truss() -> None:
- style = RenderStyle()
- builder = PlotlyTraceBuilder(style)
- beam = _traces(builder.build(_load("cantilever"), SceneOptions()), "elements")[0]
- truss = _traces(builder.build(_load("basic_truss"), SceneOptions()), "elements")[0]
- assert _color_for_index(beam, beam["line"]["color"][0]) == style.element_beam_color
- assert _color_for_index(truss, truss["line"]["color"][0]) == style.element_truss_color
-
-
-def test_selected_node_is_highlighted_by_colour() -> None:
- project = _load("basic_truss")
- target = project.nodes[1].id
- scene = PlotlyTraceBuilder().build(project, SceneOptions(selection_nodes=frozenset({target})))
- (nodes,) = _traces(scene, "nodes")
- ids = nodes["customdata"]
- assert nodes["marker"]["color"][ids.index(target)] != nodes["marker"]["color"][0]
-
-
-def test_working_plane_filters_grid_segments() -> None:
- project = _load("basic_truss")
- builder = PlotlyTraceBuilder()
- iso = builder.build(project, SceneOptions())
- plane = builder.build(project, SceneOptions(working_plane=("XY", 0.0)))
- (iso_grid,) = _traces(iso, "grid-active")
- (plane_grid,) = _traces(plane, "grid-active")
- assert len(plane_grid["x"]) <= len(iso_grid["x"])
-
-
-def test_snap_targets_carry_world_coordinates() -> None:
- scene = PlotlyTraceBuilder().build(_load("basic_truss"), SceneOptions())
- (snap,) = _traces(scene, "snap")
- assert snap["meta"]["kind"] == "snap"
- assert snap["customdata"], "expected at least one snappable intersection"
- assert all(len(point) == 3 for point in snap["customdata"])
- assert all(isinstance(coord, float) for point in snap["customdata"] for coord in point)
-
-
-def test_extrusions_add_a_mesh3d_trace() -> None:
- project = _load("cantilever")
- scene = PlotlyTraceBuilder().build(project, SceneOptions(show_extrusions=True))
- (mesh,) = _traces(scene, "extrusions")
- assert mesh["type"] == "mesh3d"
- assert mesh["i"] and mesh["j"] and mesh["k"]
- assert max(mesh["i"]) < len(mesh["x"])
-
-
-def test_local_axes_add_coloured_cones() -> None:
- project = _load("cantilever")
- scene = PlotlyTraceBuilder().build(project, SceneOptions(show_local_axes=True))
- names = {trace.get("name") for trace in scene.data}
- assert {"local-x", "local-y", "local-z"} <= names
- for trace in scene.data:
- if trace.get("name", "").startswith("local-"):
- assert trace["type"] == "cone"
- assert len(trace["u"]) == len(trace["x"]) > 0
-
-
-def test_labels_traces_are_text_only() -> None:
- project = _load("cantilever")
- scene = PlotlyTraceBuilder().build(
- project, SceneOptions(show_node_labels=True, show_element_labels=True)
- )
- (node_labels,) = _traces(scene, "node-labels")
- (element_labels,) = _traces(scene, "element-labels")
- assert node_labels["mode"] == "text"
- assert len(node_labels["text"]) == len(project.nodes)
- assert element_labels["mode"] == "text"
-
-
-def test_deformation_shifts_node_coordinates() -> None:
- project = _load("cantilever")
-
- class _Shift:
- def shifted(self, points: np.ndarray, node_ids: list[int]) -> np.ndarray:
- out = points.copy()
- out[:, 2] += 1.0
- return out
-
- base = PlotlyTraceBuilder().build(project, SceneOptions())
- moved = PlotlyTraceBuilder().build(project, SceneOptions(deformation=_Shift()))
- (base_nodes,) = _traces(base, "nodes")
- (moved_nodes,) = _traces(moved, "nodes")
- assert moved_nodes["z"] == pytest.approx([z + 1.0 for z in base_nodes["z"]])
-
-
-def test_payload_is_json_serialisable() -> None:
- scene = PlotlyTraceBuilder().build(
- _load("cantilever"),
- SceneOptions(
- selection_nodes=frozenset({1}),
- selection_elements=frozenset({1}),
- working_plane=("XY", 0.0),
- show_extrusions=True,
- show_local_axes=True,
- show_node_labels=True,
- show_element_labels=True,
- ),
- )
- payload = json.dumps(scene.to_payload())
- assert '"data"' in payload and '"layout"' in payload
- assert scene.layout["scene"]["aspectmode"] == "data"
-
-
-def test_empty_project_yields_only_the_hover_marker() -> None:
- scene = PlotlyTraceBuilder().build(None, SceneOptions())
- assert scene.data == []
- assert scene.hover_trace == -1
-
- from otko.core import Project
-
- empty = PlotlyTraceBuilder().build(Project(ndm=3, ndf=6), SceneOptions())
- assert _kinds(empty) == ["hover"]
-
-
-def test_frame_trace_meta_marks_elements_pickable() -> None:
- project = _load("cantilever")
- scene = PlotlyTraceBuilder().build(project, SceneOptions())
- (frames,) = _traces(scene, "elements")
- assert frames["meta"]["kind"] == "element"
- # None separators break the line into per-element segments.
- assert None in frames["x"]
- assert len(frames["customdata"]) == len(frames["x"])
-
-
-# ── scene recipe (opstool-derived framing) ───────────────────────────────
-def test_axis_bounds_are_padded_around_the_model() -> None:
- project = _load("cantilever")
- scene = PlotlyTraceBuilder().build(project, SceneOptions())
- points = np.array([node.coords for node in project.nodes], dtype=float)
- for index, axis in enumerate(("x", "y", "z")):
- low, high = scene.axis_bounds[axis]
- assert low < points[:, index].min() or low <= points[:, index].min()
- assert high > points[:, index].max() or high >= points[:, index].max()
- assert scene.diagonal > 0
-
-
-def test_planar_model_gets_unit_slack_on_the_flat_axis() -> None:
- project = _load("basic_truss")
- scene = PlotlyTraceBuilder().build(project, SceneOptions())
- low, high = scene.axis_bounds["z"]
- assert high - low > 0 # a degenerate axis must not collapse the view
-
-
-def test_axis_overrides_pin_ranges_with_autorange_off() -> None:
- scene = PlotlyTraceBuilder().build(_load("cantilever"), SceneOptions())
- overrides = scene.axis_overrides()
- assert set(overrides) == {"xaxis", "yaxis", "zaxis"}
- for axis, override in overrides.items():
- assert override["autorange"] is False
- assert len(override["range"]) == 2
- assert override["range"] == list(scene.axis_bounds[axis[0]])
-
-
-def test_hover_templates_identify_entities() -> None:
- scene = PlotlyTraceBuilder().build(_load("basic_truss"), SceneOptions())
- (nodes,) = _traces(scene, "nodes")
- (frames,) = _traces(scene, "elements")
- assert nodes["hovertemplate"] == "Node #%{customdata}"
- assert frames["hovertemplate"] == "Element #%{customdata}"
- # Hover must not fall back to the raw-id "skip" mode.
- assert "hoverinfo" not in nodes
-
-
-def test_axis_outline_flag_toggles_grid_and_ticks() -> None:
- plain = RenderStyle()
- outlined = RenderStyle(show_axis_outline=True)
- project = _load("cantilever")
- scene_plain = PlotlyTraceBuilder(plain).build(project, SceneOptions())
- scene_outlined = PlotlyTraceBuilder(outlined).build(project, SceneOptions())
-
- off_axis = scene_plain.layout["scene"]["xaxis"]
- on_axis = scene_outlined.layout["scene"]["xaxis"]
- assert off_axis["showgrid"] is False and off_axis["showticklabels"] is False
- assert on_axis["showgrid"] is True and on_axis["showticklabels"] is True
- # The coloured axis lines stay visible either way (orientation cue).
- assert off_axis["visible"] is True and on_axis["visible"] is True
diff --git a/tests/unit/test_project.py b/tests/unit/test_project.py
index fe36fd5..6df0846 100644
--- a/tests/unit/test_project.py
+++ b/tests/unit/test_project.py
@@ -126,8 +126,7 @@ 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))],
)
],
@@ -144,13 +143,8 @@ 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,
)
],
)
@@ -168,13 +162,8 @@ 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,
)
],
)
@@ -190,8 +179,7 @@ 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")],
),
],
@@ -206,8 +194,7 @@ 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")],
),
],
@@ -227,30 +214,19 @@ 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")],
),
],
@@ -261,8 +237,7 @@ 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)),
@@ -278,8 +253,7 @@ 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_render_style.py b/tests/unit/test_render_style.py
deleted file mode 100644
index c179eb7..0000000
--- a/tests/unit/test_render_style.py
+++ /dev/null
@@ -1,117 +0,0 @@
-"""Unit tests for the shared render style and its editable subset."""
-
-from __future__ import annotations
-
-import dataclasses
-
-import pytest
-
-from otko.core import ElasticBeamColumn, Node, Project, TrussElement, ZeroLengthElement
-from otko.views.canvas3d.style import (
- EDITABLE_FIELDS,
- SELECTED_STATE,
- STYLE_FIELDS,
- RenderStyle,
- element_family_index,
- family_palette,
-)
-
-
-def test_defaults_are_the_documented_palette() -> None:
- style = RenderStyle()
- assert style.node_color == "#d9d9d9"
- assert style.selected_color == "#00ffff"
- # opstool-derived element palette.
- assert style.element_beam_color == "#0652ff"
- assert style.element_truss_color == "#FF8C00"
- assert style.element_link_color == "#39FF14"
- assert len(style.response_scale_colors) == 10
- assert style.response_scale_colors[0] == "#313695" # cold end
- assert style.response_scale_colors[-1] == "#a50026" # warm end
-
-
-def test_with_updates_is_immutable() -> None:
- original = RenderStyle()
- updated = original.with_updates(node_color="#123456", label_font_size=20)
- assert updated is not original
- assert updated.node_color == "#123456"
- assert updated.label_font_size == 20
- # The source instance is untouched (frozen dataclass contract).
- assert original.node_color == "#d9d9d9"
- assert original.label_font_size == 12
-
-
-def test_editable_subset_matches_the_field_table() -> None:
- style = RenderStyle()
- assert RenderStyle.EDITABLE == EDITABLE_FIELDS
- assert set(style.editable_values()) == set(EDITABLE_FIELDS)
- # Every advertised field must exist on the dataclass with the right kind.
- kinds = {name: kind for name, _label, kind in STYLE_FIELDS}
- for name in EDITABLE_FIELDS:
- assert hasattr(style, name), name
- assert kinds[name] in {"color", "float", "int", "bool"}
-
-
-def test_response_colorscale_is_an_evenly_spaced_mapping() -> None:
- style = RenderStyle()
- scale = style.response_colorscale()
- assert len(scale) == len(style.response_scale_colors)
- assert scale[0] == (0.0, style.response_scale_colors[0])
- assert scale[-1] == (1.0, style.response_scale_colors[-1])
- positions = [position for position, _color in scale]
- assert positions == sorted(positions)
-
-
-@pytest.mark.parametrize(
- ("element", "expected"),
- [
- (ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1), 0),
- (TrussElement(id=2, nodes=(1, 2), area=0.01, material_id=1), 1),
- (ZeroLengthElement(id=3, nodes=(1, 1), material_ids=(1,), dofs=(1,)), 2),
- ],
-)
-def test_element_family_index(element: object, expected: int) -> None:
- assert element_family_index(element) == expected
-
-
-def test_family_palette_order_and_selected_slot() -> None:
- style = RenderStyle(node_color="#010101")
- palette = family_palette(style)
- assert palette == [
- style.element_beam_color,
- style.element_truss_color,
- style.element_link_color,
- style.selected_color,
- ]
- assert len(palette) - 1 == SELECTED_STATE
-
-
-def test_editable_values_round_trip_through_replace() -> None:
- style = RenderStyle()
- values = style.editable_values()
- assert RenderStyle(**values) == style
- # And the persisted shape is JSON-friendly (str/int/float only).
- import json
-
- assert json.loads(json.dumps(values)) == values
-
-
-def test_style_is_a_frozen_slots_dataclass() -> None:
- assert dataclasses.is_dataclass(RenderStyle)
- with pytest.raises(dataclasses.FrozenInstanceError):
- RenderStyle().node_color = "#000000" # type: ignore[misc]
-
-
-def test_project_builder_helper_is_unused_but_valid() -> None:
- """A style constructed from a project's elements classifies every family."""
- project = Project(
- ndm=2,
- ndf=3,
- nodes=[Node(id=1, coords=(0, 0, 0)), Node(id=2, coords=(1, 0, 0))],
- elements=[
- ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1),
- TrussElement(id=2, nodes=(1, 2), area=0.01, material_id=1),
- ],
- )
- indices = [element_family_index(el) for el in project.elements]
- assert indices == [0, 1]
diff --git a/tests/unit/test_renderer.py b/tests/unit/test_renderer.py
index 04529d6..e1c8fb0 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
+import numpy as np # noqa: E402
-from otko.core import (
+from otko.core import ( # noqa: E402
ElasticBeamColumn,
ElasticSection,
LinearTimeSeries,
@@ -18,13 +18,12 @@ from otko.core import (
Steel01,
TrussElement,
)
-from otko.services.deformation import DeformationSource
-from otko.views.canvas3d.model_renderer import (
+from otko.services.deformation import DeformationSource # noqa: E402
+from otko.views.canvas3d.model_renderer import ( # noqa: E402
ModelRenderer,
RendererMode,
_classify_support,
)
-from otko.views.canvas3d.style import SELECTED_STATE
# ──────────────────────────── support classification ────────────────────────────
@@ -126,13 +125,9 @@ def test_update_selection_writes_state_array(offscreen_plotter, small_3d_project
frame_states = np.asarray(r._frame_pd.cell_data["_oss_state"]).tolist()
# Nodes 1 and 3 selected → row 0 and row 2
assert node_states == [1, 0, 1]
- # Frame cells carry the palette slot: the element family normally, and
- # SELECTED_STATE when selected.
+ # Element 2 selected → it's the second frame (index 1 in frame_ids_ordered)
selected_frame_idx = r._frame_id_to_row[2]
- assert frame_states[selected_frame_idx] == SELECTED_STATE
- for index, family in enumerate(r._frame_family):
- expected = SELECTED_STATE if index == selected_frame_idx else family
- assert frame_states[index] == expected
+ assert frame_states[selected_frame_idx] == 1
def test_clear_selection(offscreen_plotter, small_3d_project) -> None: # type: ignore[no-untyped-def]
diff --git a/tests/services/test_runner_translation.py b/tests/unit/test_runner_translation.py
similarity index 89%
rename from tests/services/test_runner_translation.py
rename to tests/unit/test_runner_translation.py
index 1903183..c4d4e15 100644
--- a/tests/services/test_runner_translation.py
+++ b/tests/unit/test_runner_translation.py
@@ -23,6 +23,7 @@ from otko.core import (
Node,
PlainLoadPattern,
Project,
+ StaticCase,
Steel01,
TrussElement,
ZeroLengthElement,
@@ -33,8 +34,7 @@ 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,8 +48,7 @@ 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))],
)
],
@@ -58,8 +57,7 @@ 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)),
@@ -74,35 +72,27 @@ 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,
),
],
)
@@ -110,8 +100,7 @@ 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)),
@@ -221,18 +210,9 @@ 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:
@@ -240,13 +220,7 @@ 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,
)
@@ -260,18 +234,24 @@ 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/services/test_section_bbox.py b/tests/unit/test_section_bbox.py
similarity index 65%
rename from tests/services/test_section_bbox.py
rename to tests/unit/test_section_bbox.py
index 25f6b24..68ee8cd 100644
--- a/tests/services/test_section_bbox.py
+++ b/tests/unit/test_section_bbox.py
@@ -10,6 +10,7 @@ from otko.core import (
CircularPatch,
ElasticSection,
FiberSection,
+ Project,
RectangularPatch,
StraightLayer,
)
@@ -23,17 +24,11 @@ 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
@@ -46,17 +41,10 @@ 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
@@ -68,16 +56,11 @@ 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
@@ -89,28 +72,15 @@ 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/services/test_transient_and_export.py b/tests/unit/test_transient_and_export.py
similarity index 80%
rename from tests/services/test_transient_and_export.py
rename to tests/unit/test_transient_and_export.py
index 31b9199..30985b1 100644
--- a/tests/services/test_transient_and_export.py
+++ b/tests/unit/test_transient_and_export.py
@@ -22,15 +22,20 @@ 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))
@@ -47,7 +52,8 @@ 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:
@@ -101,7 +107,8 @@ 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/unit/test_unit_labels.py b/tests/unit/test_unit_labels.py
index 999f0e4..1fe1e3e 100644
--- a/tests/unit/test_unit_labels.py
+++ b/tests/unit/test_unit_labels.py
@@ -2,6 +2,8 @@
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 69ef334..72d3409 100644
--- a/tests/unit/test_zerolength_section.py
+++ b/tests/unit/test_zerolength_section.py
@@ -3,7 +3,6 @@
from __future__ import annotations
import pytest
-from pydantic import ValidationError
from otko.core import (
ElasticSection,
@@ -22,11 +21,9 @@ def test_zero_length_section_schema_defaults() -> None:
def test_zero_length_section_rejects_extra_fields() -> None:
- with pytest.raises(ValidationError):
+ with pytest.raises(Exception):
ZeroLengthSectionElement(
- id=1,
- nodes=(1, 2),
- section_id=1,
+ id=1, nodes=(1, 2), section_id=1,
bogus="not allowed", # type: ignore[call-arg]
)
@@ -34,24 +31,17 @@ 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)),
- ],
- sections=[
- ElasticSection(
- id=1,
- name="Box",
- E=200e9,
- A=0.01,
- Iz=1e-5,
- Iy=1e-5,
- G=80e9,
- J=1e-6,
- )
+ 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,
+ )],
elements=[
ZeroLengthSectionElement(id=10, nodes=(1, 2), section_id=1),
],