Compare commits

..
Author SHA1 Message Date
d01a5957b7 feat: consolidate units to Metric/Imperial with unit-aware dialogs
Some checks failed
CI / lint (push) Has been cancelled
CI / test (macos-latest, 3.10) (push) Has been cancelled
CI / test (macos-latest, 3.11) (push) Has been cancelled
CI / test (macos-latest, 3.12) (push) Has been cancelled
CI / test (ubuntu-latest, 3.10) (push) Has been cancelled
CI / test (ubuntu-latest, 3.11) (push) Has been cancelled
CI / test (ubuntu-latest, 3.12) (push) Has been cancelled
CI / test (windows-latest, 3.10) (push) Has been cancelled
CI / test (windows-latest, 3.11) (push) Has been cancelled
CI / test (windows-latest, 3.12) (push) Has been cancelled
- core/units: two dominant systems (Metric m/kN, Imperial ft/kip) with
  display conversion helpers, legacy 4-system migration in ProjectMeta
- dialogs/docks: unit-aware material, section, case, load, grid and
  results labels; diagram renderer unit labels; render controls update
- docs: add consistent_units.md; regen examples/*.osmodel artifacts
- tests: update persistence/phase8/project/unit-labels for new systems
2026-09-08 16:33:30 -04:00
13bb5d1d91 feat: show default node/element names in table
Some checks failed
CI / lint (push) Has been cancelled
CI / test (macos-latest, 3.10) (push) Has been cancelled
CI / test (macos-latest, 3.11) (push) Has been cancelled
CI / test (macos-latest, 3.12) (push) Has been cancelled
CI / test (ubuntu-latest, 3.10) (push) Has been cancelled
CI / test (ubuntu-latest, 3.11) (push) Has been cancelled
CI / test (ubuntu-latest, 3.12) (push) Has been cancelled
CI / test (windows-latest, 3.10) (push) Has been cancelled
CI / test (windows-latest, 3.11) (push) Has been cancelled
CI / test (windows-latest, 3.12) (push) Has been cancelled
2026-09-08 03:41:10 -04:00
8f1aae4370 feat: add distributed-load icon and toolbar entry
Some checks failed
CI / lint (push) Has been cancelled
CI / test (macos-latest, 3.10) (push) Has been cancelled
CI / test (macos-latest, 3.11) (push) Has been cancelled
CI / test (macos-latest, 3.12) (push) Has been cancelled
CI / test (ubuntu-latest, 3.10) (push) Has been cancelled
CI / test (ubuntu-latest, 3.11) (push) Has been cancelled
CI / test (ubuntu-latest, 3.12) (push) Has been cancelled
CI / test (windows-latest, 3.10) (push) Has been cancelled
CI / test (windows-latest, 3.11) (push) Has been cancelled
CI / test (windows-latest, 3.12) (push) Has been cancelled
2026-09-08 03:16:14 -04:00
af78e564c9 docs: direct feature work to develop branch in AGENTS.md 2026-09-08 03:16:14 -04:00
372 changed files with 10902 additions and 16507 deletions

View file

@ -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"
- 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"

4
.gitignore vendored
View file

@ -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

View file

@ -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

View file

@ -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)."
}
]
}

View file

@ -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,13 +19,15 @@ 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).
Runner emits OpenSeesPy commands in fixed order (`docs/architecture.md`): `wipe → model → node → fix → material → section → geomTransf → element → timeSeries → pattern/load → recorder → system/numberer/... → analyze`. Never reorder.
## Branches
- Do all feature/fix work on `develop` (or short-lived branches off `develop`, merged back into it). `main` is release-only — never commit directly to it.
## Verify (in this order)
```bash
@ -39,36 +41,8 @@ Focused runs: `pytest tests/unit` (pure logic, ms), `pytest tests/gui -k <name>`
Notes: `tests/conftest.py` auto-`ops.wipe()`s the OpenSees domain between tests (lazy import so core tests stay Qt/OpenSees-free). Coverage omits `views/`. Commit style: Conventional Commits (`feat:`, `fix:`, ...). `pre-commit install` runs ruff + mypy (mypy hook scoped to `core|services|viewmodels`).
## Git & Forgejo
Remote is a self-hosted Forgejo, SSH-only:
```bash
git remote add origin ssh://git@smill-home.ddns.net/smill/otko.git
```
- Web UI / HTTPS clone: `https://smill-home.ddns.net/forgejo/smill/otko.git` (note the `/forgejo` subpath — SSH URLs don't have it).
- Forgejo SSH runs through the system sshd on **port 22** (`SSH_PORT = 22` in the server's `app.ini`). If the site ever advertises a `:2222` SSH URL again, that config regressed — push/pull with port 22 anyway and flag it.
- Auth is by SSH key (Forgejo → Settings → SSH Keys). Test: `ssh -T git@smill-home.ddns.net` should answer `Hi there, <user>! ... Forgejo does not provide shell access.`
- Default branch `main`. Work on short-lived branches (`fix/<topic>`, `docs/<topic>`), open a PR against `main`. Never commit directly to `main` from an agent session.
- Repo root is `otko/` itself. (On the dev laptop there is also an empty, commit-less parent repo at `Sync/coding/` — ignore it; all git work happens inside `otko/`.)
## Examples & persistence
- `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.

View file

@ -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 <name>` (pytest-qt, needs a display;
Linux GUI tests want `xvfb-run -a pytest ...`), and
`pytest tests/integration -k <name>` (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/<topic>`, `fix/<topic>`, or
`docs/<topic>` — 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/<name>.py` rather than hand-editing them.
`chore:`, `ci:`.

85
NOTICE
View file

@ -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.

View file

@ -82,13 +82,6 @@ views (Qt) → viewmodels → services (OpenSeesRunner, Persistence) → c
Long version in [`docs/architecture.md`](docs/architecture.md),
including the OpenSeesPy command order the runner emits.
## Documentation
Practical, task-first walkthroughs live in
[`docs/QUICK_GUIDE.md`](docs/QUICK_GUIDE.md) — a cantilever build,
modal analysis, report/script export, display units, and undo/redo.
The full index is [`docs/README.md`](docs/README.md).
## Install (development)
**Desktop GUI** (Qt, PyVista, pyqtgraph, imageio):

View file

@ -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.

View file

@ -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.

249
docs/consistent_units.md Normal file
View file

@ -0,0 +1,249 @@
# Consistent Units
Plan for making units explicit and uniform across OTKO: model input,
solver boundary, result display, and persistence. This is a plan, not
an implementation — no code changes ship with this document.
## 1. Principle
OpenSees is unit-agnostic: it never converts. The engineer picks one
consistent system and sticks to it. OTKO follows the same rule, one
step further:
1. **Stored values are always in the project's native system**
(`ProjectMeta.units`, default `SI_M_N`). The solver, persistence,
and undo stack only ever see native values.
2. **Views may relabel or rescale for humans, never for the solver.**
Any display conversion is a pure view-layer factor applied on read;
converted values are never written back.
3. **Every number shown to the user carries its unit**, or is
explicitly dimensionless (strain, drift ratio, damping ratio).
What this document does NOT propose: auto-conversion on input, unit
migration of existing models, or any change to OpenSeesPy command
emission order/content (`docs/architecture.md` stays authoritative).
## 2. Where we are
- `src/otko/core/units.py` — `UnitSystem` enum (4 systems), frozen
`UnitLabels` bundle (length / force / moment / stress / curvature /
rotation, rotation always `rad`), `labels_for()` helper. Display
only; zero conversion factors anywhere in the repo.
- `ProjectMeta.units` (`src/otko/core/project.py:124`) persisted in
every `.osmodel`; `SetUnitsCommand` (`src/otko/commands/project.py`)
is display-only by contract; `Options → Set Display Units`
(`src/otko/views/action_handlers.py`) applies it and refreshes docks.
- Views consume labels via `set_units()` (`src/otko/views/dock_manager.py`):
pushover curve and material tester do; force diagrams, time-history,
results panel, and response-spectrum Sa axis do not. `section_editor`
hardcodes `y (m)` / `z (m)` — wrong for kip-in models. DOF labels
(`views/docks/_labels.py`) are unitless by design.
- ADR-0001 §2.7 deferred per-field `UnitTag` quantity annotations;
generated catalog fields are still `str` placeholders (`'500 MPa'`,
`# TODO: unit-aware type`). The quantity taxonomy in §5 below is
meant to become those `UnitTag` strings verbatim.
- Examples use two systems in practice: `SI_M_N` (cantilever →
M3 peaks 50 kN·m; portal, space frame, concrete04) and `US_IN_KIP`
(ex1a–ex4 families in inches/kips/ksi, `G=386.4 in/s²`).
`SI_MM_N` and `US_FT_KIP` appear only in the enum, tests, and
`assign_hinge` prefills. `basic_truss.py` hand-converts
(`IN_TO_M`, `KIP_TO_N`) to work in SI — the only example that does.
- Roadmap backlog item 3 already asks for "units-aware labels" in the
input-dialog pass; this plan is the spec for that item.
## 3. Canonical systems
> **Update (Sept 2026):** collapsed to two stored systems, `METRIC`
> and `IMPERIAL` (§3.1 and §3.4 below, renamed). `SI_MM_N` and
> `US_FT_KIP` are retired — old `.osmodel` files still load via the
> `LEGACY_UNIT_SYSTEMS` map in `core/units.py`, but their stored
> numbers keep the old mm/ft scale (re-enter values natively).
> The status bar offers Metric / Imperial only; switching routes
> through `SetUnitsCommand` and refreshes every units-aware label
> (`_sync_units_everywhere` on `modelMutated`, covering combo, menu,
> and undo/redo). All values stay native, so every label next to a
> value uses the `NATIVE` table (`native_label()`), never the
> `DISPLAY` prefixes — `DISPLAY` remains data-only for a future
> true display-switching phase.
> Each table lists the **stored unit** (what the solver and
> `.osmodel` see) and the **display unit + factor** (view layer only,
> applied on read).
### 3.1 Metric — `SI_M_N` (m, N, Pa)
Building/civil scale. Stored = displayed, except where humans expect
scaled prefixes (factor applies at the view, values untouched).
| Quantity | Stored | Display | Factor |
|---|---|---|---|
| Geometry / length | m | m | 1 |
| Section dims | m | mm | 1000 |
| Displacement | m | mm | 1000 |
| Rotation | rad | rad | 1 (° toggle, §6) |
| Point load | N | kN | 1e-3 |
| Distributed load | N/m | kN/m | 1e-3 |
| Moment | N·m | kN·m | 1e-3 |
| Stress / modulus | Pa | MPa | 1e-6 |
| Area | m² | mm² | 1e6 |
| Inertia | m⁴ | mm⁴ / cm⁴ | 1e12 / 1e8 |
| Mass | kg | kg (t for large) | 1 |
| Time / period | s | s | 1 |
### 3.2 Metric — `SI_MM_N` (mm, N, MPa)
Detail/component scale (steel connections, lab specimens). Stored =
displayed everywhere; no factors.
| Quantity | Stored = Display |
|---|---|
| Geometry, section dims, displacement | mm |
| Rotation | rad |
| Point load | N |
| Distributed load | N/mm |
| Moment | N·mm |
| Stress / modulus | MPa |
| Mass | t |
### 3.3 Imperial — `US_FT_KIP` (ft, kip, ksf)
Building scale. The mixed ft/in convention engineers expect
(lengths in feet, displacements in inches) **requires display
factors** — this is the case that forces §4 to exist.
| Quantity | Stored | Display | Factor |
|---|---|---|---|
| Geometry / length | ft | ft | 1 |
| Section dims | ft | in | 12 |
| Displacement | ft | in | 12 |
| Rotation | rad | rad | 1 (° toggle, §6) |
| Point load | kip | kip | 1 |
| Distributed load | kip/ft | kip/ft (plf alt.) | 1 |
| Moment | kip·ft | kip·ft | 1 |
| Stress / modulus | ksf | ksi (materials) | 1/144 |
| Area | ft² | in² | 144 |
| Inertia | ft⁴ | in⁴ | 20736 |
| Mass | slug | slug | 1 |
| Time / period | s | s | 1 |
Small-load alternative: `lbf` / `plf` / `lb·ft` are display aliases
(×1000 from kip units), not separate systems. A view showing
`0.004 kip` should render `4.0 lbf`; threshold and format rules are
view concerns (§6).
### 3.4 Imperial — `US_IN_KIP` (in, kip, ksi)
Component scale (members, sections, the ex1a–ex4 example families).
Stored = displayed everywhere; no factors.
| Quantity | Stored = Display |
|---|---|
| Geometry, section dims, displacement | in |
| Rotation | rad |
| Point load | kip (lbf alias ×1000) |
| Distributed load | kip/in |
| Moment | kip·in |
| Stress / modulus | ksi |
| Mass | kip·s²/in |
Gravity for mass derivation is `386.4 in/s²` in this system
(`32.2 ft/s²` under `US_FT_KIP`, `9.81 m/s²` metric) — document the
constant next to every mass-from-weight computation; never hardcode
it in a system-agnostic path.
## 4. Display-factor mechanism (view layer only)
New pure-data table in `core/units.py`, e.g.
`DISPLAY: dict[UnitSystem, dict[str, tuple[str, float]]]`
mapping quantity → (display label, multiply-by-factor). Rules:
- Factors live in `core/` as **data only** (like `_LABELS` today) —
no Qt, no application logic, trivially unit-testable.
- Factors are **applied in views/viewmodels on read** (axis labels,
table cells, diagram annotations). Converted values never flow
into commands, services, or persistence.
- `rotation` factor is always 1 (`rad`); a degrees toggle is a
formatting option (§6), not a system.
- Dimensionless quantities (strain, drift, damping ratio, mass
participation) never take factors.
This keeps the current "we don't auto-convert" contract intact: the
solver boundary is untouched; only human-facing strings change.
## 5. Quantity taxonomy (shared with ADR-0001)
Each numeric field in `core/` and `catalog/` eventually gets one of
these quantity tags (same strings as the future `UnitTag`, so the
follow-up ADR adopts them unchanged):
`length, displacement, rotation, force, moment, distributed_load,
stress, area, inertia, mass, time, frequency, temperature`
Minimum viable step (no ADR needed): use the taxonomy as the key set
for the §4 table and as the vocabulary for dialog hints (§6). Field
annotation (`UnitTag`) stays deferred per ADR-0001 §2.7.
## 6. UI rollout checklist
File-by-file, each item independently reviewable:
- [ ] `views/dialogs/section_editor.py:184-185` — replace hardcoded
`(m)` with `labels_for()` length unit. (Bug fix, do first.)
- [ ] `views/docks/force_diagram.py` — append force/moment display
units to component labels and min/max annotations.
- [ ] `views/docks/time_history.py` — y-axis unit per trace kind
(displacement / velocity / acceleration); x-axis stays `s`.
- [ ] `views/docks/results_panel.py` — displacement/force headers
take display units; modal table keeps `rad²/s²`, `rad/s`, `Hz`, `s`.
- [ ] `views/docks/response_spectrum.py` — Sa axis takes the
acceleration display unit; period stays `s`.
- [ ] Assign dialogs (`assign_load`, `distributed_load`,
`pattern_loads`) — unit hints on field labels, e.g.
`wy (kip/ft)`, driven by project system (roadmap item 3).
- [ ] Table dock headers — `name`-style unit suffixes where numeric
columns carry units; dimensionless columns stay bare.
- [ ] Rotation display — solver and storage stay `rad`; add an
optional ° formatting toggle in post views only (×180/π on read).
- [ ] `lbf`/`plf` small-value aliases — formatting rule in shared
label helper, not per-view logic.
## 7. Persistence, examples, conventions
- `meta.units` keeps meaning "native stored system". Display choices
(factors applied, ° toggle, lbf alias) are **never persisted** —
reopening a file always shows native-unit defaults.
- Old files without new fields (if any are added as `Optional` with
defaults) load unchanged; `ProjectMeta extra="forbid"` still
rejects unknown keys — no migration needed for this plan.
- Document each example family's convention at the top of its
script (one comment line: system + key constants). Add one
`SI_MM_N` and one `US_FT_KIP` example so all four systems have
runner-verified coverage; today two systems have none.
- Rule for new examples: no hand-conversion constants. Either model
natively in the declared system or, once §4 exists, use the shared
factor table. `basic_truss.py` is grandfathered until then.
## 8. Verification
- Extend `tests/unit/test_unit_labels.py`: factor-table coverage per
system (identity for `US_IN_KIP`/`SI_MM_N`; ft→in ×12, N→kN,
Pa→MPa, ksf→ksi spots), rotation always factor 1, dimensionless
quantities absent from the table.
- Round-trip invariant: regen all examples, `git diff` on
`*.osmodel` must show label/metadata changes only — never numeric
value changes.
- GUI smoke (offscreen/xvfb): open one model per system, flip
display units, assert axis/table labels change and stored values
do not (compare `model_dump()` before/after).
## 9. Phases
- **A — label the stored unit everywhere** (§6 minus factors):
no new mechanism, pure label plumbing. Shippable alone.
- **B — display-factor table + apply in post views** (§4, §5 keys):
ft→in, N→kN, Pa→MPa and friends appear; solver untouched.
- **C — input-dialog hints + section_editor fix** (roadmap item 3):
hints only, values still entered in native units.
- **D — optional, needs follow-up ADR**: true display-unit
switching (type in inches, store feet) and `UnitTag` field
annotation per ADR-0001 §2.7. Explicitly out of scope until A–C
ship.

View file

@ -304,7 +304,7 @@ def build_ex4_portal2d_elastic_element() -> Project:
name=ELASTIC_VARIANT.name,
author="OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
description=ELASTIC_VARIANT.description,
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2,
ndf=3,
@ -333,7 +333,7 @@ def build_ex4_portal2d_inelastic_section() -> Project:
name=INELASTIC_SECTION_VARIANT.name,
author="OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
description=INELASTIC_SECTION_VARIANT.description,
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2,
ndf=3,
@ -383,7 +383,7 @@ def build_ex4_portal2d_inelastic_fiber_section() -> Project:
name=FIBER_VARIANT.name,
author="OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
description=FIBER_VARIANT.description,
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2,
ndf=3,

View file

@ -4,7 +4,7 @@
"name": "Basic Truss",
"description": "3-bar asymmetric planar truss, linear static analysis.",
"author": "OpenSees Examples Manual - Example 1",
"units": "SI (m, N, kg, s, Pa)"
"units": "Metric (m, N, kg, s, Pa)"
},
"ndm": 2,
"ndf": 2,
@ -245,6 +245,7 @@
"rho": 0.0
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -284,6 +285,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 1,
"load_factor_increment": 1.0,
"system": "BandGeneral",

View file

@ -95,7 +95,7 @@ def build_basic_truss() -> Project:
name="Basic Truss",
author="OpenSees Examples Manual - Example 1",
description="3-bar asymmetric planar truss, linear static analysis.",
units=UnitSystem.SI_M_N,
units=UnitSystem.METRIC,
),
ndm=2, ndf=2,
coord_systems=[

View file

@ -4,7 +4,7 @@
"name": "Simply Supported Beam — Quad Elements (OpenSees Ex 6.4)",
"description": "16×4 plane-stress quad mesh of a 40×10 deep beam with two midspan point loads, LoadControl 0→10 in 10 steps.",
"author": "OpenSees Examples Manual",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 2,
@ -3391,6 +3391,7 @@
"b2": 0.0
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -3441,6 +3442,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 1.0,
"system": "ProfileSPD",
@ -3456,6 +3458,7 @@
"name": "FreeVibration",
"type": "Transient",
"pattern_ids": [],
"pattern_factors": {},
"dt": 0.5,
"n_steps": 1500,
"system": "BandGeneral",
@ -3471,6 +3474,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -132,7 +132,7 @@ def build_beam_quad_2d() -> Project:
f"{NX}×{NY} plane-stress quad mesh of a {L:.0f}×{H:.0f} deep "
"beam with two midspan point loads, LoadControl 0→10 in 10 steps."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2, ndf=2,
nodes=nodes,

View file

@ -4,7 +4,7 @@
"name": "Cantilever",
"description": "",
"author": "Ozan",
"units": "SI (m, N, kg, s, Pa)"
"units": "Metric (m, N, kg, s, Pa)"
},
"ndm": 3,
"ndf": 6,
@ -198,7 +198,8 @@
"Iz": 0.0002,
"Iy": 4.5e-05,
"G": 80000000000.0,
"J": 8.5e-07
"J": 8.5e-07,
"shape": null
}
],
"elements": [
@ -212,7 +213,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 2,
@ -224,7 +226,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 3,
@ -236,7 +239,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 4,
@ -248,7 +252,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 5,
@ -260,9 +265,11 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -341,6 +348,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 1,
"load_factor_increment": 1.0,
"system": "BandGeneral",
@ -358,6 +366,7 @@
"pattern_ids": [
2
],
"pattern_factors": {},
"n_steps": 1,
"load_factor_increment": 1.0,
"system": "BandGeneral",

View file

@ -58,7 +58,7 @@ def build_cantilever() -> Project:
]
return Project(
meta=ProjectMeta(name="Cantilever", author="Ozan", units=UnitSystem.SI_M_N),
meta=ProjectMeta(name="Cantilever", author="Ozan", units=UnitSystem.METRIC),
ndm=3, ndf=6,
nodes=nodes,
sections=[

View file

@ -4,7 +4,7 @@
"name": "Concrete04 Cantilever",
"description": "Single RC column cantilever using Concrete04 (Popovics) fiber section. Demonstrates the Concrete04 material end-to-end: schema → model → runner → results.",
"author": "",
"units": "SI (m, N, kg, s, Pa)"
"units": "Metric (m, N, kg, s, Pa)"
},
"ndm": 2,
"ndf": 3,
@ -237,6 +237,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -254,6 +255,7 @@
"pattern_ids": [
2
],
"pattern_factors": {},
"control_node": 2,
"control_dof": 1,
"target_disp": 0.15000000000000002,

View file

@ -85,7 +85,7 @@ def build_concrete04_cantilever() -> Project:
"fiber section. Demonstrates the Concrete04 material "
"end-to-end: schema → model → runner → results."
),
units=UnitSystem.SI_M_N,
units=UnitSystem.METRIC,
),
ndm=2,
ndf=3,

View file

@ -4,7 +4,7 @@
"name": "Eigen - Two-Storey One-Bay Frame",
"description": "Two-storey one-bay elastic frame with two-mode eigenvalue analysis.",
"author": "OpenSees Wiki / Chopra Example 10.5",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -198,7 +198,8 @@
"Iz": 640.0,
"Iy": null,
"G": null,
"J": null
"J": null,
"shape": null
},
{
"id": 2,
@ -209,7 +210,8 @@
"Iz": 320.0,
"Iy": null,
"G": null,
"J": null
"J": null,
"shape": null
}
],
"elements": [
@ -223,7 +225,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 2,
@ -235,7 +238,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 3,
@ -247,7 +251,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 4,
@ -259,7 +264,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 5,
@ -271,7 +277,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 6,
@ -283,7 +290,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],

View file

@ -56,7 +56,7 @@ def build_eigen_two_storey_one_bay_frame() -> Project:
description=(
"Two-storey one-bay elastic frame with two-mode eigenvalue analysis."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2,
ndf=3,

View file

@ -4,7 +4,7 @@
"name": "Eigen - Two-Storey Shear Frame",
"description": "Two-storey shear frame with equalDOF floor constraints and two-mode eigenvalue analysis.",
"author": "OpenSees Wiki / Chopra Example 10.4",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -198,7 +198,8 @@
"Iz": 640.0,
"Iy": null,
"G": null,
"J": null
"J": null,
"shape": null
},
{
"id": 2,
@ -209,7 +210,8 @@
"Iz": 320.0,
"Iy": null,
"G": null,
"J": null
"J": null,
"shape": null
},
{
"id": 3,
@ -220,7 +222,8 @@
"Iz": 10000000000000.0,
"Iy": null,
"G": null,
"J": null
"J": null,
"shape": null
}
],
"elements": [
@ -234,7 +237,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 2,
@ -246,7 +250,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 3,
@ -258,7 +263,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 4,
@ -270,7 +276,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 5,
@ -282,7 +289,8 @@
],
"section_id": 3,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 6,
@ -294,7 +302,8 @@
],
"section_id": 3,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [

View file

@ -62,7 +62,7 @@ def build_eigen_two_storey_shear_frame() -> Project:
"Two-storey shear frame with equalDOF floor constraints and "
"two-mode eigenvalue analysis."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2,
ndf=3,

View file

@ -4,7 +4,7 @@
"name": "Elastic Frame (OpenSees Ex 4)",
"description": "3-story 3-bay 2D elastic frame, AISC W-shape sections, gravity (distributed) + lateral (point) + 5-mode eigen.",
"author": "OpenSees Examples Manual",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -523,7 +523,8 @@
"Iz": 3400.0,
"Iy": 3400.0,
"G": 11200.0,
"J": 1.0
"J": 1.0,
"shape": null
},
{
"id": 2,
@ -534,7 +535,8 @@
"Iz": 4330.0,
"Iy": 4330.0,
"G": 11200.0,
"J": 1.0
"J": 1.0,
"shape": null
},
{
"id": 3,
@ -545,7 +547,8 @@
"Iz": 5900.0,
"Iy": 5900.0,
"G": 11200.0,
"J": 1.0
"J": 1.0,
"shape": null
},
{
"id": 4,
@ -556,7 +559,8 @@
"Iz": 4930.0,
"Iy": 4930.0,
"G": 11200.0,
"J": 1.0
"J": 1.0,
"shape": null
},
{
"id": 5,
@ -567,7 +571,8 @@
"Iz": 1830.0,
"Iy": 1830.0,
"G": 11200.0,
"J": 1.0
"J": 1.0,
"shape": null
}
],
"elements": [
@ -581,7 +586,8 @@
],
"section_id": 1,
"geom_transf": "PDelta",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 2,
@ -593,7 +599,8 @@
],
"section_id": 2,
"geom_transf": "PDelta",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 3,
@ -605,7 +612,8 @@
],
"section_id": 2,
"geom_transf": "PDelta",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 4,
@ -617,7 +625,8 @@
],
"section_id": 1,
"geom_transf": "PDelta",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 5,
@ -629,7 +638,8 @@
],
"section_id": 1,
"geom_transf": "PDelta",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 6,
@ -641,7 +651,8 @@
],
"section_id": 2,
"geom_transf": "PDelta",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 7,
@ -653,7 +664,8 @@
],
"section_id": 2,
"geom_transf": "PDelta",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 8,
@ -665,7 +677,8 @@
],
"section_id": 1,
"geom_transf": "PDelta",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 9,
@ -677,7 +690,8 @@
],
"section_id": 1,
"geom_transf": "PDelta",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 10,
@ -689,7 +703,8 @@
],
"section_id": 2,
"geom_transf": "PDelta",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 11,
@ -701,7 +716,8 @@
],
"section_id": 2,
"geom_transf": "PDelta",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 12,
@ -713,7 +729,8 @@
],
"section_id": 1,
"geom_transf": "PDelta",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 13,
@ -725,7 +742,8 @@
],
"section_id": 3,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 14,
@ -737,7 +755,8 @@
],
"section_id": 3,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 15,
@ -749,7 +768,8 @@
],
"section_id": 3,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 16,
@ -761,7 +781,8 @@
],
"section_id": 4,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 17,
@ -773,7 +794,8 @@
],
"section_id": 4,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 18,
@ -785,7 +807,8 @@
],
"section_id": 4,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 19,
@ -797,7 +820,8 @@
],
"section_id": 5,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 20,
@ -809,7 +833,8 @@
],
"section_id": 5,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 21,
@ -821,9 +846,11 @@
],
"section_id": 5,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -954,6 +981,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 1,
"load_factor_increment": 1.0,
"system": "BandGeneral",
@ -972,6 +1000,7 @@
1,
2
],
"pattern_factors": {},
"n_steps": 1,
"load_factor_increment": 1.0,
"system": "BandGeneral",

View file

@ -227,7 +227,7 @@ def build_elastic_frame() -> Project:
"3-story 3-bay 2D elastic frame, AISC W-shape sections, "
"gravity (distributed) + lateral (point) + 5-mode eigen."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2, ndf=3,
coord_systems=[

View file

@ -4,7 +4,7 @@
"name": "OpenSees Ex 1a - 2D Elastic Cantilever Column",
"description": "Original Ex 1a elastic cantilever column with shared gravity preload, static pushover, and BM68elc base-excitation cases.",
"author": "OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -98,7 +98,8 @@
"Iz": 1080000.0,
"Iy": 1080000.0,
"G": 1.0,
"J": 1.0
"J": 1.0,
"shape": null
}
],
"elements": [
@ -112,7 +113,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
@ -4138,7 +4140,8 @@
0.0006658006,
0.0006636009
],
"file_path": "BM68elc.acc"
"file_path": "BM68elc.acc",
"use_last": false
}
],
"load_patterns": [
@ -4202,6 +4205,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -4219,6 +4223,7 @@
"pattern_ids": [
2
],
"pattern_factors": {},
"control_node": 2,
"control_dof": 1,
"target_disp": 100.0,
@ -4243,6 +4248,7 @@
"pattern_ids": [
3
],
"pattern_factors": {},
"dt": 0.02,
"n_steps": 1000,
"system": "BandGeneral",
@ -4258,6 +4264,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -84,7 +84,7 @@ def build_ex1a_canti2d() -> Project:
"Original Ex 1a elastic cantilever column with shared gravity "
"preload, static pushover, and BM68elc base-excitation cases."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2,
ndf=3,
@ -222,7 +222,6 @@ def main() -> None:
project = build_ex1a_canti2d()
project.validate_references()
gm = next(ts for ts in project.time_series if ts.id == 3)
assert isinstance(gm, PathTimeSeries)
print(f"Built '{project.meta.name}'")
print(f" Gravity + pushover + earthquake cases: {len(project.analyses)}")
print(f" Ground motion file: {GROUND_MOTION_FILE.name}")

View file

@ -4,7 +4,7 @@
"name": "2D Elastic Cantilever EQ (OpenSees Ex 1a)",
"description": "Elastic 2D cantilever with gravity preload + horizontal UniformExcitation time history from the A10000 record.",
"author": "OpenSees Wiki / Examples Manual",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -98,7 +98,8 @@
"Iz": 1080000.0,
"Iy": 1080000.0,
"G": 1.0,
"J": 1.0
"J": 1.0,
"shape": null
}
],
"elements": [
@ -112,9 +113,11 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -8121,7 +8124,8 @@
-0.000147416,
-0.000147326
],
"file_path": "A10000.txt"
"file_path": "A10000.txt",
"use_last": false
}
],
"load_patterns": [
@ -8165,6 +8169,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -8182,6 +8187,7 @@
"pattern_ids": [
2
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 3995,
"system": "BandGeneral",
@ -8197,6 +8203,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -81,7 +81,7 @@ def build_ex1a_canti2d_eq() -> Project:
"Elastic 2D cantilever with gravity preload + horizontal "
"UniformExcitation time history from the A10000 record."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2,
ndf=3,
@ -190,7 +190,6 @@ def main() -> None:
project = build_ex1a_canti2d_eq()
project.validate_references()
gm = next(ts for ts in project.time_series if ts.id == 2)
assert isinstance(gm, PathTimeSeries)
print(f"Built '{project.meta.name}'")
print(f" Ground motion file: {GROUND_MOTION_FILE.name}")
print(f" Reference Tcl: {REFERENCE_TCL.name}")

View file

@ -4,7 +4,7 @@
"name": "OpenSees Ex 1b - Elastic Portal Frame",
"description": "Original Ex 1b elastic portal frame with shared gravity preload, static pushover, and BM68elc earthquake cases.",
"author": "OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -148,7 +148,8 @@
"Iz": 1080000.0,
"Iy": 1080000.0,
"G": 1.0,
"J": 1.0
"J": 1.0,
"shape": null
},
{
"id": 2,
@ -159,7 +160,8 @@
"Iz": 4423680.0,
"Iy": 4423680.0,
"G": 1.0,
"J": 1.0
"J": 1.0,
"shape": null
}
],
"elements": [
@ -173,7 +175,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 2,
@ -185,7 +188,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 3,
@ -197,7 +201,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
@ -4223,7 +4228,8 @@
0.0006658006,
0.0006636009
],
"file_path": "BM68elc.acc"
"file_path": "BM68elc.acc",
"use_last": false
}
],
"load_patterns": [
@ -4293,6 +4299,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -4310,6 +4317,7 @@
"pattern_ids": [
2
],
"pattern_factors": {},
"control_node": 3,
"control_dof": 1,
"target_disp": 10.0,
@ -4335,6 +4343,7 @@
"pattern_ids": [
3
],
"pattern_factors": {},
"dt": 0.02,
"n_steps": 1000,
"system": "BandGeneral",
@ -4350,6 +4359,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -88,7 +88,7 @@ def build_ex1b_portal2d() -> Project:
"Original Ex 1b elastic portal frame with shared gravity "
"preload, static pushover, and BM68elc earthquake cases."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2,
ndf=3,
@ -209,7 +209,6 @@ def main() -> None:
project = build_ex1b_portal2d()
project.validate_references()
gm = next(ts for ts in project.time_series if ts.id == 3)
assert isinstance(gm, PathTimeSeries)
print(f"Built '{project.meta.name}'")
print(f" Gravity + pushover + earthquake cases: {len(project.analyses)}")
print(f" Ground motion file: {GROUND_MOTION_FILE.name}")

View file

@ -4,7 +4,7 @@
"name": "OpenSees Ex 2a - Elastic Cantilever Column with Variables",
"description": "Variable-driven elastic cantilever model with derived mass, section, pushover, and earthquake analysis parameters.",
"author": "OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -98,7 +98,8 @@
"Iz": 1080000.0,
"Iy": 1080000.0,
"G": 1.0,
"J": 1.0
"J": 1.0,
"shape": null
}
],
"elements": [
@ -112,7 +113,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
@ -4138,7 +4140,8 @@
0.0006658006,
0.0006636009
],
"file_path": "BM68elc.acc"
"file_path": "BM68elc.acc",
"use_last": false
}
],
"load_patterns": [
@ -4202,6 +4205,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -4219,6 +4223,7 @@
"pattern_ids": [
200
],
"pattern_factors": {},
"control_node": 2,
"control_dof": 1,
"target_disp": 4.32,
@ -4243,6 +4248,7 @@
"pattern_ids": [
400
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 1000,
"system": "SparseGeneral",
@ -4258,6 +4264,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -99,7 +99,7 @@ def build_ex2a_canti2d_elastic_element() -> Project:
"Variable-driven elastic cantilever model with derived mass, "
"section, pushover, and earthquake analysis parameters."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2,
ndf=3,
@ -231,7 +231,6 @@ def main() -> None:
project = build_ex2a_canti2d_elastic_element()
project.validate_references()
gm = next(ts for ts in project.time_series if ts.id == 3)
assert isinstance(gm, PathTimeSeries)
print(f"Built '{project.meta.name}'")
print(f" LCol={L_COL}, Weight={WEIGHT}, ACol={A_COL:.1f}, Ec={E_C:.3f}")
print(f" Gravity + pushover + earthquake cases: {len(project.analyses)}")

View file

@ -4,7 +4,7 @@
"name": "OpenSees Ex 2b - Nonlinear Cantilever Column",
"description": "Nonlinear cantilever with an aggregated uniaxial section: elastic axial P plus bilinear inelastic Mz in one force-based element.",
"author": "OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -4166,7 +4166,8 @@
0.0006658006,
0.0006636009
],
"file_path": "BM68elc.acc"
"file_path": "BM68elc.acc",
"use_last": false
}
],
"load_patterns": [
@ -4230,6 +4231,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -4247,6 +4249,7 @@
"pattern_ids": [
200
],
"pattern_factors": {},
"control_node": 2,
"control_dof": 1,
"target_disp": 21.6,
@ -4271,6 +4274,7 @@
"pattern_ids": [
400
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 1000,
"system": "SparseGeneral",
@ -4286,6 +4290,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -108,7 +108,7 @@ def build_ex2b_canti2d_inelastic_section() -> Project:
"Nonlinear cantilever with an aggregated uniaxial section: "
"elastic axial P plus bilinear inelastic Mz in one force-based element."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2,
ndf=3,
@ -253,7 +253,6 @@ def main() -> None:
project = build_ex2b_canti2d_inelastic_section()
project.validate_references()
gm = next(ts for ts in project.time_series if ts.id == 400)
assert isinstance(gm, PathTimeSeries)
print(f"Built '{project.meta.name}'")
print(
f" LCol={L_COL}, EA={EA_COL:.1f}, My={MY_COL:.1f}, "

View file

@ -4,7 +4,7 @@
"name": "OpenSees Ex 2c - Inelastic Fiber-Section Cantilever",
"description": "Nonlinear cantilever with a fiber section built from Concrete02 and Steel02 uniaxial materials.",
"author": "OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -4194,7 +4194,8 @@
0.0006658006,
0.0006636009
],
"file_path": "BM68elc.acc"
"file_path": "BM68elc.acc",
"use_last": false
}
],
"load_patterns": [
@ -4258,6 +4259,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -4275,6 +4277,7 @@
"pattern_ids": [
200
],
"pattern_factors": {},
"control_node": 2,
"control_dof": 1,
"target_disp": 4.32,
@ -4299,6 +4302,7 @@
"pattern_ids": [
400
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 1000,
"system": "SparseGeneral",
@ -4314,6 +4318,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -120,7 +120,7 @@ def build_ex2c_canti2d_inelastic_fiber_section() -> Project:
"Nonlinear cantilever with a fiber section built from "
"Concrete02 and Steel02 uniaxial materials."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2,
ndf=3,
@ -301,7 +301,6 @@ def main() -> None:
project = build_ex2c_canti2d_inelastic_fiber_section()
project.validate_references()
gm = next(ts for ts in project.time_series if ts.id == 400)
assert isinstance(gm, PathTimeSeries)
print(f"Built '{project.meta.name}'")
print(
f" LCol={L_COL}, ACol={A_COL:.1f}, Iz={IZ_COL:.1f}, "

View file

@ -4,7 +4,7 @@
"name": "OpenSees Ex 3 - Cantilever (Elastic Build)",
"description": "Example 3 elastic cantilever build with unit-scaled geometry and shared push / uniform-EQ analysis files.",
"author": "OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -98,7 +98,8 @@
"Iz": 1080000.0,
"Iy": 1080000.0,
"G": 1.0,
"J": 1.0
"J": 1.0,
"shape": null
}
],
"elements": [
@ -112,7 +113,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
@ -4138,7 +4140,8 @@
0.0006658006,
0.0006636009
],
"file_path": "BM68elc.acc"
"file_path": "BM68elc.acc",
"use_last": false
}
],
"load_patterns": [
@ -4202,6 +4205,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -4219,6 +4223,7 @@
"pattern_ids": [
200
],
"pattern_factors": {},
"control_node": 2,
"control_dof": 1,
"target_disp": 21.6,
@ -4243,6 +4248,7 @@
"pattern_ids": [
400
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 1000,
"system": "SparseGeneral",
@ -4258,6 +4264,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -87,7 +87,7 @@ def build_ex3_canti2d_elastic_element() -> Project:
"Example 3 elastic cantilever build with unit-scaled geometry "
"and shared push / uniform-EQ analysis files."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2,
ndf=3,

View file

@ -4,7 +4,7 @@
"name": "OpenSees Ex 3 - Cantilever (Fiber Section Build)",
"description": "Example 3 cantilever with inelastic fiber section and shared analysis files.",
"author": "OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -4194,7 +4194,8 @@
0.0006658006,
0.0006636009
],
"file_path": "BM68elc.acc"
"file_path": "BM68elc.acc",
"use_last": false
}
],
"load_patterns": [
@ -4258,6 +4259,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -4275,6 +4277,7 @@
"pattern_ids": [
200
],
"pattern_factors": {},
"control_node": 2,
"control_dof": 1,
"target_disp": 21.6,
@ -4299,6 +4302,7 @@
"pattern_ids": [
400
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 1000,
"system": "SparseGeneral",
@ -4314,6 +4318,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -107,7 +107,7 @@ def build_ex3_canti2d_inelastic_fiber_section() -> Project:
name="OpenSees Ex 3 - Cantilever (Fiber Section Build)",
author="OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
description="Example 3 cantilever with inelastic fiber section and shared analysis files.",
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2, ndf=3,
nodes=[

View file

@ -4,7 +4,7 @@
"name": "OpenSees Ex 3 - Cantilever (Inelastic Section Build)",
"description": "Example 3 cantilever with aggregated uniaxial inelastic section and shared analysis files.",
"author": "OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -4166,7 +4166,8 @@
0.0006658006,
0.0006636009
],
"file_path": "BM68elc.acc"
"file_path": "BM68elc.acc",
"use_last": false
}
],
"load_patterns": [
@ -4230,6 +4231,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -4247,6 +4249,7 @@
"pattern_ids": [
200
],
"pattern_factors": {},
"control_node": 2,
"control_dof": 1,
"target_disp": 21.6,
@ -4271,6 +4274,7 @@
"pattern_ids": [
400
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 1000,
"system": "SparseGeneral",
@ -4286,6 +4290,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -88,7 +88,7 @@ def build_ex3_canti2d_inelastic_section() -> Project:
name="OpenSees Ex 3 - Cantilever (Inelastic Section Build)",
author="OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
description="Example 3 cantilever with aggregated uniaxial inelastic section and shared analysis files.",
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2, ndf=3,
nodes=[

View file

@ -4,7 +4,7 @@
"name": "OpenSees Ex 4 - Portal Frame (Elastic Build)",
"description": "Example 4 elastic portal frame with shared gravity, push, and sine-wave support motion.",
"author": "OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -148,7 +148,8 @@
"Iz": 864000.0,
"Iy": 864000.0,
"G": 1.0,
"J": 1.0
"J": 1.0,
"shape": null
},
{
"id": 2,
@ -159,7 +160,8 @@
"Iz": 4423680.0,
"Iy": 4423680.0,
"G": 1.0,
"J": 1.0
"J": 1.0,
"shape": null
}
],
"elements": [
@ -173,7 +175,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 2,
@ -185,7 +188,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 3,
@ -197,7 +201,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
@ -335,7 +340,7 @@
-83.82633839751217,
-99.09214039482102,
-113.56011074290575,
-127.11376185155243,
-127.11376185155244,
-139.643967691452,
-151.04984241282355,
-161.23955261895566,
@ -428,7 +433,7 @@
-113.56011074290593,
-99.0921403948212,
-83.82633839751236,
-67.88561601251554,
-67.88561601251556,
-51.398318563481766,
-34.49719207509674,
-17.318314480143496,
@ -511,7 +516,7 @@
99.0921403948209,
113.56011074290566,
127.11376185155234,
139.64396769145193,
139.6439676914519,
151.04984241282366,
161.23955261895557,
170.1310567547217,
@ -540,12 +545,12 @@
2.129415854357418e-13,
-17.31831448014314,
-34.49719207509707,
-51.398318563481425,
-51.39831856348143,
-67.88561601251521,
-83.82633839751205,
-99.09214039482087,
-113.5601107429062,
-127.11376185155231,
-127.11376185155234,
-139.6439676914519,
-151.04984241282324,
-161.23955261895594,
@ -578,7 +583,7 @@
51.3983185634814,
67.88561601251583,
83.82633839751202,
99.09214039482086,
99.09214039482087,
113.56011074290619,
127.11376185155231,
139.64396769145142,
@ -635,7 +640,7 @@
-151.04984241282355,
-139.6439676914518,
-127.1137618515527,
-113.56011074290548,
-113.5601107429055,
-99.09214039482131,
-83.82633839751311,
-67.88561601251568,
@ -654,7 +659,7 @@
139.64396769145233,
151.0498424128232,
161.2395526189559,
170.13105675472167,
170.13105675472164,
177.6527656569399,
183.7441189482236,
188.3560726335284,
@ -668,7 +673,7 @@
170.1310567547216,
161.23955261895586,
151.04984241282312,
139.64396769145227,
139.64396769145225,
127.11376185155324,
113.56011074290606,
99.09214039482075,
@ -805,14 +810,14 @@
188.35607263352813,
183.7441189482236,
177.65276565694043,
170.13105675472167,
170.13105675472164,
161.2395526189559,
151.0498424128232,
139.64396769145233,
127.11376185155329,
113.56011074290502,
99.09214039482083,
83.82633839751259,
83.8263383975126,
67.88561601251514,
51.39831856348202,
34.49719207509632,
@ -824,7 +829,8 @@
-67.88561601251568,
-83.82633839751188
],
"file_path": "generated:sine-wave"
"file_path": "generated:sine-wave",
"use_last": false
},
{
"id": 401,
@ -860,7 +866,7 @@
5.09980167540335,
5.928784854272076,
6.7100329435632435,
7.437255793028317,
7.437255793028318,
8.104598231883518,
8.70668721120218,
9.238675064545419,
@ -911,7 +917,7 @@
-10.075813279025667,
-9.696278538521907,
-9.238675064545415,
-8.706687211202178,
-8.706687211202176,
-8.10459823188352,
-7.437255793028321,
-6.710032943563246,
@ -941,7 +947,7 @@
10.718732373371513,
10.762057251873964,
10.718732373371514,
10.58910656431125,
10.589106564311248,
10.37422349548898,
10.075813279025667,
9.696278538521907,
@ -1017,7 +1023,7 @@
9.696278538521907,
9.238675064545422,
8.706687211202185,
8.104598231883523,
8.104598231883521,
7.437255793028323,
6.71003294356325,
5.928784854272083,
@ -1158,8 +1164,8 @@
9.238675064545424,
8.706687211202166,
8.104598231883525,
7.437255793028327,
6.710032943563254,
7.437255793028328,
6.710032943563253,
5.928784854272055,
5.099801675403362,
4.229757892079114,
@ -1193,7 +1199,7 @@
-9.238675064545426,
-8.706687211202167,
-8.104598231883527,
-7.437255793028355,
-7.437255793028356,
-6.710032943563254,
-5.928784854272056,
-5.099801675403363,
@ -1265,7 +1271,7 @@
-8.104598231883529,
-7.437255793028303,
-6.710032943563257,
-5.928784854272058,
-5.928784854272059,
-5.0998016754033655,
-4.229757892079153,
-3.3256585852652147,
@ -1279,7 +1285,7 @@
4.229757892079088,
5.09980167540337,
5.928784854272062,
6.710032943563261,
6.71003294356326,
7.4372557930283065,
8.104598231883482,
8.706687211202171,
@ -1316,7 +1322,7 @@
-5.92878485427203,
-6.71003294356329,
-7.437255793028332,
-8.104598231883505,
-8.104598231883507,
-8.706687211202192,
-9.23867506454541,
-9.69627853852188,
@ -1328,7 +1334,7 @@
-10.71873237337151,
-10.589106564311251,
-10.374223495488975,
-10.075813279025672,
-10.075813279025674,
-9.696278538521929,
-9.238675064545427,
-8.706687211202215,
@ -1355,7 +1361,7 @@
8.70668721120217,
9.238675064545427,
9.696278538521897,
10.075813279025672,
10.075813279025674,
10.374223495488975,
10.589106564311237,
10.718732373371518,
@ -1402,7 +1408,7 @@
-9.696278538521899,
-9.23867506454543,
-8.706687211202215,
-8.104598231883534,
-8.104598231883532,
-7.437255793028362,
-6.710032943563201,
-5.928784854272062,
@ -1418,11 +1424,11 @@
3.3256585852652147,
4.229757892079083,
5.0998016754033655,
5.928784854272058,
5.928784854272059,
6.710032943563257,
7.437255793028303,
8.104598231883479,
8.706687211202214,
8.706687211202212,
9.238675064545427,
9.696278538521895,
10.075813279025672,
@ -1436,7 +1442,8 @@
10.07581327902566,
9.696278538521915
],
"file_path": "generated:sine-wave-velocity"
"file_path": "generated:sine-wave-velocity",
"use_last": false
}
],
"load_patterns": [
@ -1506,6 +1513,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -1523,6 +1531,7 @@
"pattern_ids": [
200
],
"pattern_factors": {},
"control_node": 3,
"control_dof": 1,
"target_disp": 43.2,
@ -1548,6 +1557,7 @@
"pattern_ids": [
400
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 1000,
"system": "BandGeneral",
@ -1563,6 +1573,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -4,7 +4,7 @@
"name": "OpenSees Ex 4 - Portal Frame (Fiber Section Build)",
"description": "Example 4 portal frame with fiber-section columns and shared pushover / sine-wave analyses.",
"author": "OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -213,7 +213,8 @@
"Iz": 4423680.0,
"Iy": 4423680.0,
"G": 1.0,
"J": 1.0
"J": 1.0,
"shape": null
}
],
"elements": [
@ -395,7 +396,7 @@
-83.82633839751217,
-99.09214039482102,
-113.56011074290575,
-127.11376185155243,
-127.11376185155244,
-139.643967691452,
-151.04984241282355,
-161.23955261895566,
@ -488,7 +489,7 @@
-113.56011074290593,
-99.0921403948212,
-83.82633839751236,
-67.88561601251554,
-67.88561601251556,
-51.398318563481766,
-34.49719207509674,
-17.318314480143496,
@ -571,7 +572,7 @@
99.0921403948209,
113.56011074290566,
127.11376185155234,
139.64396769145193,
139.6439676914519,
151.04984241282366,
161.23955261895557,
170.1310567547217,
@ -600,12 +601,12 @@
2.129415854357418e-13,
-17.31831448014314,
-34.49719207509707,
-51.398318563481425,
-51.39831856348143,
-67.88561601251521,
-83.82633839751205,
-99.09214039482087,
-113.5601107429062,
-127.11376185155231,
-127.11376185155234,
-139.6439676914519,
-151.04984241282324,
-161.23955261895594,
@ -638,7 +639,7 @@
51.3983185634814,
67.88561601251583,
83.82633839751202,
99.09214039482086,
99.09214039482087,
113.56011074290619,
127.11376185155231,
139.64396769145142,
@ -695,7 +696,7 @@
-151.04984241282355,
-139.6439676914518,
-127.1137618515527,
-113.56011074290548,
-113.5601107429055,
-99.09214039482131,
-83.82633839751311,
-67.88561601251568,
@ -714,7 +715,7 @@
139.64396769145233,
151.0498424128232,
161.2395526189559,
170.13105675472167,
170.13105675472164,
177.6527656569399,
183.7441189482236,
188.3560726335284,
@ -728,7 +729,7 @@
170.1310567547216,
161.23955261895586,
151.04984241282312,
139.64396769145227,
139.64396769145225,
127.11376185155324,
113.56011074290606,
99.09214039482075,
@ -865,14 +866,14 @@
188.35607263352813,
183.7441189482236,
177.65276565694043,
170.13105675472167,
170.13105675472164,
161.2395526189559,
151.0498424128232,
139.64396769145233,
127.11376185155329,
113.56011074290502,
99.09214039482083,
83.82633839751259,
83.8263383975126,
67.88561601251514,
51.39831856348202,
34.49719207509632,
@ -884,7 +885,8 @@
-67.88561601251568,
-83.82633839751188
],
"file_path": "generated:sine-wave"
"file_path": "generated:sine-wave",
"use_last": false
},
{
"id": 401,
@ -920,7 +922,7 @@
5.09980167540335,
5.928784854272076,
6.7100329435632435,
7.437255793028317,
7.437255793028318,
8.104598231883518,
8.70668721120218,
9.238675064545419,
@ -971,7 +973,7 @@
-10.075813279025667,
-9.696278538521907,
-9.238675064545415,
-8.706687211202178,
-8.706687211202176,
-8.10459823188352,
-7.437255793028321,
-6.710032943563246,
@ -1001,7 +1003,7 @@
10.718732373371513,
10.762057251873964,
10.718732373371514,
10.58910656431125,
10.589106564311248,
10.37422349548898,
10.075813279025667,
9.696278538521907,
@ -1077,7 +1079,7 @@
9.696278538521907,
9.238675064545422,
8.706687211202185,
8.104598231883523,
8.104598231883521,
7.437255793028323,
6.71003294356325,
5.928784854272083,
@ -1218,8 +1220,8 @@
9.238675064545424,
8.706687211202166,
8.104598231883525,
7.437255793028327,
6.710032943563254,
7.437255793028328,
6.710032943563253,
5.928784854272055,
5.099801675403362,
4.229757892079114,
@ -1253,7 +1255,7 @@
-9.238675064545426,
-8.706687211202167,
-8.104598231883527,
-7.437255793028355,
-7.437255793028356,
-6.710032943563254,
-5.928784854272056,
-5.099801675403363,
@ -1325,7 +1327,7 @@
-8.104598231883529,
-7.437255793028303,
-6.710032943563257,
-5.928784854272058,
-5.928784854272059,
-5.0998016754033655,
-4.229757892079153,
-3.3256585852652147,
@ -1339,7 +1341,7 @@
4.229757892079088,
5.09980167540337,
5.928784854272062,
6.710032943563261,
6.71003294356326,
7.4372557930283065,
8.104598231883482,
8.706687211202171,
@ -1376,7 +1378,7 @@
-5.92878485427203,
-6.71003294356329,
-7.437255793028332,
-8.104598231883505,
-8.104598231883507,
-8.706687211202192,
-9.23867506454541,
-9.69627853852188,
@ -1388,7 +1390,7 @@
-10.71873237337151,
-10.589106564311251,
-10.374223495488975,
-10.075813279025672,
-10.075813279025674,
-9.696278538521929,
-9.238675064545427,
-8.706687211202215,
@ -1415,7 +1417,7 @@
8.70668721120217,
9.238675064545427,
9.696278538521897,
10.075813279025672,
10.075813279025674,
10.374223495488975,
10.589106564311237,
10.718732373371518,
@ -1462,7 +1464,7 @@
-9.696278538521899,
-9.23867506454543,
-8.706687211202215,
-8.104598231883534,
-8.104598231883532,
-7.437255793028362,
-6.710032943563201,
-5.928784854272062,
@ -1478,11 +1480,11 @@
3.3256585852652147,
4.229757892079083,
5.0998016754033655,
5.928784854272058,
5.928784854272059,
6.710032943563257,
7.437255793028303,
8.104598231883479,
8.706687211202214,
8.706687211202212,
9.238675064545427,
9.696278538521895,
10.075813279025672,
@ -1496,7 +1498,8 @@
10.07581327902566,
9.696278538521915
],
"file_path": "generated:sine-wave-velocity"
"file_path": "generated:sine-wave-velocity",
"use_last": false
}
],
"load_patterns": [
@ -1566,6 +1569,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -1583,6 +1587,7 @@
"pattern_ids": [
200
],
"pattern_factors": {},
"control_node": 3,
"control_dof": 1,
"target_disp": 43.2,
@ -1608,6 +1613,7 @@
"pattern_ids": [
400
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 1000,
"system": "BandGeneral",
@ -1623,6 +1629,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -4,7 +4,7 @@
"name": "OpenSees Ex 4 - Portal Frame (Inelastic Section Build)",
"description": "Example 4 portal frame with aggregated uniaxial inelastic column sections and shared analyses.",
"author": "OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -185,7 +185,8 @@
"Iz": 4423680.0,
"Iy": 4423680.0,
"G": 1.0,
"J": 1.0
"J": 1.0,
"shape": null
}
],
"elements": [
@ -367,7 +368,7 @@
-83.82633839751217,
-99.09214039482102,
-113.56011074290575,
-127.11376185155243,
-127.11376185155244,
-139.643967691452,
-151.04984241282355,
-161.23955261895566,
@ -460,7 +461,7 @@
-113.56011074290593,
-99.0921403948212,
-83.82633839751236,
-67.88561601251554,
-67.88561601251556,
-51.398318563481766,
-34.49719207509674,
-17.318314480143496,
@ -543,7 +544,7 @@
99.0921403948209,
113.56011074290566,
127.11376185155234,
139.64396769145193,
139.6439676914519,
151.04984241282366,
161.23955261895557,
170.1310567547217,
@ -572,12 +573,12 @@
2.129415854357418e-13,
-17.31831448014314,
-34.49719207509707,
-51.398318563481425,
-51.39831856348143,
-67.88561601251521,
-83.82633839751205,
-99.09214039482087,
-113.5601107429062,
-127.11376185155231,
-127.11376185155234,
-139.6439676914519,
-151.04984241282324,
-161.23955261895594,
@ -610,7 +611,7 @@
51.3983185634814,
67.88561601251583,
83.82633839751202,
99.09214039482086,
99.09214039482087,
113.56011074290619,
127.11376185155231,
139.64396769145142,
@ -667,7 +668,7 @@
-151.04984241282355,
-139.6439676914518,
-127.1137618515527,
-113.56011074290548,
-113.5601107429055,
-99.09214039482131,
-83.82633839751311,
-67.88561601251568,
@ -686,7 +687,7 @@
139.64396769145233,
151.0498424128232,
161.2395526189559,
170.13105675472167,
170.13105675472164,
177.6527656569399,
183.7441189482236,
188.3560726335284,
@ -700,7 +701,7 @@
170.1310567547216,
161.23955261895586,
151.04984241282312,
139.64396769145227,
139.64396769145225,
127.11376185155324,
113.56011074290606,
99.09214039482075,
@ -837,14 +838,14 @@
188.35607263352813,
183.7441189482236,
177.65276565694043,
170.13105675472167,
170.13105675472164,
161.2395526189559,
151.0498424128232,
139.64396769145233,
127.11376185155329,
113.56011074290502,
99.09214039482083,
83.82633839751259,
83.8263383975126,
67.88561601251514,
51.39831856348202,
34.49719207509632,
@ -856,7 +857,8 @@
-67.88561601251568,
-83.82633839751188
],
"file_path": "generated:sine-wave"
"file_path": "generated:sine-wave",
"use_last": false
},
{
"id": 401,
@ -892,7 +894,7 @@
5.09980167540335,
5.928784854272076,
6.7100329435632435,
7.437255793028317,
7.437255793028318,
8.104598231883518,
8.70668721120218,
9.238675064545419,
@ -943,7 +945,7 @@
-10.075813279025667,
-9.696278538521907,
-9.238675064545415,
-8.706687211202178,
-8.706687211202176,
-8.10459823188352,
-7.437255793028321,
-6.710032943563246,
@ -973,7 +975,7 @@
10.718732373371513,
10.762057251873964,
10.718732373371514,
10.58910656431125,
10.589106564311248,
10.37422349548898,
10.075813279025667,
9.696278538521907,
@ -1049,7 +1051,7 @@
9.696278538521907,
9.238675064545422,
8.706687211202185,
8.104598231883523,
8.104598231883521,
7.437255793028323,
6.71003294356325,
5.928784854272083,
@ -1190,8 +1192,8 @@
9.238675064545424,
8.706687211202166,
8.104598231883525,
7.437255793028327,
6.710032943563254,
7.437255793028328,
6.710032943563253,
5.928784854272055,
5.099801675403362,
4.229757892079114,
@ -1225,7 +1227,7 @@
-9.238675064545426,
-8.706687211202167,
-8.104598231883527,
-7.437255793028355,
-7.437255793028356,
-6.710032943563254,
-5.928784854272056,
-5.099801675403363,
@ -1297,7 +1299,7 @@
-8.104598231883529,
-7.437255793028303,
-6.710032943563257,
-5.928784854272058,
-5.928784854272059,
-5.0998016754033655,
-4.229757892079153,
-3.3256585852652147,
@ -1311,7 +1313,7 @@
4.229757892079088,
5.09980167540337,
5.928784854272062,
6.710032943563261,
6.71003294356326,
7.4372557930283065,
8.104598231883482,
8.706687211202171,
@ -1348,7 +1350,7 @@
-5.92878485427203,
-6.71003294356329,
-7.437255793028332,
-8.104598231883505,
-8.104598231883507,
-8.706687211202192,
-9.23867506454541,
-9.69627853852188,
@ -1360,7 +1362,7 @@
-10.71873237337151,
-10.589106564311251,
-10.374223495488975,
-10.075813279025672,
-10.075813279025674,
-9.696278538521929,
-9.238675064545427,
-8.706687211202215,
@ -1387,7 +1389,7 @@
8.70668721120217,
9.238675064545427,
9.696278538521897,
10.075813279025672,
10.075813279025674,
10.374223495488975,
10.589106564311237,
10.718732373371518,
@ -1434,7 +1436,7 @@
-9.696278538521899,
-9.23867506454543,
-8.706687211202215,
-8.104598231883534,
-8.104598231883532,
-7.437255793028362,
-6.710032943563201,
-5.928784854272062,
@ -1450,11 +1452,11 @@
3.3256585852652147,
4.229757892079083,
5.0998016754033655,
5.928784854272058,
5.928784854272059,
6.710032943563257,
7.437255793028303,
8.104598231883479,
8.706687211202214,
8.706687211202212,
9.238675064545427,
9.696278538521895,
10.075813279025672,
@ -1468,7 +1470,8 @@
10.07581327902566,
9.696278538521915
],
"file_path": "generated:sine-wave-velocity"
"file_path": "generated:sine-wave-velocity",
"use_last": false
}
],
"load_patterns": [
@ -1538,6 +1541,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -1555,6 +1559,7 @@
"pattern_ids": [
200
],
"pattern_factors": {},
"control_node": 3,
"control_dof": 1,
"target_disp": 43.2,
@ -1580,6 +1585,7 @@
"pattern_ids": [
400
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 1000,
"system": "BandGeneral",
@ -1595,6 +1601,8 @@
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": 0.02,
"preload_case_ids": [
1

View file

@ -4,7 +4,7 @@
"name": "Moment-Curvature (OpenSees Ex 2)",
"description": "RC column fibre section — constant axial P + DisplacementControl curvature pushover. Kip-in-ksi units throughout.",
"author": "OpenSees Examples Manual",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -251,6 +251,7 @@
"section_id": 1
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -317,6 +318,7 @@
1,
2
],
"pattern_factors": {},
"control_node": 2,
"control_dof": 3,
"target_disp": 0.0019047619047619052,
@ -329,7 +331,8 @@
"algorithm": "Newton",
"test": "NormUnbalance",
"tolerance": 1e-09,
"max_iter": 25
"max_iter": 25,
"preload_case_ids": []
}
]
}

View file

@ -14,7 +14,7 @@ linear reference moment pattern (Mz = 1 kip·in) is added and
DisplacementControl on DOF 3 ramps the curvature to μ·Ky where
μ = 15 and Ky is the elastic yield curvature estimate.
Units: kip, in, ksi (UnitSystem.US_IN_KIP).
Units: kip, in, ksi (UnitSystem.IMPERIAL).
GUI walkthrough: File → Open → moment_curvature.osmodel, Options →
Set Display Units → US (in, kip, kip·s²/in, s, ksi), Analyze → Run →
@ -83,7 +83,7 @@ def build_moment_curvature() -> Project:
"RC column fibre section — constant axial P + DisplacementControl "
"curvature pushover. Kip-in-ksi units throughout."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2, ndf=3,
coord_systems=[

View file

@ -4,7 +4,7 @@
"name": "Portal Frame",
"description": "",
"author": "Ozan",
"units": "SI (m, N, kg, s, Pa)"
"units": "Metric (m, N, kg, s, Pa)"
},
"ndm": 3,
"ndf": 6,
@ -161,7 +161,8 @@
"Iz": 0.000416,
"Iy": 0.000129,
"G": 80000000000.0,
"J": 2.04e-06
"J": 2.04e-06,
"shape": null
}
],
"elements": [
@ -175,7 +176,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 2,
@ -187,7 +189,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 3,
@ -199,9 +202,11 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -418,7 +423,8 @@
0.0,
0.0
],
"file_path": null
"file_path": null,
"use_last": false
}
],
"load_patterns": [
@ -472,6 +478,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 1,
"load_factor_increment": 1.0,
"system": "BandGeneral",
@ -496,6 +503,7 @@
"pattern_ids": [
2
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 200,
"system": "BandGeneral",
@ -510,7 +518,12 @@
"tolerance": 1e-06,
"max_iter": 25,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.0
"rayleigh_beta_k": 0.0,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": null,
"preload_case_ids": [],
"remove_patterns": []
}
]
}

View file

@ -59,7 +59,7 @@ def _sine_pulse_factors() -> list[float]:
def build_portal_frame() -> Project:
"""A two-column, one-beam steel portal frame with static + dynamic cases."""
return Project(
meta=ProjectMeta(name="Portal Frame", author="Ozan", units=UnitSystem.SI_M_N),
meta=ProjectMeta(name="Portal Frame", author="Ozan", units=UnitSystem.METRIC),
ndm=3,
ndf=6,
nodes=[

View file

@ -4,7 +4,7 @@
"name": "Portal Pushover",
"description": "",
"author": "Ozan",
"units": "SI (m, N, kg, s, Pa)"
"units": "Metric (m, N, kg, s, Pa)"
},
"ndm": 3,
"ndf": 6,
@ -231,7 +231,8 @@
"Iz": 0.000416,
"Iy": 0.000129,
"G": 80000000000.0,
"J": 2.04e-06
"J": 2.04e-06,
"shape": null
}
],
"elements": [
@ -285,9 +286,11 @@
],
"section_id": 3,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -327,6 +330,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"control_node": 3,
"control_dof": 1,
"target_disp": 0.15,
@ -340,7 +344,8 @@
"algorithm": "Newton",
"test": "NormDispIncr",
"tolerance": 1e-06,
"max_iter": 25
"max_iter": 25,
"preload_case_ids": []
},
{
"id": 2,

View file

@ -64,7 +64,7 @@ def build_portal_pushover() -> Project:
return Project(
meta=ProjectMeta(name="Portal Pushover", author="Ozan",
units=UnitSystem.SI_M_N),
units=UnitSystem.METRIC),
ndm=3, ndf=6,
nodes=[
Node(id=1, name="Base-L", coords=(0.0, 0.0, 0.0),

View file

@ -4,7 +4,7 @@
"name": "RC Frame Earthquake (OpenSees Ex 3.3)",
"description": "Ex 3 gravity + uniform base excitation (horizontal, 4-s synthetic record peaking at ~0.35 g) + Rayleigh beta_k",
"author": "OpenSees Examples Manual",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -313,7 +313,8 @@
"Iz": 8640.0,
"Iy": 8640.0,
"G": 1500.0,
"J": 1.0
"J": 1.0,
"shape": null
}
],
"elements": [
@ -355,9 +356,11 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -455,7 +458,7 @@
-0.07226305111525733,
-0.08816778784387092,
-0.10268206588930337,
-0.11557698641636836,
-0.11557698641636838,
-0.12664918882530235,
-0.13572405786990283,
-0.142658477444273,
@ -489,7 +492,7 @@
0.14970400926424074,
0.14734308760930329,
0.14265847744427299,
0.13572405786990302,
0.135724057869903,
0.12664918882530243,
0.11557698641636846,
0.10268206588930347,
@ -504,7 +507,7 @@
-0.0552186829027018,
-0.0722630511152573,
-0.08816778784387089,
-0.10268206588930313,
-0.10268206588930316,
-0.11557698641636852,
-0.12664918882530232,
-0.13572405786990294,
@ -566,7 +569,7 @@
-0.14265847744427307,
-0.1357240578699028,
-0.12664918882530243,
-0.11557698641636868,
-0.11557698641636867,
-0.10268206588930333,
-0.08816778784387108,
-0.0722630511152575,
@ -616,12 +619,12 @@
-0.08652674043787481,
-0.08129819692830416,
-0.07492000704231337,
-0.06752087821731563,
-0.05924239844431021,
-0.06752087821731562,
-0.059242398444310215,
-0.05023649120795737,
-0.04066277069740133,
-0.030685841350052333,
-0.020472585578247403,
-0.0204725855782474,
-0.010189482631897374,
-9.832592927142318e-17,
0.009937903409330418,
@ -642,7 +645,7 @@
0.05947903147792919,
0.05481264807294881,
0.04939932978399098,
0.043342664598141574,
0.04334266459814158,
0.03675380211118236,
0.029749518558492356,
0.02245024111907521,
@ -666,9 +669,9 @@
-0.04631442669186875,
-0.04351578902386234,
-0.04010178999412147,
-0.03614132172945704,
-0.036141321729457034,
-0.03171017082019282,
-0.02688965604775296,
-0.026889656047752963,
-0.021765212731031044,
-0.016424947276336123,
-0.010958185402089994,
@ -682,7 +685,7 @@
0.027293196958740988,
0.03033908626567152,
0.03283256872166209,
0.034748063307665326,
0.03474806330766532,
0.03606971177513051,
0.03679138802438222,
0.03691655417444788,
@ -720,7 +723,7 @@
-0.016973231331797612,
-0.014392995708504558,
-0.011650078858431393,
-0.008791640742480778,
-0.008791640742480776,
-0.005865493972298641,
-0.002919335651562286,
-3.943919054879792e-17,
@ -729,7 +732,7 @@
0.008156387432977339,
0.010541427274178315,
0.012701774131668711,
0.014608995592985858,
0.01460899559298586,
0.016239342654524213,
0.01757400763590476,
0.01859929800432925,
@ -742,7 +745,7 @@
0.01704102783102496,
0.015704086601509296,
0.014153144944791883,
0.012417881316069912,
0.012417881316069914,
0.010530140607703934,
0.008523379771289316,
0.00643210177128294,
@ -754,7 +757,7 @@
-0.005967340521710147,
-0.007712272945193301,
-0.009292816470078945,
-0.010688169499041026,
-0.010688169499041028,
-0.011880956889869792,
-0.012857418649655518,
-0.013607537107403339,
@ -774,7 +777,8 @@
-0.003139572682582101,
-0.0015626077711816495
],
"file_path": null
"file_path": null,
"use_last": false
}
],
"load_patterns": [
@ -830,6 +834,7 @@
1,
2
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 400,
"system": "BandGeneral",
@ -844,7 +849,12 @@
"tolerance": 1e-12,
"max_iter": 10,
"rayleigh_alpha_m": 0.0,
"rayleigh_beta_k": 0.000625
"rayleigh_beta_k": 0.000625,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": null,
"preload_case_ids": [],
"remove_patterns": []
}
]
}

View file

@ -4,7 +4,7 @@
"name": "RC Frame Gravity (OpenSees Ex 3)",
"description": "1-bay 1-storey portal frame, nonlinear fibre columns + elastic beam, 10-step LoadControl gravity pushdown.",
"author": "OpenSees Examples Manual",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -313,7 +313,8 @@
"Iz": 8640.0,
"Iy": 8640.0,
"G": 1500.0,
"J": 1.0
"J": 1.0,
"shape": null
}
],
"elements": [
@ -355,9 +356,11 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -408,6 +411,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",

View file

@ -92,7 +92,7 @@ def build_rc_frame_gravity() -> Project:
"1-bay 1-storey portal frame, nonlinear fibre columns + "
"elastic beam, 10-step LoadControl gravity pushdown."
),
units=UnitSystem.US_IN_KIP,
units=UnitSystem.IMPERIAL,
),
ndm=2, ndf=3,
coord_systems=[

View file

@ -4,7 +4,7 @@
"name": "RC Frame Pushover (OpenSees Ex 3.2)",
"description": "Ex 3 gravity preload (StaticCase) + lateral reference load + DisplacementControl pushover on node 3 (DOF 1) to 15 in, chained via preload_case_ids.",
"author": "OpenSees Examples Manual",
"units": "US (in, kip, kip·s²/in, s, ksi)"
"units": "Imperial (in, kip, kip·s²/in, s, ksi)"
},
"ndm": 2,
"ndf": 3,
@ -313,7 +313,8 @@
"Iz": 8640.0,
"Iy": 8640.0,
"G": 1500.0,
"J": 1.0
"J": 1.0,
"shape": null
}
],
"elements": [
@ -355,9 +356,11 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -445,6 +448,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 10,
"load_factor_increment": 0.1,
"system": "BandGeneral",
@ -462,6 +466,7 @@
"pattern_ids": [
2
],
"pattern_factors": {},
"control_node": 3,
"control_dof": 1,
"target_disp": 15.0,

View file

@ -4,7 +4,7 @@
"name": "SDOF Pushover",
"description": "",
"author": "Ozan",
"units": "SI (m, N, kg, s, Pa)"
"units": "Metric (m, N, kg, s, Pa)"
},
"ndm": 3,
"ndf": 6,
@ -121,7 +121,8 @@
"Iz": 0.0002,
"Iy": 4.5e-05,
"G": 80000000000.0,
"J": 8.5e-07
"J": 8.5e-07,
"shape": null
}
],
"elements": [
@ -135,9 +136,11 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -177,6 +180,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"control_node": 2,
"control_dof": 1,
"target_disp": 0.1,
@ -189,7 +193,8 @@
"algorithm": "Newton",
"test": "NormDispIncr",
"tolerance": 1e-06,
"max_iter": 25
"max_iter": 25,
"preload_case_ids": []
},
{
"id": 2,

View file

@ -48,7 +48,7 @@ from otko.services import load_project, save_project
def build_sdof() -> Project:
return Project(
meta=ProjectMeta(name="SDOF Pushover", author="Ozan",
units=UnitSystem.SI_M_N),
units=UnitSystem.METRIC),
ndm=3, ndf=6,
nodes=[
Node(id=1, name="Base", coords=(0.0, 0.0, 0.0),

View file

@ -4,7 +4,7 @@
"name": "Space Frame 3D",
"description": "",
"author": "Ozan",
"units": "SI (m, N, kg, s, Pa)"
"units": "Metric (m, N, kg, s, Pa)"
},
"ndm": 3,
"ndf": 6,
@ -348,7 +348,8 @@
"Iz": 0.00025,
"Iy": 0.00025,
"G": 80000000000.0,
"J": 0.0004
"J": 0.0004,
"shape": null
},
{
"id": 2,
@ -359,7 +360,8 @@
"Iz": 0.0003,
"Iy": 8e-05,
"G": 80000000000.0,
"J": 1e-06
"J": 1e-06,
"shape": null
}
],
"elements": [
@ -373,7 +375,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 2,
@ -385,7 +388,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 3,
@ -397,7 +401,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 4,
@ -409,7 +414,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 5,
@ -421,7 +427,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 6,
@ -433,7 +440,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 7,
@ -445,7 +453,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 8,
@ -457,7 +466,8 @@
],
"section_id": 1,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 9,
@ -469,7 +479,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 10,
@ -481,7 +492,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 11,
@ -493,7 +505,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 12,
@ -505,7 +518,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 13,
@ -517,7 +531,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 14,
@ -529,7 +544,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 15,
@ -541,7 +557,8 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
},
{
"id": 16,
@ -553,9 +570,11 @@
],
"section_id": 2,
"geom_transf": "Linear",
"rho": 0.0
"rho": 0.0,
"consistent_mass": false
}
],
"mp_constraints": [],
"time_series": [
{
"id": 1,
@ -653,7 +672,7 @@
-0.29326050060188424,
-0.3555645611718062,
-0.4115042921209622,
-0.46028029767148104,
-0.4602802976714811,
-0.5012157143407345,
-0.5337653150252811,
-0.557522431249662,
@ -663,7 +682,7 @@
-0.5615384725351046,
-0.5402796151704672,
-0.5107978730732473,
-0.473659120634178,
-0.47365912063417787,
-0.42954246541985186,
-0.37922816465313647,
-0.3235841034221959,
@ -687,7 +706,7 @@
0.4906740795248149,
0.4799109935380196,
0.46174240873333633,
0.43654625061930163,
0.4365462506193016,
0.40480613582123315,
0.367102454112839,
0.32410204140538085,
@ -702,7 +721,7 @@
-0.16470790295421026,
-0.21419825881788915,
-0.2597053123214179,
-0.3005638423432181,
-0.30056384234321815,
-0.33618996805592183,
-0.3660893065503416,
-0.3898636224828937,
@ -764,10 +783,10 @@
-0.2882327405527284,
-0.2725045822392421,
-0.25269150009501856,
-0.2291557899194038,
-0.22915578991940377,
-0.20231370964878448,
-0.17262826564213357,
-0.14060135458277695,
-0.14060135458277698,
-0.10676539250226193,
-0.07167456884782708,
-0.03589586768362362,
@ -814,8 +833,8 @@
-0.210525969348039,
-0.1990380801906294,
-0.18456655167452068,
-0.1673760056265102,
-0.14777047796350654,
-0.16737600562651017,
-0.14777047796350656,
-0.12608814977607422,
-0.10269560775234517,
-0.07798172999451634,
@ -840,7 +859,7 @@
0.17010510853326,
0.15773721930062054,
0.14304555980288974,
0.1262899700796923,
0.12628997007969234,
0.10775947186525829,
0.08776736334007407,
0.06664599372956057,
@ -864,9 +883,9 @@
-0.15376873454743234,
-0.14537794939239213,
-0.13480790602063883,
-0.12225188492657595,
-0.12225188492657593,
-0.10793195476209022,
-0.09209512390578932,
-0.09209512390578933,
-0.07500914826118861,
-0.05695806544062365,
-0.0382375289144052,
@ -875,7 +894,7 @@
0.018910877417136193,
0.037288490913057575,
0.054850768151679684,
0.07133196903235188,
0.07133196903235187,
0.08648665679303691,
0.1000933005365811,
0.11195745718335284,
@ -904,7 +923,7 @@
-0.0739146103417395,
-0.08554333791262553,
-0.09568287328255036,
-0.10419251035749608,
-0.10419251035749609,
-0.11095890755818626,
-0.11589752681422662,
-0.11895360205510225,
@ -918,7 +937,7 @@
-0.07883379020837138,
-0.06726652633324699,
-0.054786883743239034,
-0.0416023242747564,
-0.041602324274756394,
-0.027928794018131982,
-0.013987224628675814,
-1.9014085379808582e-16,
@ -927,7 +946,7 @@
0.040063148664072165,
0.05210106213902843,
0.06317008686143476,
0.07310841606785966,
0.07310841606785967,
0.0817740280096844,
0.08904667019366247,
0.09482947682594194,
@ -945,14 +964,14 @@
0.04682284312839545,
0.035554843973652946,
0.023868952780831416,
0.011953985695908748,
0.011953985695908746,
5.108354497396276e-16,
-0.011804707389018669,
-0.023276536275777145,
-0.034239409087743526,
-0.04452744329813354,
-0.05398743030141857,
-0.0624810842126653,
-0.06248108421266531,
-0.06988702813284133,
-0.076102489952229,
-0.08104468467631713,
@ -972,7 +991,8 @@
-0.020399266308586933,
-0.010216306509086727
],
"file_path": null
"file_path": null,
"use_last": false
}
],
"load_patterns": [
@ -1082,6 +1102,7 @@
"pattern_ids": [
1
],
"pattern_factors": {},
"n_steps": 1,
"load_factor_increment": 1.0,
"system": "BandGeneral",
@ -1106,6 +1127,7 @@
"pattern_ids": [
2
],
"pattern_factors": {},
"dt": 0.01,
"n_steps": 400,
"system": "BandGeneral",
@ -1120,7 +1142,12 @@
"tolerance": 1e-06,
"max_iter": 25,
"rayleigh_alpha_m": 0.524,
"rayleigh_beta_k": 0.00106
"rayleigh_beta_k": 0.00106,
"rayleigh_beta_k_init": 0.0,
"rayleigh_beta_k_comm": 0.0,
"rayleigh_mode1_damping": null,
"preload_case_ids": [],
"remove_patterns": []
},
{
"id": 4,

View file

@ -106,7 +106,7 @@ def build_space_frame() -> Project:
add_el(b, d, sec=2, name=f"Beam-{story_base}-Y2")
return Project(
meta=ProjectMeta(name="Space Frame 3D", author="Ozan", units=UnitSystem.SI_M_N),
meta=ProjectMeta(name="Space Frame 3D", author="Ozan", units=UnitSystem.METRIC),
ndm=3, ndf=6,
nodes=nodes,
sections=[

View file

@ -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

View file

@ -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/<topic>`, 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.

View file

@ -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).

View file

@ -1,5 +1,3 @@
"""OTKO — a desktop GUI for OpenSeesPy."""
from otko._const import __version__
__all__ = ["__version__"]
__version__ = "0.1.0"

View file

@ -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"

View file

@ -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",
]

View file

@ -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

View file

@ -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:

View file

@ -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()

View file

@ -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]] = []

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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]] = []

View file

@ -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
]

View file

@ -35,6 +35,7 @@ from otko.core.geometry import (
DispBeamColumn,
ElasticBeamColumn,
Element,
ElementBase,
ForceBeamColumn,
GridLine,
GridSystem,
@ -50,8 +51,6 @@ from otko.core.loads import (
ConstantTimeSeries,
ImposedSupportMotionPattern,
LinearTimeSeries,
LoadCombination,
LoadCombinationItem,
LoadPattern,
NodalLoad,
PathTimeSeries,
@ -90,7 +89,24 @@ from otko.core.sections import (
SectionShape,
StraightLayer,
)
from otko.core.units import UnitLabels, UnitSystem, labels_for
from otko.core.units import (
DISPLAY,
LEGACY_UNIT_SYSTEMS,
NATIVE,
QUANTITIES,
RAD_TO_DEG,
UnitLabels,
UnitSystem,
display_factor,
display_for,
display_label,
display_with_alias,
format_display,
format_rotation,
labels_for,
native_label,
to_display,
)
__all__ = [
# Project + meta
@ -99,6 +115,19 @@ __all__ = [
"UnitSystem",
"UnitLabels",
"labels_for",
"DISPLAY",
"LEGACY_UNIT_SYSTEMS",
"NATIVE",
"QUANTITIES",
"RAD_TO_DEG",
"display_for",
"display_label",
"display_factor",
"display_with_alias",
"format_display",
"format_rotation",
"native_label",
"to_display",
# Geometry
"Node",
"EqualDOFConstraint",
@ -116,6 +145,7 @@ __all__ = [
"ensure_default_truss_material",
"ensure_default_timeseries_and_pattern",
"Element",
"ElementBase",
"TrussElement",
"CorotTrussElement",
"ElasticBeamColumn",
@ -167,8 +197,6 @@ __all__ = [
"PlainLoadPattern",
"UniformExcitationPattern",
"ImposedSupportMotionPattern",
"LoadCombination",
"LoadCombinationItem",
"NodalLoad",
"UniformElementLoad",
"ResponseSpectrum",

View file

@ -26,3 +26,21 @@ class Entity(BaseModel):
..., description="Unique tag within its kind. Used as the OpenSees tag."
)
name: str = Field(default="", description="Optional human-readable label.")
@property
def default_name(self) -> str:
"""Fallback label used when ``name`` is blank.
Display-only — reading it never writes back into ``name`` and it
is never persisted. Subclasses override this with their prefix
(e.g. ``N<id>`` for nodes, ``E<id>`` for elements).
"""
return ""
@property
def display_name(self) -> str:
"""Explicit ``name`` stripped, or ``default_name`` when blank.
Display-only fallback — reading this never mutates ``name``.
"""
return self.name.strip() or self.default_name

View file

@ -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"),
]

View file

@ -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']

View file

@ -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']

View file

@ -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'

File diff suppressed because it is too large Load diff

View file

@ -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)

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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)

View file

@ -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']

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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

Some files were not shown because too many files have changed in this diff Show more