diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f911d7..0097058 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ # Contributing -Early-stage project. The bar is architecture cleanliness, not feature -count. If your change breaks a layering rule below, it won't merge — -no matter how useful the feature. +Thanks for your interest. This project is in an early phase; the bar for +incoming changes is on architecture cleanliness rather than feature +breadth. ## Dev setup diff --git a/README.md b/README.md index 38f1fd9..4620453 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,14 @@

- A SAP2000-style desktop GUI for - OpenSeesPy. - Draw the model, click run, look at the diagrams. + A modern, SAP2000-style desktop GUI for + OpenSeesPy — + built for structural and earthquake engineers who want a visual + modeling environment without leaving the OpenSees ecosystem.

- Pre-alpha. Under active development. APIs and file formats will change. + Status: Pre-alpha. Active development. APIs and file formats will change.

--- @@ -18,20 +19,21 @@ ## Why -OpenSees does nonlinear FEM well. Its user interface is a script. -OTKO puts a visual front-end on it: +OpenSees is the gold-standard nonlinear FEM solver for earthquake +engineering, but its native interface is Tcl/Python scripts. +OTKO adds a visual front-end so you can: -- Draw nodes, frames, supports, and loads on a snapped grid. +- Click to draw nodes, frames, supports, and loads on a snapped grid. - Assign materials, sections, and load patterns through dialogs. - Run static, modal, pushover, and time-history analyses with progress - and cancel. -- Look at the results — deformed shape, mode shapes, force + and cancel support. +- Inspect results visually — 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. +- Save the model as a single `.osmodel` JSON file that round-trips + cleanly (diff-able in Git, scriptable from Python). -Underneath, the `core` Pydantic model works fine from a script or -notebook. The GUI is a front-end, not the whole product. +Behind the GUI, the same `core` Pydantic model is fully usable from a +script or Jupyter notebook — the GUI is one frontend, not the only one. ## What works today @@ -51,11 +53,11 @@ notebook. The GUI is a front-end, not the whole product. 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). +- **Persistence** — projects save as a single JSON `.osmodel` file + (Pydantic-validated, round-trip-clean). +- **Examples** — 20+ verified examples bundled, including the OpenSees + Wiki Examples-1 through Example-4 family and a fiber-section RC frame + pushover. See [`examples/README.md`](examples/README.md). ## Tech stack @@ -72,22 +74,22 @@ notebook. The GUI is a front-end, not the whole product. ## Architecture -Strict MVVM + service layer. `core` is pure Python — no Qt, -no OpenSeesPy imports — and unit-tests in isolation. +Strict MVVM + service layer. The `core` package is pure Python — no Qt, +no OpenSeesPy imports — and is fully unit-testable 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. +See [`docs/architecture.md`](docs/architecture.md) for the long form, +including the canonical OpenSeesPy command sequence the runner emits. ## Install (development) -**Desktop GUI** (Qt, PyVista, pyqtgraph, imageio): +**Desktop GUI** (includes Qt, PyVista, pyqtgraph, imageio): ```bash -git clone ssh://git@smill-home.ddns.net/smill/otko.git +git clone https://github.com/ogunc/otko.git cd otko python -m venv .venv @@ -97,21 +99,22 @@ source .venv/bin/activate # Linux / macOS pip install -e ".[gui,dev]" ``` -**Headless** (core + services only, no Qt): +**Headless / web reuse** (core + services only, no Qt pulled in): ```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. +This installs only the headless base set (pydantic, numpy, h5py, openseespy). +It is the correct install for web backends, scripts, and Jupyter notebooks 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`. +Python 3.10+ is required. On Windows use **3.12+** — the `openseespywin==3.8.0.0` +wheel has no 3.11 build (`Requires-Python >=3.12`). Pin both +`openseespy==3.8.0.0` and `openseespywin==3.8.0.0` (already pinned +in `pyproject.toml`). -## Quick start +## Quick start — the 60-second tour ```bash python -m otko @@ -119,20 +122,22 @@ python -m otko Then: -1. **File → Open** → `examples/cantilever.osmodel`. +1. **File → Open** → pick `examples/cantilever.osmodel`. 2. **Analyze → Cases** → run `Tip-Load`. -3. **Display → Show Force Diagram** → **M3**: linear moment, - 50 kN·m at the fixed end. **V2**: constant -10 kN. -4. **Display → Show Deformed Shape** → cantilever curve, as advertised. +3. **Display → Show Force Diagram** → component **M3** → linear moment + peaking at 50 kN·m at the fixed end. Component **V2** → constant + -10 kN. +4. **Display → Show Deformed Shape** → the classic cantilever curve. -Nonlinear version: open `examples/portal_pushover.osmodel`, -run `Push-X`, **Display → Show Pushover Curve**. Elastic ramp, -then a yield plateau as the base hinges form. +For a nonlinear walkthrough, open `examples/portal_pushover.osmodel`, +run the `Push-X` case, then **Display → Show Pushover Curve** — you'll +see the elastic ramp followed by a yield plateau as the fiber-section +hinges form at the column bases. ## Run the test suite ```bash -pytest tests/unit # pure logic, milliseconds +pytest tests/unit # pure-logic tests, milliseconds pytest tests/gui # Qt event-loop tests (pytest-qt) pytest tests/integration # real OpenSeesPy runs on bundled examples ``` @@ -142,47 +147,57 @@ CI runs lint + the non-`slow` subset on Linux / macOS / Windows ## 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. +See [`docs/roadmap.md`](docs/roadmap.md) for the phase-by-phase plan. +Phases 0–7 (modeling, analysis, post-processing) are largely done. +Phase 8 (earthquake-engineering primitives — isolators, ground-motion +library, IDA, fiber-section editor polish) is the active edge. -## Collaborators wanted +## We're looking for collaborators -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: +This project is most useful to researchers and engineers who already +work with OpenSees and want a faster path from "idea" to "model" — +**and who would rather build that path together than alone.** -- 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. +If any of the following sounds like you, please open an issue or +say hi: -Bug reports and reproducible test cases count as contributions. -See [`CONTRIBUTING.md`](CONTRIBUTING.md) for setup and the rules -enforced in review. +- 🌉 **Structural / earthquake engineers** comfortable with OpenSees Tcl + or OpenSeesPy who can spot when a feature is "almost right but not + quite" — that calibration feedback is gold. +- 🧪 **Researchers** running pushover, IDA, or response-spectrum studies + who want to validate the GUI against their hand-built scripts. +- 🐍 **Python / Qt developers** interested in scientific desktop apps, + PyVista / VTK rendering, or Pydantic-driven schema design. +- 📚 **Students** who want to learn structural FEM and modern GUI + architecture at the same time — example walkthroughs and tests are + designed to read as documentation. +- 🎨 **UX / icon designers** willing to help shape the dialog set, + toolbar icons, and overall visual language. + +Open issues, bug reports, and reproducible test cases are just as +valuable as code. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the dev +setup and the architectural rules enforced in review. ## License -OTKO is **GNU Affero General Public License v3.0** -([`LICENSE`](LICENSE)). Read the license itself, not just this: +OTKO is released under the **GNU Affero General Public +License v3.0** ([`LICENSE`](LICENSE)). -- Research, education, personal projects: fine, keep the copyright - notice. -- Fork and modify: fine. -- Distribute it (modified or not): release your full source under +Plain-language summary (not legal advice — read the license itself): + +- ✅ Use it for **research, education, and personal projects** with no + obligation other than keeping the copyright notice intact. +- ✅ Modify and fork it freely. +- ⚠️ If you **distribute** it, modified or not, you must release your + full source under AGPL-3.0. +- ⚠️ If you **run it as a network service** (e.g. host a modified + version as a SaaS), you must release your modifications under AGPL-3.0. -- Run a modified version as a network service: release your - modifications under AGPL-3.0. -Commercial forks stay open. If you need a different arrangement -(e.g. closed-source commercial license), open an issue. +In other words: anyone is free to learn from and build on this code, +but commercial forks and proprietary derivatives must contribute their +changes back to the community. If your use case needs a different +arrangement (e.g. a closed-source commercial license), please open an +issue to discuss. Copyright © 2026 Ozan and contributors. diff --git a/docs/architecture.md b/docs/architecture.md index 0c56d01..0bcddfb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -24,11 +24,11 @@ the reverse. ### Why this matters -- `core` tests without a display server, without OpenSees, without Qt. - CI runs `pytest tests/unit/` in milliseconds. -- Swapping solvers (e.g. `xara`, a future fork) touches - `services/opensees_runner.py` and nothing else. -- A future CLI or notebook front-end reuses `core` and `services` as-is. +- The `core` package is testable without a display server, without OpenSees, + and without Qt. CI runs `pytest tests/unit/` in milliseconds. +- Replacing OpenSeesPy with another solver (e.g. `xara`, a future fork) only + touches `services/opensees_runner.py`. +- A future CLI or Jupyter frontend reuses `core` and `services` unchanged. ## Package map diff --git a/docs/gap-analysis-gidopensees.md b/docs/gap-analysis-gidopensees.md index 012eda6..f750094 100644 --- a/docs/gap-analysis-gidopensees.md +++ b/docs/gap-analysis-gidopensees.md @@ -11,7 +11,7 @@ | **Category** | Schema group (material family, element type, etc.) | | **Object** | Name as it appears in gidopensees BOOK/CONDITION | | **OTKO name** | Corresponding class in `core/` (if any) | -| **In OTKO?** | ✅ fully supported · 🟡 partial · ❌ missing | +| **In Studio?** | ✅ fully supported · 🟡 partial · ❌ missing | | **In gidopensees?** | ✅ · ❌ | | **Priority** | P0 = already done · P1 = Phase 8 target · P2 = later | @@ -28,7 +28,7 @@ Priority rationale: ## 1. Uniaxial Materials -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Uniaxial / linear | Elastic | `ElasticUniaxial` | ✅ | ✅ | P0 | | Uniaxial / elastic-plastic | Elastic_Perfectly_Plastic | `ElasticPP` | ✅ | ✅ | P0 | @@ -43,7 +43,7 @@ Priority rationale: ## 2. Steel Uniaxial Materials -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Steel | Steel01 | `Steel01` | ✅ | ✅ | P0 | | Steel | Steel02 | `Steel02` | ✅ | ✅ | P0 | @@ -53,7 +53,7 @@ Priority rationale: ## 3. Concrete Uniaxial Materials -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Concrete | Concrete01_(Zero_tensile_strength) | `Concrete01` | ✅ | ✅ | P0 | | Concrete | Concrete02_(Linear_tension_softening) | `Concrete02` | ✅ | ✅ | P0 | @@ -63,7 +63,7 @@ Priority rationale: ## 4. Combined Materials -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Combination | Series | — | ❌ | ✅ | P1 | | Combination | Parallel | — | ❌ | ✅ | P1 | @@ -71,7 +71,7 @@ Priority rationale: ## 5. nD (Multi-dimensional) Materials -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | nD | Elastic_Isotropic | `ElasticIsotropic` | ✅ | ✅ | P0 | | nD | Elastic_Orthotropic | — | ❌ | ✅ | P2 | @@ -84,7 +84,7 @@ Priority rationale: ## 6. Sections -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Section | Elastic_Section | `ElasticSection` | ✅ | ✅ | P0 | | Section | Fiber | `FiberSection` | ✅ | ✅ | P0 | @@ -97,7 +97,7 @@ Priority rationale: ## 7. Beam-Column Elements -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Frame | Elastic_Beam-Column | `ElasticBeamColumn` | ✅ | ✅ | P0 | | Frame | Elastic_Timoshenko_Beam-Column | — | ❌ | ✅ | P1 | @@ -108,14 +108,14 @@ Priority rationale: ## 8. Truss Elements -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Truss | Truss | `TrussElement` | ✅ | ✅ | P0 | | Truss | Corotational_Truss | `CorotTrussElement` | ✅ | ✅ | P0 | ## 9. Surface / Plate Elements -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Surface | Quad | `QuadElement` | ✅ | ✅ | P0 | | Surface | Shell (ShellMITC4 / MITC4) | — | ❌ | ✅ | P1 | @@ -125,13 +125,13 @@ Priority rationale: ## 10. Solid Elements -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Solid | Standard_Brick_Element | — | ❌ | ✅ | P2 | ## 11. Zero-Length / Special Elements -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Special | Auto_Zero_Length (per-DOF uniaxial) | `ZeroLengthElement` | ✅ | ✅ | P0 | | Special | Auto_equal_constraint (auto equalDOF) | `EqualDOFConstraint` | ✅ | ✅ | P0 | @@ -140,7 +140,7 @@ Priority rationale: ## 12. Restraints (Boundary Conditions) -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Restraint | Point_Restraints | Node.restraint (6-tuple) | ✅ | ✅ | P0 | | Restraint | Line_Restraints (auto-apply to nodes on line) | — | ❌ | ✅ | P2 | @@ -148,7 +148,7 @@ Priority rationale: ## 13. Nodal Loads & Displacements -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Load | Point_Forces | `NodalLoad` | ✅ | ✅ | P0 | | Load | Line_Forces (nodal, along a line) | — | ❌ | ✅ | P2 | @@ -160,7 +160,7 @@ Priority rationale: ## 14. Ground Motions -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Ground motion | Point_Ground_Motion_from_Record | `PathTimeSeries` + `UniformExcitationPattern` | ✅ | ✅ | P0 | | Ground motion | Point_Sine_Ground_Motion | — (no `TrigTimeSeries`) | ❌ | ✅ | P1 | @@ -168,7 +168,7 @@ Priority rationale: ## 15. Constraints -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Constraint | Point_Equal_constraint (master + slave) | `EqualDOFConstraint` | ✅ | ✅ | P0 | | Constraint | Line_Equal_constraint (slave nodes on line) | — | ❌ | ✅ | P1 | @@ -179,7 +179,7 @@ Priority rationale: ## 16. Mass -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Mass | Point_Mass | node mass (Properties dock + SetMassCommand) | ✅ | ✅ | P0 | | Mass | Line_Mass (auto-lump to nodes) | — | ❌ | ✅ | P1 | @@ -188,7 +188,7 @@ Priority rationale: ## 17. Rayleigh Damping -| Category | Object (gidopensees) | OTKO name | In OTKO? | In gidopensees? | Priority | +| Category | Object (gidopensees) | OTKO name | In Studio? | In gidopensees? | Priority | |---|---|---|---|---|---| | Damping | Global αM + βK (TransientCase fields) | `TransientCase.rayleigh_alpha_m/beta_k` | ✅ | 🟡 | P0 | | Damping | Mode-1 stiffness-proportional βK auto-compute | `TransientCase.rayleigh_mode1_damping` | ✅ | ❌ | P0 | @@ -205,7 +205,7 @@ Priority rationale: | ❌ P1 targets (Phase 8 additions) | 23 | | ❌ P2 deferred | 21 | -**Top P1 targets** (highest EQ-engineering impact, not in OTKO yet): +**Top P1 targets** (highest EQ-engineering impact, not in Studio yet): 1. `ElasticPP_with_Gap` — bearing pad / isolation gap nonlinearity 2. `Viscous` / `Viscous_Damper` — supplemental damping devices diff --git a/docs/logo.svg b/docs/logo.svg index 3b6f06f..dc428a2 100644 --- a/docs/logo.svg +++ b/docs/logo.svg @@ -37,7 +37,7 @@ - OTKO + OpenSees Studio A SAP2000-STYLE GUI FOR OPENSEESPY diff --git a/docs/roadmap.md b/docs/roadmap.md index 00d0d55..7abc9e7 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,8 +1,9 @@ # Roadmap -Eight phases. 0–7 are the core GUI plus post-processing. Phase 8 is the -earthquake-engineering primitives — the part that makes it a research -tool instead of a model viewer. +OTKO is built in eight phases. Phases 0–7 ship the core GUI +plus all the post-processing tooling we need for verification work. +Phase 8 layers in the earthquake-engineering primitives that turn the +GUI from "OpenSees frontend" into a usable research tool. Status legend: ✅ done · 🟡 partial · ⬜ planned · ✂️ deferred / out-of-scope. diff --git a/examples/README.md b/examples/README.md index 7f043b9..7934af9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,34 +1,34 @@ # Example models -Pre-built `.osmodel` files plus the Python scripts that generate them. -Each one carries the case types the post-processing views need, so you -can exercise the GUI without defining materials, sections, loads, and -cases by hand. +Pre-built `.osmodel` files plus the Python scripts that produce them. +Each model is set up with whichever case types the post-processing +features need, so you can exercise the full GUI without manually +defining materials, sections, loads, and analysis cases. ## Files -| Model | Nodes | Elements | Cases | Shows | +| Model | Nodes | Elements | Cases | Best for demonstrating | |---|---|---|---|---| -| `cantilever.osmodel` | 6 | 5 | Static × 2, Modal | Point + distributed loads, force diagrams, deformed shape, mode shapes | -| `portal_frame.osmodel` | 4 | 3 | Static, Modal, Transient | All Display features, smallest 3D | -| `space_frame_3d.osmodel` | 12 | 16 | Static, Modal, Transient (5% damping) | 3D rendering, multiple modes, damped EQ time-history | +| `cantilever.osmodel` | 6 | 5 | Static × 2, Modal | Point & distributed loads, force diagrams, deformed shape, mode shapes | +| `portal_frame.osmodel` | 4 | 3 | Static, Modal, Transient | All Display features, simplest 3D | +| `space_frame_3d.osmodel` | 12 | 16 | Static, Modal, Transient (5% damping) | Realistic 3D rendering, multiple modes, damped EQ time-history | | `sdof_pushover.osmodel` | 2 | 1 | Pushover, Modal | Monotonic pushover curve, HystereticMaterial | -| `portal_pushover.osmodel` | 4 | 3 | Pushover, Modal | Fiber sections, BeamWithHinges, yielding pushover | -| `ex1a_canti2d.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | OpenSees Ex 1a, shared gravity + push + quake | -| `ex1b_portal2d.osmodel` | 4 | 3 | Static preload, Pushover, Transient EQ | OpenSees Ex 1b elastic portal, distributed gravity | -| `ex2a_canti2d_elastic_element.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | Ex 2a cantilever, dimensions as named parameters | -| `ex2b_canti2d_inelastic_section.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | Ex 2b, aggregated axial+flexure section | -| `ex2c_canti2d_inelastic_fiber_section.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | Ex 2c, fiber section, coupled axial-flexure | -| `ex3_canti2d_elastic_element.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | Ex 3 elastic build, unit-scaled parameters | -| `ex3_canti2d_inelastic_section.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | Ex 3 aggregated-section build | -| `ex3_canti2d_inelastic_fiber_section.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | Ex 3 fiber-section build | -| `ex4_portal2d_elastic_element.osmodel` | 4 | 3 | Static preload, Pushover, Transient sine | Ex 4 elastic portal, build/analysis split | -| `ex4_portal2d_inelastic_section.osmodel` | 4 | 3 | Static preload, Pushover, Transient sine | Ex 4 aggregated-section portal | -| `ex4_portal2d_inelastic_fiber_section.osmodel` | 4 | 3 | Static preload, Pushover, Transient sine | Ex 4 fiber-section portal | -| `ex1a_canti2d_eq.osmodel` | 2 | 1 | Static preload, Transient EQ | Ex 1a gravity + base excitation only | -| `eigen_two_storey_shear_frame.osmodel` | 6 | 6 | Modal | equalDOF floor constraints, shear-frame modes | -| `eigen_two_storey_one_bay_frame.osmodel` | 6 | 6 | Modal | Chopra 10.5 frame, sway modes, no constraints | -| `concrete04_cantilever.osmodel` | 2 | 1 | Static (gravity), Pushover | Concrete04 fiber section end-to-end | +| `portal_pushover.osmodel` | 4 | 3 | Pushover, Modal | Fiber sections, BeamWithHinges, nonlinear pushover with yielding | +| `ex1a_canti2d.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | Original OpenSees Ex 1a with shared gravity, push, and earthquake cases | +| `ex1b_portal2d.osmodel` | 4 | 3 | Static preload, Pushover, Transient EQ | Original OpenSees Ex 1b elastic portal frame with distributed gravity | +| `ex2a_canti2d_elastic_element.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | Variable-driven cantilever example with derived parameters | +| `ex2b_canti2d_inelastic_section.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | First nonlinear cantilever with aggregated uniaxial section | +| `ex2c_canti2d_inelastic_fiber_section.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | Fiber-section cantilever with coupled axial-flexural nonlinearity | +| `ex3_canti2d_elastic_element.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | Example 3 elastic build with unit-scaled parameters | +| `ex3_canti2d_inelastic_section.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | Example 3 aggregated-section nonlinear build | +| `ex3_canti2d_inelastic_fiber_section.osmodel` | 2 | 1 | Static preload, Pushover, Transient EQ | Example 3 fiber-section nonlinear build | +| `ex4_portal2d_elastic_element.osmodel` | 4 | 3 | Static preload, Pushover, Transient sine | Example 4 elastic portal frame with separated build/analysis workflow | +| `ex4_portal2d_inelastic_section.osmodel` | 4 | 3 | Static preload, Pushover, Transient sine | Example 4 aggregated-section portal frame variant | +| `ex4_portal2d_inelastic_fiber_section.osmodel` | 4 | 3 | Static preload, Pushover, Transient sine | Example 4 fiber-section portal frame variant | +| `ex1a_canti2d_eq.osmodel` | 2 | 1 | Static preload, Transient EQ | OpenSees Ex 1a style gravity + base excitation workflow | +| `eigen_two_storey_shear_frame.osmodel` | 6 | 6 | Modal | equalDOF floor constraints, mode shapes, eigenvalue workflow | +| `eigen_two_storey_one_bay_frame.osmodel` | 6 | 6 | Modal | classic elastic frame modal example, sway mode shapes | +| `concrete04_cantilever.osmodel` | 2 | 1 | Static (gravity), Pushover | Popovics Concrete04 fiber section; proof-of-concept for the Concrete04 end-to-end stack | ## Quick tour @@ -38,36 +38,37 @@ File → Open → cantilever.osmodel Analyze → Cases → run "Tip-Load" Display → Show Force Diagram → component "M3" → linear moment, max at fixed end (50 kN·m) → component "V2" → constant -10 kN along the whole span - → component "N" → ~zero (no axial load) + → component "N" → ~zero (no axial load applied) → component "T" → ~zero (no torsion → console hint, no diagram) -Display → Show Deformed Shape → cantilever curve +Display → Show Deformed Shape → classic cantilever curve ``` -Load runs along global Y (perpendicular to the beam, horizontal plane). -With the default 3D vertical-reference convention that lands on the -V2 / M3 pair — the in-plane bending pair. +The load is applied along the global Y axis (perpendicular to the beam, +in the horizontal plane). With the default 3D vertical-reference +convention this gives V2 / M3 — i.e. the "in-plane bending" pair. -UDL variant, parabolic moment: +**Distributed load (UDL) variant** — run the second case to see a +parabolic moment diagram: ``` Analyze → Cases → run "Uniform-Load" Display → Show Force Diagram → M3 → parabolic, max 25 kN·m at fixed end → V2 → linear, max 10 kN at fixed end ``` -### 2. Mode shapes — `space_frame_3d.osmodel` +### 2. Mode shapes — `portal_frame.osmodel` or `space_frame_3d.osmodel` ``` File → Open → space_frame_3d.osmodel Analyze → Cases → run "Modal-6" Display → Animate Mode Shape → mode 1 = X-sway, mode 2 = Y-sway - → Play, scrub timeline, change scale + → ▶ Play, scrub timeline, change scale ``` -### 3. Time-history and hysteresis — `space_frame_3d.osmodel` +### 3. Time-history & hysteresis — `portal_frame.osmodel` or `space_frame_3d.osmodel` ``` File → Open → space_frame_3d.osmodel Analyze → Cases → run "EQ-4s" (~5-10 sec on a modern laptop) Display → Time-History Plot - Node 12 (roof corner) + DOF 1 (X displacement) → "Add trace" - - Node 9 + DOF 1 → second trace, compare phase + - Node 9 + DOF 1 → another trace, compare phase Display → Hysteresis Plot - X = Node 12 / DOF 1, Y = Node 12 / DOF 3 → orbit ``` @@ -77,129 +78,137 @@ Display → Hysteresis Plot File → Open → sdof_pushover.osmodel Analyze → Cases → run "Push-X" Display → Show Pushover Curve - → linear from origin, then softens through yield + → linear segment from origin, then softens through yield ``` -Column stays elastic here. Real nonlinear hinges need -BeamWithHinges + fiber sections; the machinery exists, the -fiber-section editor is still rough. +Note: this demo keeps the column elastic (proper nonlinear hinges require +BeamWithHingesElement with fibre sections — infrastructure is in place, +fibre-section editor is future work). ### 5. Nonlinear pushover with fiber hinges — `portal_pushover.osmodel` ``` File → Open → portal_pushover.osmodel Analyze → Cases → run "Push-X" Display → Show Pushover Curve - → linear stiffness, then yield plateau as base hinges form - → peak base shear = concrete crushing + rebar yield + → initial linear stiffness, then yield plateau as base hinges form + → peak base shear corresponds to concrete crushing + rebar yield ``` -Columns are BeamWithHinges + FiberSections (concrete core, rebar -layers) wrapped in a SectionAggregator for torsion. +The columns use BeamWithHingesElements with FiberSections (concrete core ++ rebar layers) wrapped in a SectionAggregator (torsion spring). ### 6. Gravity + time-history chain — `ex1a_canti2d_eq.osmodel` -``` +```bash File → Open → ex1a_canti2d_eq.osmodel Analyze → Cases → run "Earthquake" Display → Time-History Plot - - Node 2 + DOF 1 (Ux) → tip horizontal response - - Node 2 + DOF 2 (Uy) → gravity should stay locked + - Node 2 + DOF 1 (Ux) → horizontal response of the cantilever tip + - Node 2 + DOF 2 (Uy) → verify gravity stays essentially locked ``` -Tiny model, exists for one reason: the standard transient recipe -`static preload → loadConst reset → UniformExcitation transient` -against a real ground-motion record in a `PathTimeSeries`. +This model is intentionally tiny but important for workflow coverage: +it demonstrates the general transient recipe of +`Static preload → loadConst reset → UniformExcitation transient` +using a real ground-motion record imported into a `PathTimeSeries`. -### 7. OpenSees Ex 1a bundle — `ex1a_canti2d.osmodel` -``` +### 7. Original OpenSees Ex 1a bundle — `ex1a_canti2d.osmodel` +```bash File → Open → ex1a_canti2d.osmodel Analyze → Cases → run "Push" or "Earthquake" Display → Show Pushover Curve / Time-History Plot ``` -Cantilever column with shared gravity preload and both lateral -variants. Small benchmark for checking pushover and transient agree -on the same geometry. +This is the original cantilever-column Example 1a packaged as one model +with a shared gravity preload plus both lateral load variants. It is a +good small benchmark for checking that pushover and transient workflows +behave consistently on the same geometry. -### 8. OpenSees Ex 1b bundle — `ex1b_portal2d.osmodel` -``` +### 8. Original OpenSees Ex 1b bundle — `ex1b_portal2d.osmodel` +```bash File → Open → ex1b_portal2d.osmodel Analyze → Cases → run "Push" or "Earthquake" Display → Show Pushover Curve / Time-History Plot ``` -Elastic portal frame. Gravity comes from a distributed beam load -instead of nodal loads, which is the whole point of keeping it -around. +This is the original elastic portal-frame Example 1b bundled as one +project. It is especially useful because the gravity preload is carried +by a distributed beam load instead of nodal loads only. -### 9. Ex 2a, parameter-driven — `ex2a_canti2d_elastic_element.osmodel` -``` +### 9. Variable-driven cantilever example — `ex2a_canti2d_elastic_element.osmodel` +```bash File → Open → ex2a_canti2d_elastic_element.osmodel Analyze → Cases → run "Push" or "Earthquake" Display → Show Pushover Curve / Time-History Plot ``` -Same physics as Ex 1a, but dimensions and derived quantities are -named parameters instead of literals. +This is the Ex2a cantilever tutorial recast as a project model. It is +useful when we want the same basic physics as Ex1a but with all major +dimensions and derived quantities exposed as named parameters. -### 10. Ex 2b, aggregated section — `ex2b_canti2d_inelastic_section.osmodel` -``` +### 10. Nonlinear aggregated-section cantilever — `ex2b_canti2d_inelastic_section.osmodel` +```bash File → Open → ex2b_canti2d_inelastic_section.osmodel Analyze → Cases → run "Push" or "Earthquake" Display → Show Pushover Curve / Time-History Plot ``` -First nonlinear cantilever in the series. Separate axial and flexural -uniaxial responses aggregated into one section on a force-based -beam-column. +This is the first nonlinear cantilever benchmark in the tutorial series. +It demonstrates how separate axial and flexural uniaxial responses can +be aggregated into one section and used by a force-based beam-column element. -### 11. Ex 2c, fiber section — `ex2c_canti2d_inelastic_fiber_section.osmodel` -``` +### 11. Fiber-section cantilever example — `ex2c_canti2d_inelastic_fiber_section.osmodel` +```bash File → Open → ex2c_canti2d_inelastic_fiber_section.osmodel Analyze → Cases → run "Push" or "Earthquake" Display → Show Pushover Curve / Time-History Plot ``` -Ex 2b's fiber counterpart. Coupled axial-flexure with concrete and -steel assigned to fibers and rebar layers directly. +This is the Ex2c fiber-section counterpart to Ex2b. It is useful for +checking coupled axial-flexural section behavior with inelastic concrete +and steel materials assigned directly to fibers and rebar layers. -### 12. Ex 3 family — `ex3_canti2d_*.osmodel` -``` +### 12. Example 3 build variants — `ex3_canti2d_*.osmodel` +```bash File → Open → ex3_canti2d_elastic_element.osmodel Analyze → Cases → run "Push" or "Earthquake" ``` -Same cantilever analyses on three build styles: elastic element, -aggregated uniaxial section, fiber section. All unit-scaled. +The Example 3 family is useful when we want the same cantilever analyses +to run on three different build styles: elastic element, aggregated +uniaxial section, and fiber section, all with unit-scaled parameters. -### 13. Modal shear building — `eigen_two_storey_shear_frame.osmodel` -``` +### 13. Modal shear-building example — `eigen_two_storey_shear_frame.osmodel` +```bash File → Open → eigen_two_storey_shear_frame.osmodel Analyze → Cases → run "Modal-2" Display → Animate Mode Shape - - mode 1 → stories sway in phase - - mode 2 → stories sway out of phase + - mode 1 → in-phase storey sway + - mode 2 → out-of-phase storey sway ``` -Validates modal workflows on a model small enough to check by hand, -with `equalDOF` doing the shear-frame duty. +This example is useful for validating modal workflows on a tiny model +that still needs multi-point constraints (`equalDOF`) to behave like an +idealized shear frame. -### 14. Modal frame, Chopra 10.5 — `eigen_two_storey_one_bay_frame.osmodel` +### 9. Modal elastic frame example — `eigen_two_storey_one_bay_frame.osmodel` +```bash +File → Open → eigen_two_storey_one_bay_frame.osmodel +Analyze → Cases → run "Modal-2" +Display → Animate Mode Shape + - mode 1 → in-phase sway of the two storeys + - mode 2 → upper storey reverses relative to the first storey ``` -File → Open → eigen_two_storey_one_bay_frame.osmodel -Analyze → Cases → run "Modal-2" -Display → Animate Mode Shape - - mode 1 → in-phase sway of both stories - - mode 2 → top story reverses against the first -``` -Companion to the shear building above. Ordinary beam-column behavior, -no multi-point constraints. +This is the Chopra Example 10.5 frame counterpart to the shear-building +example above. It gives us a small modal benchmark with ordinary +beam-column frame behavior and no multi-point constraints. -### 15. Ex 4 portal family — `ex4_portal2d_*.osmodel` +### 13. Example 4 portal-frame variants +```bash +File -> Open -> ex4_portal2d_elastic_element.osmodel +Analyze -> Cases -> run "Push" or "Sine-Uniform" +Display -> Show Pushover Curve / Time-History Plot ``` -File → Open → ex4_portal2d_elastic_element.osmodel -Analyze → Cases → run "Push" or "Sine-Uniform" -Display → Show Pushover Curve / Time-History Plot -``` -Keeps the OpenSees split between model-building and analysis files, -recast as project variants. Covers pinned-base sway, distributed -girder gravity, and support-motion dynamics without an external quake -file. The fiber transient is kept as a nonlinear stress test — it may -stop early and still produce usable partial histories. +The Example 4 family keeps the OpenSees split between model-building +and analysis files, but moves it into project variants. These are +useful benchmarks for pinned-base frame sway, distributed gravity on the +beam, and support-motion dynamics without depending on an external +earthquake file. The fiber-section transient is intentionally retained +as a strong nonlinear stress test and may stop early while still +producing useful partial histories. ## Regenerating the .osmodel files -Scripts are the source of truth, `.osmodel` files are build artifacts -checked in for convenience. Change a script, rerun it: +If you change the Python scripts, run them to regenerate the saved models: ```bash python examples/cantilever.py @@ -223,5 +232,6 @@ python examples/eigen_two_storey_shear_frame.py python examples/eigen_two_storey_one_bay_frame.py ``` -Each script builds the project, saves it, reloads it, and asserts a -clean round-trip. +Each script builds the project, saves it, reloads it, and asserts a clean +round-trip. The Python source is the source of truth; the `.osmodel` files +are generated artifacts checked in for convenience. diff --git a/examples/ex1b_portal2d.py b/examples/ex1b_portal2d.py index 12d6589..f923862 100644 --- a/examples/ex1b_portal2d.py +++ b/examples/ex1b_portal2d.py @@ -3,8 +3,8 @@ OpenSees Wiki: https://opensees.berkeley.edu/wiki/index.php?title=OpenSees_Example_1b._Elastic_Portal_Frame -This packages the original Example 1b portal frame into one OTKO -project with shared gravity preload and both lateral-load cases: +This packages the original Example 1b portal frame into one OpenSees +Studio project with shared gravity preload and both lateral-load cases: - static pushover - base-excitation earthquake analysis with ``BM68elc.acc`` diff --git a/src/otko/core/catalog/README.md b/src/otko/core/catalog/README.md index c81a93c..709d7cc 100644 --- a/src/otko/core/catalog/README.md +++ b/src/otko/core/catalog/README.md @@ -1,24 +1,25 @@ # core/catalog — GiD schema catalog -Auto-generated Pydantic v2 schema descriptions for every OpenSees -material and condition in the +This package contains **auto-generated Pydantic v2 schema descriptions** for +all OpenSees materials and conditions defined in the [gidopensees](https://github.com/rclab-auth/gidopensees) GiD preprocessor. -## Use +## Public import surface ```python from otko.core.catalog import CATALOG # Look up a Spec class by its gidopensees name Steel02Spec = CATALOG["Steel02"] -spec = Steel02Spec() # defaults +spec = Steel02Spec() # instantiate with defaults spec.model_dump_json() # serialize ``` -`CATALOG` maps each material's gidopensees name (e.g. `"Steel02"`) to its -generated `Spec` class. 58 entries, one per material in `OpenSees.mat`. +`CATALOG` is a `dict[str, type[BaseModel]]` mapping every material's +gidopensees name (e.g. `"Steel02"`) to its generated `Spec` class. +It contains exactly 58 entries (one per material in `OpenSees.mat`). -Per-book discriminated unions live in `generated/__init__.py`: +Per-book discriminated Union types are available in `generated/__init__.py`: ```python from otko.core.catalog.generated import UniaxialSteelMaterials @@ -28,7 +29,7 @@ Condition specs live under `generated/conditions/`. ## Regenerating -When upstream `schemas.json` changes, rerun codegen: +Run the codegen tool any time the upstream `schemas.json` changes: ```bash python -m tools.gidopensees_import.codegen \ @@ -38,15 +39,17 @@ python -m tools.gidopensees_import.codegen \ ## Do not hand-edit `generated/` -Codegen overwrites it. Put overrides, corrections, and extensions in -`curated/` (empty for now, reserved). +Files under `generated/` are overwritten on each codegen run. +Hand-curated overrides, corrections, or extensions belong in +`curated/` (currently empty — reserved for future use). ## Scope note -Spec classes are schema descriptions: field names, types, defaults, UI -metadata from the gidopensees definition files. They are not wired into -the runtime. Mapping a `Spec` to an actual `uniaxialMaterial` call is -still open — see the ADR. +Catalog Spec classes are **schema descriptions only**. They capture the +field names, types, defaults, and UI metadata from the gidopensees +definition files. They are **not yet wired into the OpenSees runtime**. +The mapping from a `Spec` to an actual `uniaxialMaterial` call is a +future deliverable tracked in the ADR. ## Attribution @@ -54,6 +57,7 @@ Schema data from [gidopensees](https://github.com/rclab-auth/gidopensees), Copyright (C) Reinforced Concrete Laboratory, Aristotle University of Thessaloniki (AUTh). -`CATALOG` holds the 58 material specs. Condition specs stay in their own -namespace (`generated/conditions/`, 39 specs) so the two don't pollute -each other. +`CATALOG` exposes the 58 material specs only; condition specs are intentionally +kept in a separate namespace (`generated/conditions/`, 39 specs) so that +material and boundary-condition objects remain independently importable and +do not pollute each other's namespace. diff --git a/src/otko/services/_run.py b/src/otko/services/_run.py index 13cef7f..c0e345e 100644 --- a/src/otko/services/_run.py +++ b/src/otko/services/_run.py @@ -55,7 +55,7 @@ class OpenSeesAnalysisRunner(OpenSeesEmitter): if isinstance(case, ResponseSpectrumCase): return self._run_response_spectrum(case) if isinstance(case, TransientCase): - target = results_dir or Path(tempfile.mkdtemp(prefix="otko_")) + target = results_dir or Path(tempfile.mkdtemp(prefix="osstudio_")) return self._run_transient(case, target) raise TypeError(f"Unsupported analysis case type: {type(case).__name__}") diff --git a/src/otko/services/export.py b/src/otko/services/export.py index f3ca54d..3715e31 100644 --- a/src/otko/services/export.py +++ b/src/otko/services/export.py @@ -363,7 +363,7 @@ def export_opspy(project: Project, case_id: int | None = None) -> str: Returns: The script source. The header pins ``openseespy==3.5.1.12``, - the OTKO version and the display units. + the Studio version and the display units. Raises: ValueError: If ``case_id`` matches no analysis case.