Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a41070788 |
215
README.md
|
|
@ -1,11 +1,13 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/logo.svg" alt="OTKO" width="640">
|
<img src="docs/logo.svg" alt="OTKO" width="600">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h3 align="center">Draw the model. Click run. Read the diagrams.</h3>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
A SAP2000-style desktop GUI for
|
<a href="LICENSE"><img alt="License: AGPL-3.0" src="https://img.shields.io/badge/license-AGPL--3.0-blue"></a>
|
||||||
<a href="https://openseespydoc.readthedocs.io/">OpenSeesPy</a>.
|
<img alt="Python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-blue">
|
||||||
Draw the model, click run, look at the diagrams.
|
<img alt="Status: pre-alpha" src="https://img.shields.io/badge/status-pre--alpha-orange">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|
@ -14,77 +16,47 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||

|
<table align="center">
|
||||||
|
<tr>
|
||||||
|
<td><img src="docs/screenshots/menu.png" alt="SAP2000-style cascading menus"></td>
|
||||||
|
<td><img src="docs/screenshots/toolbar.png" alt="Compact dark toolbar"></td>
|
||||||
|
<td><img src="docs/screenshots/grid.png" alt="Parametric grid generation"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><sub>SAP2000-style menus</sub></td>
|
||||||
|
<td align="center"><sub>Compact dark toolbar</sub></td>
|
||||||
|
<td align="center"><sub>Parametric grid wizard</sub></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
## Why
|
## What is OTKO?
|
||||||
|
|
||||||
OpenSees does nonlinear FEM well. Its user interface is a script.
|
OpenSees does nonlinear FEM well. Its user interface is a script.
|
||||||
OTKO puts a visual front-end on it:
|
OTKO puts a visual front-end on top of it: draw nodes, frames,
|
||||||
|
supports, and loads on a snapped grid; assign materials, sections,
|
||||||
- Draw nodes, frames, supports, and loads on a snapped grid.
|
and load patterns through dialogs; run the analysis with progress
|
||||||
- Assign materials, sections, and load patterns through dialogs.
|
and cancel; then read the results — deformed shapes, mode shapes,
|
||||||
- Run static, modal, pushover, and time-history analyses with progress
|
force diagrams, pushover curves, hysteresis loops.
|
||||||
and cancel.
|
|
||||||
- Look at the results — deformed shape, mode shapes, force
|
|
||||||
diagrams, pushover curves, time-history plots, hysteresis loops.
|
|
||||||
- Save the model as one `.osmodel` JSON file. Diffs cleanly in Git,
|
|
||||||
builds cleanly from Python.
|
|
||||||
|
|
||||||
Underneath, the `core` Pydantic model works fine from a script or
|
Underneath, the `core` Pydantic model works fine from a script or
|
||||||
notebook. The GUI is a front-end, not the whole product.
|
notebook without any GUI. The GUI is a front-end, not the whole
|
||||||
|
product.
|
||||||
|
|
||||||
## What works today
|
## Highlights
|
||||||
|
|
||||||
- **Modeling** — grids, nodes, frames (elastic + force-based), trusses,
|
| Area | What you get |
|
||||||
quads, zero-length sections, restraints, equalDOF constraints,
|
| ---- | ------------ |
|
||||||
distributed loads, ground motions (`PathTimeSeries` /
|
| **Modeling** | Grid wizards, nodes, frames (elastic + force-based), trusses, quads, zero-length sections, restraints, equalDOF constraints, distributed loads, ground motions (`PathTimeSeries` / `UniformExcitation`). |
|
||||||
`UniformExcitation`).
|
| **Materials & sections** | `Steel01`, `Steel02`, `Concrete01`, `Concrete02`, `ElasticPP`, `Hysteretic`, fiber sections (rectangular / circular patches + rebar), `SectionAggregator`, `BeamWithHinges`. |
|
||||||
- **Materials and sections** — `Steel01`, `Steel02`, `Concrete01`,
|
| **Analysis** | Static (load- or displacement-controlled), modal, pushover, transient time-history with mode-1 Rayleigh damping. Chained workflows: gravity preload → `loadConst -time 0.0` → pushover or transient. |
|
||||||
`Concrete02`, `ElasticPP`, `Hysteretic`, fiber sections (rectangular /
|
| **Post-processing** | Deformed shape with scale slider, animated mode shapes, axial / shear / moment diagrams, pushover curves, time-history plots, hysteresis loops, response-spectrum SRSS / CQC, snapshot + video export. |
|
||||||
circular patches + rebar layers), `SectionAggregator`,
|
| **Persistence** | One Pydantic-validated `.osmodel` JSON per project — diffs cleanly in Git, builds cleanly from Python. Analysis output in HDF5 (`*.osresults.h5`). |
|
||||||
`BeamWithHinges`.
|
|
||||||
- **Analyses** — static (load- or displacement-controlled), modal,
|
|
||||||
displacement-controlled pushover, transient time-history with
|
|
||||||
mode-1 Rayleigh damping. Chained workflows: gravity preload →
|
|
||||||
`loadConst -time 0.0` → pushover or transient.
|
|
||||||
- **Post-processing** — deformed shape (with scale slider), animated
|
|
||||||
mode shapes, axial / shear / moment diagrams, pushover curves
|
|
||||||
(in display units), time-history plots, hysteresis loops,
|
|
||||||
response-spectrum SRSS / CQC, snapshot + video export.
|
|
||||||
- **Persistence** — one JSON `.osmodel` per project, Pydantic-validated,
|
|
||||||
round-trips clean.
|
|
||||||
- **Examples** — 20+ verified examples, including OpenSees
|
|
||||||
Wiki Examples 1–4 and a fiber-section RC frame pushover.
|
|
||||||
See [`examples/README.md`](examples/README.md).
|
|
||||||
|
|
||||||
## Tech stack
|
20+ verified examples ship with the repo, including OpenSees Wiki
|
||||||
|
Examples 1–4 and a fiber-section RC frame pushover. See
|
||||||
|
[`examples/README.md`](examples/README.md).
|
||||||
|
|
||||||
| Layer | Library |
|
## Quick start
|
||||||
| ------------ | ------------------------------------ |
|
|
||||||
| GUI | PySide6 (Qt 6) |
|
|
||||||
| 3D viewport | PyVista + pyvistaqt (VTK) |
|
|
||||||
| 2D plots | pyqtgraph |
|
|
||||||
| Solver | OpenSeesPy 3.8.0.0 |
|
|
||||||
| Numerics | NumPy |
|
|
||||||
| Storage | Pydantic v2 (model), h5py (results) |
|
|
||||||
| Tests | pytest, pytest-qt |
|
|
||||||
| Lint / type | ruff, mypy |
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
Strict MVVM + service layer. `core` is pure Python — no Qt,
|
|
||||||
no OpenSeesPy imports — and unit-tests in isolation.
|
|
||||||
|
|
||||||
```
|
|
||||||
views (Qt) → viewmodels → services (OpenSeesRunner, Persistence) → core (model)
|
|
||||||
```
|
|
||||||
|
|
||||||
Long version in [`docs/architecture.md`](docs/architecture.md),
|
|
||||||
including the OpenSeesPy command order the runner emits.
|
|
||||||
|
|
||||||
## Install (development)
|
|
||||||
|
|
||||||
**Desktop GUI** (Qt, PyVista, pyqtgraph, imageio):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone ssh://git@smill-home.ddns.net/smill/otko.git
|
git clone ssh://git@smill-home.ddns.net/smill/otko.git
|
||||||
|
|
@ -94,26 +66,9 @@ python -m venv .venv
|
||||||
.venv\Scripts\activate # Windows
|
.venv\Scripts\activate # Windows
|
||||||
source .venv/bin/activate # Linux / macOS
|
source .venv/bin/activate # Linux / macOS
|
||||||
|
|
||||||
pip install -e ".[gui,dev]"
|
pip install -e ".[gui,dev]" # desktop: Qt + PyVista + dev tools
|
||||||
```
|
# pip install -e . # headless: core + services only
|
||||||
|
|
||||||
**Headless** (core + services only, no Qt):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -e .
|
|
||||||
```
|
|
||||||
|
|
||||||
That pulls pydantic, numpy, h5py, openseespy and nothing else.
|
|
||||||
Use it for scripts, notebooks, and web backends that reuse
|
|
||||||
`otko.core` or `otko.services` without the GUI.
|
|
||||||
|
|
||||||
Python 3.10+. On Windows use **3.12+** — `openseespywin==3.8.0.0`
|
|
||||||
has no 3.11 wheel (`Requires-Python >=3.12`). Both pins already
|
|
||||||
live in `pyproject.toml`.
|
|
||||||
|
|
||||||
## Quick start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python -m otko
|
python -m otko
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -121,15 +76,58 @@ Then:
|
||||||
|
|
||||||
1. **File → Open** → `examples/cantilever.osmodel`.
|
1. **File → Open** → `examples/cantilever.osmodel`.
|
||||||
2. **Analyze → Cases** → run `Tip-Load`.
|
2. **Analyze → Cases** → run `Tip-Load`.
|
||||||
3. **Display → Show Force Diagram** → **M3**: linear moment,
|
3. **Display → Show Force Diagram** → **M3**: 50 kN·m at the fixed end.
|
||||||
50 kN·m at the fixed end. **V2**: constant -10 kN.
|
**V2**: constant −10 kN.
|
||||||
4. **Display → Show Deformed Shape** → cantilever curve, as advertised.
|
4. **Display → Show Deformed Shape** → the cantilever curve.
|
||||||
|
|
||||||
Nonlinear version: open `examples/portal_pushover.osmodel`,
|
Nonlinear version: open `examples/portal_pushover.osmodel`, run
|
||||||
run `Push-X`, **Display → Show Pushover Curve**. Elastic ramp,
|
`Push-X`, **Display → Show Pushover Curve** — elastic ramp, then a
|
||||||
then a yield plateau as the base hinges form.
|
yield plateau as the base hinges form.
|
||||||
|
|
||||||
## Run the test suite
|
Python 3.10+. On Windows use **3.12+** — `openseespywin==3.8.0.0`
|
||||||
|
has no 3.11 wheel (`Requires-Python >=3.12`). Both pins already
|
||||||
|
live in `pyproject.toml`.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Strict MVVM + service layer, one direction only:
|
||||||
|
|
||||||
|
```
|
||||||
|
views (Qt) → viewmodels → services (OpenSeesRunner, Persistence) → core (model)
|
||||||
|
```
|
||||||
|
|
||||||
|
`core` is pure Python — no Qt, no OpenSeesPy imports — and unit-tests
|
||||||
|
in isolation. PySide6 + PyVista on the view, pyqtgraph for 2D plots,
|
||||||
|
Pydantic v2 for the model, h5py for results. Long version, including
|
||||||
|
the OpenSeesPy command order the runner emits, in
|
||||||
|
[`docs/architecture.md`](docs/architecture.md).
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
[`docs/roadmap.md`](docs/roadmap.md) has the phase-by-phase plan.
|
||||||
|
Phases 0–7 (modeling, analysis, post-processing) are mostly done.
|
||||||
|
Phase 8 — isolators, ground-motion library, IDA, fiber-section
|
||||||
|
editor polish — is where the open work is.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Collaborators welcome — most useful to people who already work with
|
||||||
|
OpenSees and want a shorter path from idea to model:
|
||||||
|
|
||||||
|
- **Structural / earthquake engineers** who can tell us when a feature
|
||||||
|
is almost right but not quite.
|
||||||
|
- **Researchers** running pushover, IDA, or response-spectrum studies
|
||||||
|
against hand-built scripts.
|
||||||
|
- **Python / Qt developers** into scientific desktop apps, VTK
|
||||||
|
rendering, or Pydantic schema design.
|
||||||
|
- **Students** learning FEM and GUI architecture — the examples and
|
||||||
|
tests are meant to read as documentation.
|
||||||
|
- **UX / icon designers** willing to argue about dialogs and toolbars.
|
||||||
|
|
||||||
|
Bug reports and reproducible test cases count as contributions. Setup
|
||||||
|
and the rules enforced in review: [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
||||||
|
|
||||||
|
Test suite:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pytest tests/unit # pure logic, milliseconds
|
pytest tests/unit # pure logic, milliseconds
|
||||||
|
|
@ -140,39 +138,10 @@ pytest tests/integration # real OpenSeesPy runs on bundled examples
|
||||||
CI runs lint + the non-`slow` subset on Linux / macOS / Windows
|
CI runs lint + the non-`slow` subset on Linux / macOS / Windows
|
||||||
× Python 3.10 / 3.11 / 3.12.
|
× Python 3.10 / 3.11 / 3.12.
|
||||||
|
|
||||||
## Roadmap
|
|
||||||
|
|
||||||
[`docs/roadmap.md`](docs/roadmap.md) has the phase-by-phase plan.
|
|
||||||
Phases 0–7 (modeling, analysis, post-processing) are mostly done.
|
|
||||||
Phase 8 (isolators, ground-motion library, IDA, fiber-section
|
|
||||||
editor polish) is where the open work is.
|
|
||||||
|
|
||||||
## Collaborators wanted
|
|
||||||
|
|
||||||
Most useful to people who already work with OpenSees and want a
|
|
||||||
shorter path from idea to model — and would rather build it together
|
|
||||||
than alone. Open an issue or say hi if you are:
|
|
||||||
|
|
||||||
- A **structural / earthquake engineer** who knows OpenSees Tcl
|
|
||||||
or OpenSeesPy and can tell us when a feature is almost right
|
|
||||||
but not quite.
|
|
||||||
- A **researcher** running pushover, IDA, or response-spectrum studies
|
|
||||||
who can check the GUI against hand-built scripts.
|
|
||||||
- A **Python / Qt developer** into scientific desktop apps,
|
|
||||||
VTK rendering, or Pydantic schema design.
|
|
||||||
- A **student** learning FEM and GUI architecture at the same time —
|
|
||||||
the examples and tests are meant to read as documentation.
|
|
||||||
- A **UX / icon designer** willing to argue about dialogs, toolbar
|
|
||||||
icons, and visual language.
|
|
||||||
|
|
||||||
Bug reports and reproducible test cases count as contributions.
|
|
||||||
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for setup and the rules
|
|
||||||
enforced in review.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
OTKO is **GNU Affero General Public License v3.0**
|
OTKO is **GNU Affero General Public License v3.0**
|
||||||
([`LICENSE`](LICENSE)). Read the license itself, not just this:
|
([`LICENSE`](LICENSE)). In short:
|
||||||
|
|
||||||
- Research, education, personal projects: fine, keep the copyright
|
- Research, education, personal projects: fine, keep the copyright
|
||||||
notice.
|
notice.
|
||||||
|
|
@ -183,6 +152,6 @@ OTKO is **GNU Affero General Public License v3.0**
|
||||||
modifications under AGPL-3.0.
|
modifications under AGPL-3.0.
|
||||||
|
|
||||||
Commercial forks stay open. If you need a different arrangement
|
Commercial forks stay open. If you need a different arrangement
|
||||||
(e.g. closed-source commercial license), open an issue.
|
(e.g. a closed-source commercial license), open an issue.
|
||||||
|
|
||||||
Copyright © 2026 Ozan and contributors.
|
Copyright © 2026 Ozan and contributors.
|
||||||
|
|
|
||||||
|
|
@ -5,41 +5,40 @@
|
||||||
|
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
<stop offset="0%" stop-color="#1F2A3D"/>
|
<stop offset="0%" stop-color="#1B2434"/>
|
||||||
<stop offset="100%" stop-color="#0F1620"/>
|
<stop offset="100%" stop-color="#0C1119"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="frameStroke" x1="0%" y1="0%" x2="100%" y2="100%">
|
<linearGradient id="frameStroke" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
<stop offset="0%" stop-color="#9DC8FF"/>
|
<stop offset="0%" stop-color="#A5D1FF"/>
|
||||||
<stop offset="100%" stop-color="#4F92E8"/>
|
<stop offset="100%" stop-color="#4F92E8"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="deformed" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
||||||
<stop offset="0%" stop-color="#4F92E8" stop-opacity="0.0"/>
|
|
||||||
<stop offset="50%" stop-color="#4F92E8" stop-opacity="0.6"/>
|
|
||||||
<stop offset="100%" stop-color="#4F92E8" stop-opacity="0.0"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<!-- Background panel — keeps contrast consistent on light and dark GitHub themes -->
|
<!-- Background panel — consistent contrast on light and dark themes -->
|
||||||
<rect x="0" y="0" width="720" height="180" rx="14" fill="url(#bg)"/>
|
<rect x="0" y="0" width="720" height="180" rx="16" fill="url(#bg)"/>
|
||||||
|
|
||||||
<!-- Mark: portal frame with a deformed-shape ghost -->
|
<!-- Mark: portal frame under lateral (pushover) load -->
|
||||||
<g transform="translate(40, 36)" stroke-linecap="round" stroke-linejoin="round" fill="none">
|
<g transform="translate(44, 30)" stroke-linecap="round" stroke-linejoin="round" fill="none">
|
||||||
<path d="M 6 100 C 22 100, 22 12, 60 12 L 60 12 C 98 12, 98 100, 114 100"
|
<!-- Deformed-shape ghost — pushover sway to the right -->
|
||||||
stroke="url(#deformed)" stroke-width="5" stroke-dasharray="3 5"/>
|
<path d="M 8 96 L 24 16 L 124 16 L 108 96"
|
||||||
<path d="M 6 100 L 6 8 L 114 8 L 114 100"
|
stroke="#4F92E8" stroke-width="5" stroke-dasharray="2 6" opacity="0.5"/>
|
||||||
stroke="url(#frameStroke)" stroke-width="7"/>
|
<!-- Push arrow -->
|
||||||
<rect x="-3" y="100" width="18" height="10" fill="#F2A93B" rx="1"/>
|
<path d="M -30 16 L -12 16" stroke="#F2A93B" stroke-width="6"/>
|
||||||
<rect x="105" y="100" width="18" height="10" fill="#F2A93B" rx="1"/>
|
<polygon points="-6,16 -18,8 -18,24" fill="#F2A93B"/>
|
||||||
<circle cx="6" cy="8" r="5" fill="#E8EDF5"/>
|
<!-- Frame -->
|
||||||
<circle cx="114" cy="8" r="5" fill="#E8EDF5"/>
|
<path d="M 8 96 L 8 16 L 108 16 L 108 96"
|
||||||
|
stroke="url(#frameStroke)" stroke-width="8"/>
|
||||||
|
<!-- Fixed supports -->
|
||||||
|
<polygon points="8,96 -2,108 18,108" fill="#F2A93B"/>
|
||||||
|
<polygon points="108,96 98,108 118,108" fill="#F2A93B"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Wordmark -->
|
<!-- Wordmark -->
|
||||||
<g font-family="Segoe UI, Inter, Helvetica, Arial, sans-serif">
|
<g font-family="Segoe UI, Inter, Helvetica, Arial, sans-serif">
|
||||||
<text x="200" y="92" font-size="56" font-weight="700" letter-spacing="-1">
|
<text x="200" y="105" font-size="62" font-weight="700" letter-spacing="-1">
|
||||||
<tspan fill="#E8EDF5">OTKO</tspan>
|
<tspan fill="#E8EDF5">OTKO</tspan>
|
||||||
</text>
|
</text>
|
||||||
<text x="202" y="124" font-size="16" font-weight="500" letter-spacing="3" fill="#8FA2BF">
|
<text x="202" y="133" font-size="15" font-weight="500" letter-spacing="3" fill="#8FA2BF">
|
||||||
A SAP2000-STYLE GUI FOR OPENSEESPY
|
A SAP2000-STYLE GUI FOR OPENSEESPY
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 306 KiB After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 210 KiB |