chore/adopt-local-tree #4

Merged
smill merged 13 commits from chore/adopt-local-tree into main 2026-09-16 15:48:16 -04:00
126 changed files with 359 additions and 397 deletions
Showing only changes of commit 61b850bf46 - Show all commits

style: resolve the remaining ruff findings
Some checks failed
CI / lint (pull_request) Has been cancelled
CI / type (pull_request) Has been cancelled
CI / test-headless (macos-latest, 3.10) (pull_request) Has been cancelled
CI / test-headless (macos-latest, 3.11) (pull_request) Has been cancelled
CI / test-headless (macos-latest, 3.12) (pull_request) Has been cancelled
CI / test-headless (ubuntu-latest, 3.10) (pull_request) Has been cancelled
CI / test-headless (ubuntu-latest, 3.11) (pull_request) Has been cancelled
CI / test-headless (ubuntu-latest, 3.12) (pull_request) Has been cancelled
CI / test-headless (windows-latest, 3.10) (pull_request) Has been cancelled
CI / test-headless (windows-latest, 3.11) (pull_request) Has been cancelled
CI / test-headless (windows-latest, 3.12) (pull_request) Has been cancelled
CI / test-gui (pull_request) Has been cancelled
CI / test-integration (macos-latest) (pull_request) Has been cancelled
CI / test-integration (ubuntu-latest) (pull_request) Has been cancelled
CI / test-integration (windows-latest) (pull_request) Has been cancelled

Auto-fixes (UP037, UP038, F401, I001, SIM105, RUF100) plus hand-fixes:
drop the now-unused `Union` imports, annotate mutable class attributes
with ClassVar, raise ValidationError instead of blind Exception in the
zero-length test, delete two dead locals, and bind the per-iteration
grid-builder closures to their loop variables (B023).
smillmorel 2026-09-16 13:20:36 -04:00

View file

@ -29,14 +29,6 @@ from otko.commands.grid import (
SetCoordSystemsCommand,
SetGridSystemCommand,
)
from otko.commands.series_and_patterns import (
AddLoadPatternCommand,
AddTimeSeriesCommand,
DeleteLoadPatternCommand,
DeleteTimeSeriesCommand,
UpdateLoadPatternCommand,
UpdateTimeSeriesCommand,
)
from otko.commands.loads import (
AddElementLoadsCommand,
AddNodalLoadsCommand,
@ -63,6 +55,14 @@ from otko.commands.sections import (
DeleteSectionsCommand,
UpdateSectionCommand,
)
from otko.commands.series_and_patterns import (
AddLoadPatternCommand,
AddTimeSeriesCommand,
DeleteLoadPatternCommand,
DeleteTimeSeriesCommand,
UpdateLoadPatternCommand,
UpdateTimeSeriesCommand,
)
from otko.commands.transforms import (
MirrorCommand,
MoveNodesCommand,

View file

@ -13,9 +13,7 @@ if TYPE_CHECKING:
class AddAnalysisCasesCommand(ProjectCommand):
"""Add one or more analysis cases."""
def __init__(
self, vm: "ProjectViewModel", cases: list[Any], *, text: str | None = None
) -> None:
def __init__(self, vm: ProjectViewModel, cases: list[Any], *, text: str | None = None) -> None:
super().__init__(vm, text or f"Add {len(cases)} analysis case(s)")
self._cases = list(cases)
@ -36,7 +34,7 @@ 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]] = []
@ -58,7 +56,7 @@ class DeleteAnalysisCasesCommand(ProjectCommand):
class UpdateAnalysisCaseCommand(ProjectCommand):
"""Replace an analysis case at a given id."""
def __init__(self, vm: "ProjectViewModel", new_case: Any) -> None:
def __init__(self, vm: ProjectViewModel, new_case: Any) -> None:
super().__init__(vm, f"Edit analysis case {new_case.id}")
self._new = new_case
self._old: Any | None = None

View file

@ -27,16 +27,16 @@ 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.")
return self._vm.project

View file

@ -13,9 +13,7 @@ if TYPE_CHECKING:
class AddCombinationsCommand(ProjectCommand):
"""Add one or more load combinations."""
def __init__(
self, vm: "ProjectViewModel", combos: list[Any], *, text: str | None = None
) -> None:
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)
@ -36,7 +34,7 @@ class AddCombinationsCommand(ProjectCommand):
class DeleteCombinationsCommand(ProjectCommand):
"""Remove a set of load combinations."""
def __init__(self, vm: "ProjectViewModel", combo_ids: set[int]) -> None:
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]] = []
@ -60,7 +58,7 @@ class DeleteCombinationsCommand(ProjectCommand):
class UpdateCombinationCommand(ProjectCommand):
"""Replace a load combination at a given id."""
def __init__(self, vm: "ProjectViewModel", new_combo: Any) -> None:
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

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}",
@ -48,7 +48,7 @@ class UpdateEqualDOFConstraintCommand(ProjectCommand):
"""
def __init__(
self, vm: "ProjectViewModel", index: int, new_constraint: EqualDOFConstraint
self, vm: ProjectViewModel, index: int, new_constraint: EqualDOFConstraint
) -> None:
super().__init__(vm, f"Edit equalDOF row {index + 1}")
self._index = index
@ -71,7 +71,7 @@ class UpdateEqualDOFConstraintCommand(ProjectCommand):
class DeleteEqualDOFConstraintCommand(ProjectCommand):
"""Remove ``equalDOF`` constraints by table-row index (no cascade)."""
def __init__(self, vm: "ProjectViewModel", indices: set[int]) -> None:
def __init__(self, vm: ProjectViewModel, indices: set[int]) -> None:
super().__init__(vm, f"Delete {len(indices)} constraint(s)")
self._indices = set(indices)
self._removed: list[tuple[int, EqualDOFConstraint]] = []

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,
@ -122,7 +122,7 @@ class AddElementLoadsCommand(ProjectCommand):
def __init__(
self,
vm: "ProjectViewModel",
vm: ProjectViewModel,
element_ids: set[int],
wy: float = 0.0,
wz: float = 0.0,

View file

@ -14,7 +14,7 @@ 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
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,7 +36,7 @@ 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]] = []
@ -64,7 +64,7 @@ class UpdateMaterialCommand(ProjectCommand):
otherwise this is an Add+Delete, not an update.
"""
def __init__(self, vm: "ProjectViewModel", new_material: Any) -> None:
def __init__(self, vm: ProjectViewModel, new_material: Any) -> None:
super().__init__(vm, f"Edit material {new_material.id}")
self._new = new_material
self._old: Any | None = None

View file

@ -14,9 +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)
@ -41,7 +39,7 @@ 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)
@ -95,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:
@ -130,7 +128,7 @@ class UpdateNodeFieldsCommand(ProjectCommand):
def __init__(
self,
vm: "ProjectViewModel",
vm: ProjectViewModel,
node_id: int,
fields: dict[str, Any],
) -> None:
@ -168,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

@ -14,7 +14,7 @@ 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
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,7 +36,7 @@ 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]] = []
@ -60,7 +60,7 @@ class DeleteSectionsCommand(ProjectCommand):
class UpdateSectionCommand(ProjectCommand):
"""Replace a section's parameters at a given id."""
def __init__(self, vm: "ProjectViewModel", new_section: Any) -> None:
def __init__(self, vm: ProjectViewModel, new_section: Any) -> None:
super().__init__(vm, f"Edit section {new_section.id}")
self._new = new_section
self._old: Any | None = None

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
@ -41,7 +41,7 @@ class UpdateTimeSeriesCommand(ProjectCommand):
The new series must carry the same id as the one being replaced.
"""
def __init__(self, vm: "ProjectViewModel", new_ts: Any) -> None:
def __init__(self, vm: ProjectViewModel, new_ts: Any) -> None:
super().__init__(vm, f"Edit time series {new_ts.id}")
self._new = new_ts
self._old: Any | None = None
@ -67,7 +67,7 @@ class DeleteTimeSeriesCommand(ProjectCommand):
"""Remove time series (no cascade — patterns referencing them must be
cleaned separately; ``validate_references`` catches strays before save)."""
def __init__(self, vm: "ProjectViewModel", ts_ids: set[int]) -> None:
def __init__(self, vm: ProjectViewModel, ts_ids: set[int]) -> None:
super().__init__(vm, f"Delete {len(ts_ids)} time serie(s)")
self._ts_ids = set(ts_ids)
self._removed: list[tuple[int, Any]] = []
@ -91,7 +91,7 @@ class DeleteTimeSeriesCommand(ProjectCommand):
class AddLoadPatternCommand(ProjectCommand):
"""Append a :class:`LoadPattern` to the project (undoable)."""
def __init__(self, vm: "ProjectViewModel", pattern: "LoadPattern") -> None:
def __init__(self, vm: ProjectViewModel, pattern: LoadPattern) -> None:
super().__init__(vm, f"Add pattern '{pattern.name or pattern.id}'")
self._pattern = pattern
@ -117,7 +117,7 @@ class UpdateLoadPatternCommand(ProjectCommand):
drops the pattern's stored loads.
"""
def __init__(self, vm: "ProjectViewModel", new_pattern: Any) -> None:
def __init__(self, vm: ProjectViewModel, new_pattern: Any) -> None:
super().__init__(vm, f"Edit pattern {new_pattern.id}")
self._new = new_pattern
self._old: Any | None = None
@ -142,7 +142,7 @@ class UpdateLoadPatternCommand(ProjectCommand):
class DeleteLoadPatternCommand(ProjectCommand):
"""Remove load patterns (their child nodal/element loads go with them)."""
def __init__(self, vm: "ProjectViewModel", pattern_ids: set[int]) -> None:
def __init__(self, vm: ProjectViewModel, pattern_ids: set[int]) -> None:
super().__init__(vm, f"Delete {len(pattern_ids)} pattern(s)")
self._pattern_ids = set(pattern_ids)
self._removed: list[tuple[int, Any]] = []

View file

@ -15,10 +15,9 @@ All copies inherit the source node's restraint, mass, and name.
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal
from typing import TYPE_CHECKING, Literal
from otko.commands.base import ProjectCommand
from otko.core import Node
if TYPE_CHECKING:
from otko.viewmodels import ProjectViewModel
@ -30,7 +29,7 @@ class MoveNodesCommand(ProjectCommand):
def __init__(
self,
vm: "ProjectViewModel",
vm: ProjectViewModel,
node_ids: set[int],
offset: tuple[float, float, float],
) -> None:
@ -63,7 +62,7 @@ class ReplicateCommand(ProjectCommand):
def __init__(
self,
vm: "ProjectViewModel",
vm: ProjectViewModel,
node_ids: set[int],
element_ids: set[int],
offset: tuple[float, float, float],
@ -147,7 +146,7 @@ class MirrorCommand(ProjectCommand):
def __init__(
self,
vm: "ProjectViewModel",
vm: ProjectViewModel,
node_ids: set[int],
element_ids: set[int],
plane: Plane,

View file

@ -9,7 +9,7 @@ in Phase 6/8.
from __future__ import annotations
from typing import Annotated, Literal, Union
from typing import Annotated, Literal
from pydantic import Field, PositiveFloat, PositiveInt, model_validator
@ -269,6 +269,6 @@ class ResponseSpectrumCase(Entity):
AnalysisCase = Annotated[
Union[StaticCase, ModalCase, TransientCase, PushoverCase, ResponseSpectrumCase],
StaticCase | ModalCase | TransientCase | PushoverCase | ResponseSpectrumCase,
Field(discriminator="type"),
]

View file

@ -10,7 +10,7 @@ Conventions follow OpenSeesPy ``element ...`` commands.
from __future__ import annotations
from typing import Annotated, Literal, Union
from typing import Annotated, Literal
from pydantic import Field, PositiveFloat, PositiveInt
@ -198,16 +198,14 @@ class QuadElement(Entity):
Element = Annotated[
Union[
TrussElement,
CorotTrussElement,
ElasticBeamColumn,
ForceBeamColumn,
DispBeamColumn,
ZeroLengthElement,
ZeroLengthSectionElement,
BeamWithHingesElement,
QuadElement,
],
TrussElement
| CorotTrussElement
| ElasticBeamColumn
| ForceBeamColumn
| DispBeamColumn
| ZeroLengthElement
| ZeroLengthSectionElement
| BeamWithHingesElement
| QuadElement,
Field(discriminator="type"),
]

View file

@ -97,7 +97,7 @@ class GridSystem(BaseModel):
return data
@model_validator(mode="after")
def _sort_and_dedupe(self) -> "GridSystem":
def _sort_and_dedupe(self) -> GridSystem:
for name in ("x_grid_lines", "y_grid_lines", "z_grid_lines"):
lines: list[GridLine] = list(getattr(self, name))
lines.sort(key=lambda ln: ln.ordinate)

View file

@ -12,7 +12,7 @@ pattern is deleted, its loads go with it.
from __future__ import annotations
from typing import Annotated, Literal, Union
from typing import Annotated, Literal
from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator
@ -110,7 +110,7 @@ class ResponseSpectrum(Entity):
TimeSeries = Annotated[
Union[LinearTimeSeries, ConstantTimeSeries, PathTimeSeries],
LinearTimeSeries | ConstantTimeSeries | PathTimeSeries,
Field(discriminator="type"),
]
@ -229,7 +229,7 @@ class LoadCombination(Entity):
)
@model_validator(mode="after")
def _check_items(self) -> "LoadCombination":
def _check_items(self) -> LoadCombination:
"""Reject duplicate case ids and non-finite factors."""
import math
@ -250,6 +250,6 @@ class LoadCombination(Entity):
LoadPattern = Annotated[
Union[PlainLoadPattern, UniformExcitationPattern, ImposedSupportMotionPattern],
PlainLoadPattern | UniformExcitationPattern | ImposedSupportMotionPattern,
Field(discriminator="type"),
]

View file

@ -11,7 +11,7 @@ https://openseespydoc.readthedocs.io/en/latest/src/uniaxialMaterial.html
from __future__ import annotations
from typing import Annotated, Literal, Union
from typing import Annotated, Literal
from pydantic import Field, PositiveFloat, model_validator
@ -130,7 +130,7 @@ class Concrete04(Entity):
)
@model_validator(mode="after")
def _tensile_params_consistent(self) -> "Concrete04":
def _tensile_params_consistent(self) -> Concrete04:
if self.fct is not None and self.et is None:
raise ValueError("et is required when fct is given.")
if self.et is not None and self.fct is None:
@ -229,18 +229,16 @@ class HystereticSM(Entity):
# ──────────────────────────── Discriminated union ────────────────────────────
Material = Annotated[
Union[
ElasticIsotropic,
ElasticUniaxial,
Steel01,
Steel02,
Concrete01,
Concrete02,
Concrete04,
ElasticPP,
HystereticMaterial,
HystereticSM,
],
ElasticIsotropic
| ElasticUniaxial
| Steel01
| Steel02
| Concrete01
| Concrete02
| Concrete04
| ElasticPP
| HystereticMaterial
| HystereticSM,
Field(discriminator="type"),
]
"""Tagged union of every material kind. Pydantic uses ``type`` to dispatch on JSON load."""

View file

@ -166,7 +166,7 @@ class Project(BaseModel):
return data
@model_validator(mode="after")
def _ensure_global_system(self) -> "Project":
def _ensure_global_system(self) -> Project:
"""Guarantee that a 'Global' entry exists as the first coord system."""
has_global = any(cs.name == "Global" for cs in self.coord_systems)
if not has_global:
@ -174,7 +174,7 @@ class Project(BaseModel):
return self
@model_validator(mode="after")
def _ensure_indexed(self) -> "Project":
def _ensure_indexed(self) -> Project:
"""Wrap every collection in an :class:`_IndexedList` and drop caches.
Runs after each validation pass (construction, ``model_validate``,
@ -223,7 +223,7 @@ class Project(BaseModel):
# ─────────────────── invariants ───────────────────
@model_validator(mode="after")
def _check_ndm_ndf(self) -> "Project":
def _check_ndm_ndf(self) -> Project:
valid = {(2, 2), (2, 3), (3, 3), (3, 6)}
if (self.ndm, self.ndf) not in valid:
raise ValueError(
@ -233,7 +233,7 @@ class Project(BaseModel):
return self
@model_validator(mode="after")
def _check_unique_ids(self) -> "Project":
def _check_unique_ids(self) -> Project:
for label, items in (
("node", self.nodes),
("material", self.materials),

View file

@ -16,7 +16,7 @@ exactly so the runner can emit them without further translation.
from __future__ import annotations
from typing import Annotated, Literal, Union
from typing import Annotated, Literal
from pydantic import BaseModel, ConfigDict, Field, PositiveFloat, PositiveInt
@ -61,7 +61,7 @@ class RectShape(BaseModel):
SectionShape = Annotated[
Union[PipeShape, AngleShape, RectShape],
PipeShape | AngleShape | RectShape,
Field(discriminator="kind"),
]
@ -165,12 +165,12 @@ class StraightLayer(BaseModel):
Patch = Annotated[
Union[RectangularPatch, CircularPatch],
RectangularPatch | CircularPatch,
Field(discriminator="kind"),
]
Layer = Annotated[
Union[StraightLayer],
StraightLayer,
Field(discriminator="kind"),
]
@ -225,6 +225,6 @@ class SectionAggregator(Entity):
Section = Annotated[
Union[ElasticSection, FiberSection, SectionAggregator],
ElasticSection | FiberSection | SectionAggregator,
Field(discriminator="type"),
]

View file

@ -12,8 +12,9 @@ the main thread.
from __future__ import annotations
import math
from collections.abc import Callable
from pathlib import Path
from typing import Any, Callable
from typing import Any
import numpy as np

View file

@ -238,7 +238,7 @@ def _record_calls(project: Project, case_id: int | None) -> list[RecordedCall]:
runner.build()
else:
case = _find_case(project, case_id)
if isinstance(case, (StaticCase, ModalCase, PushoverCase)):
if isinstance(case, StaticCase | ModalCase | PushoverCase):
# Safe to run end-to-end: dummy query results let the
# harvesting code finish; queries are filtered at render.
runner.run(case)

View file

@ -11,6 +11,7 @@ building the PathTimeSeries.
from __future__ import annotations
import contextlib
import re
from pathlib import Path
@ -64,10 +65,9 @@ def parse_peer_record(path: str | Path) -> tuple[float, int, list[float]]:
values: list[float] = []
for line in lines[data_start:]:
for tok in line.split():
try:
# Skip stray non-numeric tokens.
with contextlib.suppress(ValueError):
values.append(float(tok))
except ValueError:
pass # skip stray tokens
if not values:
raise ValueError("Header parsed but no numeric data lines found.")
return dt, npts, values
@ -83,10 +83,8 @@ def parse_plain_values(path: str | Path) -> list[float]:
vals: list[float] = []
for line in text.splitlines():
for tok in line.split():
try:
with contextlib.suppress(ValueError):
vals.append(float(tok))
except ValueError:
pass
if not vals:
raise ValueError(f"{path} contains no numeric values.")
return vals

View file

@ -54,7 +54,7 @@ def _angle_in_arc(angle: float, start: float, span: float) -> bool:
def bbox_for_section(
section: object,
project: "Project | None" = None,
project: Project | None = None,
) -> tuple[float, float] | None:
"""Return ``(width_y, height_z)`` of the section's local bounding box.

View file

@ -21,7 +21,6 @@ import numpy as np
from otko.core.sections import (
CircularPatch,
FiberSection,
Fibre,
RectangularPatch,
StraightLayer,
)

View file

@ -11,6 +11,7 @@ a re-paint without losing camera/selection state.
from __future__ import annotations
import contextlib
from pathlib import Path
from PySide6.QtCore import QObject, Signal, Slot
@ -40,10 +41,8 @@ class ProjectViewModel(QObject):
@Slot(bool)
def _on_stack_clean_changed(self, clean: bool) -> None:
# Guard against the late-fire that Qt sends during destruction.
try:
with contextlib.suppress(RuntimeError):
self._set_dirty(not clean)
except RuntimeError:
pass
# ── read ─────────────────────────────────────────────────────────
@property

View file

@ -47,9 +47,9 @@ from otko.views.dialogs import (
AssignSectionDialog,
AssignSupportDialog,
AssignZeroLengthSectionDialog,
LoadCombinationManagerDialog,
CoordinateGridSystemsDialog,
LinearTimeSeriesDialog,
LoadCombinationManagerDialog,
MaterialLibraryDialog,
MaterialTesterDialog,
MirrorDialog,

View file

@ -22,6 +22,7 @@ Convention:
from __future__ import annotations
import contextlib
import logging
from typing import Any
@ -254,10 +255,8 @@ class DiagramRenderer:
pass
self._actor = None
if self._label_actor is not None:
try:
with contextlib.suppress(Exception):
self._plotter.remove_actor(self._label_actor, render=False)
except Exception:
pass
self._label_actor = None
# ── helpers ─────────────────────────────────────────────────────

View file

@ -10,9 +10,10 @@ Mode-aware: MODEL / DEFORMED / MODAL change only the points array.
from __future__ import annotations
import contextlib
import enum
from dataclasses import dataclass
from typing import Any
from typing import Any, ClassVar
import numpy as np
import pyvista as pv
@ -24,11 +25,11 @@ from otko.core import (
ElasticBeamColumn,
ForceBeamColumn,
NodalLoad,
QuadElement,
UniformElementLoad,
PlainLoadPattern,
Project,
QuadElement,
TrussElement,
UniformElementLoad,
ZeroLengthElement,
ZeroLengthSectionElement,
)
@ -184,8 +185,8 @@ class ModelRenderer:
r, g, b = (int(round(x * 255)) for x in rgb)
return f"#{r:02x}{g:02x}{b:02x}"
_NODE_LUT = ["#d9d9d9", "#00ffff"] # gray normal, cyan selected
_FRAME_LUT = ["#338cd9", "#00ffff"] # blue normal, cyan selected
_NODE_LUT: ClassVar[list[str]] = ["#d9d9d9", "#00ffff"] # gray normal, cyan selected
_FRAME_LUT: ClassVar[list[str]] = ["#338cd9", "#00ffff"] # blue normal, cyan selected
def __init__(self, plotter: Any, style: RenderStyle | None = None) -> None:
self._plotter = plotter
@ -219,10 +220,8 @@ class ModelRenderer:
# plan view at Z=3 doesn't see the Z=0 grid cluttering the view.
self._working_plane: tuple[str, float] | None = None
try:
with contextlib.suppress(Exception):
self._plotter.enable_anti_aliasing("ssaa")
except Exception:
pass
# ── public API ───────────────────────────────────────────────────
def render(self, project: Project | None) -> None:
@ -329,10 +328,8 @@ class ModelRenderer:
"""
# Remove any previous marker.
if self._hover_actor is not None:
try:
with contextlib.suppress(Exception):
self._plotter.remove_actor(self._hover_actor, render=False)
except Exception:
pass
self._hover_actor = None
if world_point is None:
@ -376,10 +373,8 @@ class ModelRenderer:
sphere = pv.Sphere(radius=radius, theta_resolution=8, phi_resolution=8)
glyph = self._node_pd.glyph(geom=sphere, scale=False, orient=False)
if self._node_actor is not None:
try:
with contextlib.suppress(Exception):
self._plotter.remove_actor(self._node_actor, render=False)
except Exception:
pass
self._node_glyph = glyph
self._node_actor = self._plotter.add_mesh(
glyph,
@ -484,10 +479,17 @@ class ModelRenderer:
plane_axis = axis_idx
plane_offset_local = plane_off - cs_shift
def _on_active_plane(local_pt: tuple[float, float, float]) -> bool:
if plane_axis is None:
# Bind the per-iteration plane state as defaults so the closure
# cannot read a later iteration's values (B023); the helper is
# only ever called from within this loop body.
def _on_active_plane(
local_pt: tuple[float, float, float],
_axis: int | None = plane_axis,
_offset: float | None = plane_offset_local,
) -> bool:
if _axis is None or _offset is None:
return True
return abs(local_pt[plane_axis] - plane_offset_local) < 1e-6 # type: ignore[operator]
return abs(local_pt[_axis] - _offset) < 1e-6
# Collect active + dim segments separately so they get their
# own polydata + actor (different opacity / color).
@ -496,13 +498,21 @@ class ModelRenderer:
dim_pts: list[tuple[float, float, float]] = []
dim_cells: list[int] = []
def add_seg(p1: tuple[float, float, float], p2: tuple[float, float, float]) -> None:
def add_seg(
p1: tuple[float, float, float],
p2: tuple[float, float, float],
_active_pts: list[tuple[float, float, float]] = active_pts,
_active_cells: list[int] = active_cells,
_dim_pts: list[tuple[float, float, float]] = dim_pts,
_dim_cells: list[int] = dim_cells,
_cs: Any = cs,
) -> None:
on_active = _on_active_plane(p1) and _on_active_plane(p2)
bucket_pts = active_pts if on_active else dim_pts
bucket_cells = active_cells if on_active else dim_cells
bucket_pts = _active_pts if on_active else _dim_pts
bucket_cells = _active_cells if on_active else _dim_cells
i = len(bucket_pts)
bucket_pts.append(cs.coord.local_to_world(p1))
bucket_pts.append(cs.coord.local_to_world(p2))
bucket_pts.append(_cs.coord.local_to_world(p1))
bucket_pts.append(_cs.coord.local_to_world(p2))
bucket_cells.extend([2, i, i + 1])
z_planes = zs if zs else [0.0]
@ -614,7 +624,7 @@ class ModelRenderer:
continue
# By design: area-only / thickness-only / material-only
# elements carry no section.
if isinstance(el, (TrussElement, CorotTrussElement, QuadElement, ZeroLengthElement)):
if isinstance(el, TrussElement | CorotTrussElement | QuadElement | ZeroLengthElement):
continue
node_i = node_by_id.get(el.nodes[0])
node_j = node_by_id.get(el.nodes[1])
@ -665,18 +675,16 @@ class ModelRenderer:
diag = self._diag_of_points(self._node_original_points)
cap = max(diag * 0.08, 1e-6)
for el in project.elements:
if isinstance(el, (QuadElement, ZeroLengthElement, ZeroLengthSectionElement)):
if isinstance(el, QuadElement | ZeroLengthElement | ZeroLengthSectionElement):
continue
if not isinstance(
el,
(
ElasticBeamColumn,
DispBeamColumn,
ForceBeamColumn,
BeamWithHingesElement,
TrussElement,
CorotTrussElement,
),
ElasticBeamColumn
| DispBeamColumn
| ForceBeamColumn
| BeamWithHingesElement
| TrussElement
| CorotTrussElement,
):
continue
node_i = node_by_id.get(el.nodes[0])
@ -1094,15 +1102,11 @@ class ModelRenderer:
self._clear_label_actors()
for a in (self._node_actor, self._frame_actor):
if a is not None:
try:
with contextlib.suppress(Exception):
self._plotter.remove_actor(a, render=False)
except Exception:
pass
for a in self._aux_actors:
try:
with contextlib.suppress(Exception):
self._plotter.remove_actor(a, render=False)
except Exception:
pass
self._node_actor = None
self._frame_actor = None
self._aux_actors.clear()
@ -1120,10 +1124,8 @@ class ModelRenderer:
def _clear_label_actors(self) -> None:
for actor in (self._node_label_actor, self._element_label_actor):
if actor is not None:
try:
with contextlib.suppress(Exception):
self._plotter.remove_actor(actor, render=False)
except Exception:
pass
self._node_label_actor = None
self._element_label_actor = None

View file

@ -8,7 +8,6 @@ from PySide6.QtWidgets import (
QDialogButtonBox,
QDoubleSpinBox,
QFormLayout,
QInputDialog,
QLabel,
QLineEdit,
QVBoxLayout,

View file

@ -8,6 +8,8 @@ it maps cleanly onto our existing :class:`SetMassCommand`.
from __future__ import annotations
import contextlib
from PySide6.QtWidgets import (
QCheckBox,
QDialog,
@ -103,10 +105,8 @@ class AssignMassesDialog(QDialog):
self._my.setValue(self._mx.value())
self._mx.valueChanged.connect(self._my.setValue)
else:
try:
with contextlib.suppress(RuntimeError, TypeError):
self._mx.valueChanged.disconnect(self._my.setValue)
except (RuntimeError, TypeError):
pass
def mass_vector(self) -> tuple[float, float, float, float, float, float]:
"""Return the 6-tuple (Mx, My, Mz, Mxx, Myy, Mzz)."""

View file

@ -8,7 +8,6 @@ model's actual ``ndf`` at translation time.
from __future__ import annotations
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QButtonGroup,
QCheckBox,
@ -22,7 +21,6 @@ from PySide6.QtWidgets import (
QWidget,
)
# Preset → 6-tuple (Ux, Uy, Uz, Rx, Ry, Rz)
PRESETS: dict[str, tuple[bool, bool, bool, bool, bool, bool]] = {
"Free": (False, False, False, False, False, False),

View file

@ -21,7 +21,6 @@ from PySide6.QtWidgets import (
QListWidget,
QListWidgetItem,
QSpinBox,
QVBoxLayout,
QWidget,
)

View file

@ -4,6 +4,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from pydantic import ValidationError
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QDialog,
@ -19,7 +20,6 @@ from PySide6.QtWidgets import (
QVBoxLayout,
QWidget,
)
from pydantic import ValidationError
from otko.commands import (
AddAnalysisCasesCommand,
@ -34,7 +34,7 @@ from otko.core import (
TransientCase,
)
from otko.viewmodels import ProjectViewModel
from otko.views.dialogs.case_forms import FORM_REGISTRY, form_for
from otko.views.dialogs.case_forms import form_for
if TYPE_CHECKING:
from otko.core import Project

View file

@ -18,6 +18,8 @@ Top-level controls:
from __future__ import annotations
from typing import ClassVar
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor
from PySide6.QtWidgets import (
@ -27,7 +29,6 @@ from PySide6.QtWidgets import (
QComboBox,
QDialog,
QDialogButtonBox,
QDoubleSpinBox,
QFormLayout,
QGroupBox,
QHBoxLayout,
@ -52,7 +53,6 @@ from otko.core.geometry import (
GridSystem,
)
_LINE_TYPE_CHOICES = ["Primary", "Secondary"]
_BUBBLE_LOC_CHOICES = ["Start", "End"]
@ -60,7 +60,14 @@ _BUBBLE_LOC_CHOICES = ["Start", "End"]
class _AxisGridTable(QWidget):
"""A single X/Y/Z spreadsheet — one row per GridLine."""
COLUMNS = ["Grid ID", "Ordinate", "Line Type", "Visibility", "Bubble Loc", "Color"]
COLUMNS: ClassVar[list[str]] = [
"Grid ID",
"Ordinate",
"Line Type",
"Visibility",
"Bubble Loc",
"Color",
]
def __init__(self, axis: str, parent: QWidget | None = None) -> None:
super().__init__(parent)

View file

@ -17,8 +17,8 @@ from PySide6.QtWidgets import (
QDialog,
QDialogButtonBox,
QFormLayout,
QLineEdit,
QLabel,
QLineEdit,
QVBoxLayout,
QWidget,
)

View file

@ -13,7 +13,6 @@ from PySide6.QtWidgets import (
QDoubleSpinBox,
QFormLayout,
QGroupBox,
QHBoxLayout,
QLabel,
QVBoxLayout,
QWidget,

View file

@ -7,9 +7,9 @@ committed. Each Add/Edit/Delete is its own undoable step.
from __future__ import annotations
from pydantic import ValidationError
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QComboBox,
QDialog,
QDialogButtonBox,
QHBoxLayout,
@ -23,7 +23,6 @@ from PySide6.QtWidgets import (
QVBoxLayout,
QWidget,
)
from pydantic import ValidationError
from otko.commands import (
AddMaterialsCommand,

View file

@ -120,7 +120,7 @@ class PathTimeSeriesDialog(QDialog):
return
try:
dt, npts, vals = parse_peer_record(fname)
except Exception as exc: # noqa: BLE001
except Exception as exc:
QMessageBox.critical(self, "PEER import failed", str(exc))
return
self._values = vals
@ -142,7 +142,7 @@ class PathTimeSeriesDialog(QDialog):
return
try:
vals = parse_plain_values(fname)
except Exception as exc: # noqa: BLE001
except Exception as exc:
QMessageBox.critical(self, "Import failed", str(exc))
return
self._values = vals

View file

@ -11,7 +11,6 @@ from PySide6.QtWidgets import (
QDialog,
QDialogButtonBox,
QDoubleSpinBox,
QFormLayout,
QGroupBox,
QHBoxLayout,
QLabel,

View file

@ -193,7 +193,7 @@ class RunAnalysisDialog(QDialog):
results_dir = self._vm.path.parent / f"{self._vm.path.stem}_results"
try:
self._runner.run(self._vm.project, case, results_dir=results_dir)
except Exception as exc: # noqa: BLE001
except Exception as exc:
self._log.appendPlainText(f"Could not start: {exc}")
def _on_evaluate_combo(self) -> None:
@ -233,7 +233,7 @@ class RunAnalysisDialog(QDialog):
self._results = combined
self._log.appendPlainText(f"--- Done. Returned {type(combined).__name__}. ---")
self._runner.finished.emit(combined)
except Exception as exc: # noqa: BLE001
except Exception as exc:
import traceback
self._log.appendPlainText("--- FAILED ---")

View file

@ -12,7 +12,6 @@ from __future__ import annotations
from typing import Any
import numpy as np
import pyqtgraph as pg
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
@ -324,7 +323,6 @@ class FiberSectionEditor(QDialog):
self._props_label.setText("Add patches or layers to see the preview.")
return
f = props.fibre_yz
# Color by material: assign a palette index per unique material_id.
mat_ids = []
for p in self._patches:

View file

@ -2,6 +2,7 @@
from __future__ import annotations
from pydantic import ValidationError
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QDialog,
@ -17,7 +18,6 @@ from PySide6.QtWidgets import (
QVBoxLayout,
QWidget,
)
from pydantic import ValidationError
from otko.commands import (
AddSectionsCommand,

View file

@ -23,7 +23,6 @@ from PySide6.QtWidgets import (
from otko.core import Project, UniformExcitationPattern
_DIRECTION_CHOICES: list[tuple[int, str]] = [
(1, "1 — X (horizontal)"),
(2, "2 — Y (vertical for ndm=2, lateral for ndm=3)"),

View file

@ -2,7 +2,7 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Callable
from typing import TYPE_CHECKING
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (

View file

@ -10,8 +10,6 @@ Exposes two signals:
from __future__ import annotations
import math
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (
QComboBox,

View file

@ -15,6 +15,7 @@ For 2D it's [N1, Vy1, Mz1, N2, Vy2, Mz2].
from __future__ import annotations
import contextlib
from typing import Any
import pyqtgraph as pg
@ -337,8 +338,6 @@ class HysteresisView(QWidget):
def _clear_curve(self) -> None:
if self._curve is not None:
try:
with contextlib.suppress(Exception):
self._plot.removeItem(self._curve)
except Exception:
pass
self._curve = None

View file

@ -8,9 +8,8 @@ dumb: no direct Project mutation, no Qt ↔ OpenSees coupling.
from __future__ import annotations
from typing import Callable
from collections.abc import Callable
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QComboBox,
QDoubleSpinBox,
@ -26,7 +25,6 @@ from PySide6.QtWidgets import (
from otko.core import Project
# Element types the Properties dock lets the user switch between.
# "ElasticBeamColumn" needs a section; "Truss" / "CorotTruss" need a
# uniaxial material + area. The Convert command drops/adds fields to

View file

@ -26,7 +26,7 @@ from PySide6.QtWidgets import (
QWidget,
)
from otko.core import UnitLabels, UnitSystem, labels_for
from otko.core import UnitSystem, labels_for
from otko.services.results import PushoverResults
if TYPE_CHECKING:

View file

@ -8,7 +8,7 @@ Two panels:
from __future__ import annotations
import pyqtgraph as pg
from PySide6.QtCore import Signal
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (
QHBoxLayout,
QHeaderView,
@ -20,7 +20,6 @@ from PySide6.QtWidgets import (
QVBoxLayout,
QWidget,
)
from PySide6.QtCore import Qt
from otko.core import ResponseSpectrum
from otko.services.results import ResponseSpectrumResults

View file

@ -197,7 +197,7 @@ def _fmt(value: Any) -> str:
return "—"
if isinstance(value, float):
return f"{value:g}"
if isinstance(value, (list, tuple)):
if isinstance(value, list | tuple):
return ", ".join(_fmt(v) for v in value)
return str(value)
@ -1035,7 +1035,7 @@ class TableDock(QWidget):
try:
key = int(pid)
label = name_of.get(key, str(key)) or str(key)
parts.append(f"{label}×{float(factor):g}") # noqa: RUF001
parts.append(f"{label}×{float(factor):g}")
except (TypeError, ValueError):
continue
return ", ".join(parts) if parts else "—"

View file

@ -10,9 +10,9 @@ runner to record additional series (planned for a later phase).
from __future__ import annotations
import contextlib
from typing import Any
import numpy as np
import pyqtgraph as pg
from PySide6.QtCore import Signal
from PySide6.QtWidgets import (
@ -208,9 +208,7 @@ class TimeHistoryView(QWidget):
def _clear_traces(self) -> None:
for _, _, item in self._traces:
try:
with contextlib.suppress(Exception):
self._plot.removeItem(item)
except Exception:
pass
self._traces.clear()
self._trace_list.clear()

View file

@ -105,7 +105,7 @@ class MainWindow(
if state is not None:
self.restoreState(state)
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802 (Qt override)
def closeEvent(self, event: QCloseEvent) -> None: # (Qt override)
"""Prompt to save unsaved work, then persist the window layout.
The prompt is shown only for a visible window: Qt also closes hidden

View file

@ -35,8 +35,8 @@ class CanvasTool(QObject):
def __init__(
self,
canvas: "ModelCanvas",
vm: "ProjectViewModel",
canvas: ModelCanvas,
vm: ProjectViewModel,
parent: QObject | None = None,
) -> None:
super().__init__(parent)
@ -87,7 +87,7 @@ class ToolController(QObject):
toolChanged = Signal(object) # emits the new CanvasTool (or None for default)
def __init__(
self, canvas: "ModelCanvas", vm: "ProjectViewModel", parent: QObject | None = None
self, canvas: ModelCanvas, vm: ProjectViewModel, parent: QObject | None = None
) -> None:
super().__init__(parent)
self._canvas = canvas

View file

@ -41,9 +41,9 @@ class DrawFrameTool(LinearElementDrawTool):
def __init__(
self,
canvas: "ModelCanvas",
vm: "ProjectViewModel",
parent: "QObject | None" = None,
canvas: ModelCanvas,
vm: ProjectViewModel,
parent: QObject | None = None,
) -> None:
super().__init__(canvas, vm, parent)

View file

@ -115,9 +115,9 @@ class DrawNodeTool(CanvasTool):
def __init__(
self,
canvas: "ModelCanvas",
vm: "ProjectViewModel",
parent: "QObject | None" = None,
canvas: ModelCanvas,
vm: ProjectViewModel,
parent: QObject | None = None,
) -> None:
super().__init__(canvas, vm, parent)

View file

@ -37,9 +37,9 @@ class DrawTrussTool(LinearElementDrawTool):
def __init__(
self,
canvas: "ModelCanvas",
vm: "ProjectViewModel",
parent: "QObject | None" = None,
canvas: ModelCanvas,
vm: ProjectViewModel,
parent: QObject | None = None,
) -> None:
super().__init__(canvas, vm, parent)

View file

@ -6,12 +6,12 @@ import pytest
pytest.importorskip("PySide6")
from otko.commands import ( # noqa: E402
from otko.commands import (
AddAnalysisCasesCommand,
DeleteAnalysisCasesCommand,
UpdateAnalysisCaseCommand,
)
from otko.core import ( # noqa: E402
from otko.core import (
LinearTimeSeries,
ModalCase,
NodalLoad,
@ -20,7 +20,7 @@ from otko.core import ( # noqa: E402
StaticCase,
TransientCase,
)
from otko.viewmodels import ProjectViewModel # noqa: E402
from otko.viewmodels import ProjectViewModel
def _vm_with_pattern() -> ProjectViewModel:

View file

@ -12,21 +12,21 @@ import pytest
pytest.importorskip("PySide6")
pytest.importorskip("openseespy.opensees")
from otko.commands import ( # noqa: E402
from otko.commands import (
AddAnalysisCasesCommand,
AddElementsCommand,
AddNodalLoadsCommand,
AddNodesCommand,
AddSectionsCommand,
)
from otko.core import ( # noqa: E402
from otko.core import (
ElasticBeamColumn,
ElasticSection,
Node,
StaticCase,
)
from otko.services.results import StaticResults # noqa: E402
from otko.viewmodels import AnalysisRunner, ProjectViewModel # noqa: E402
from otko.services.results import StaticResults
from otko.viewmodels import AnalysisRunner, ProjectViewModel
@pytest.fixture

View file

@ -6,15 +6,14 @@ import pytest
pytest.importorskip("PySide6")
from otko.commands import AddNodalLoadsCommand # noqa: E402
from otko.core import ( # noqa: E402
from otko.commands import AddNodalLoadsCommand
from otko.core import (
LinearTimeSeries,
Node,
PlainLoadPattern,
Project,
)
from otko.viewmodels import ProjectViewModel # noqa: E402
from otko.views.dialogs.assign_load import AssignLoadDialog # noqa: E402
from otko.viewmodels import ProjectViewModel
from otko.views.dialogs.assign_load import AssignLoadDialog
@pytest.mark.gui

View file

@ -6,7 +6,7 @@ import pytest
pytest.importorskip("PySide6")
from otko.views.dialogs.assign_masses import AssignMassesDialog # noqa: E402
from otko.views.dialogs.assign_masses import AssignMassesDialog
@pytest.mark.gui

View file

@ -6,12 +6,12 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import ( # noqa: E402
from otko.core import (
ElasticSection,
Node,
Project,
)
from otko.views.dialogs.assign_zls import ( # noqa: E402
from otko.views.dialogs.assign_zls import (
AssignZeroLengthSectionDialog,
)

View file

@ -12,7 +12,7 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import ( # noqa: E402
from otko.core import (
CoordinateGridSystem,
CoordinateSystem,
GridSystem,

View file

@ -6,13 +6,13 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import ( # noqa: E402
from otko.core import (
NodalLoad,
PlainLoadPattern,
StaticCase,
TransientCase,
)
from otko.views.dialogs.case_forms import TransientCaseForm # noqa: E402
from otko.views.dialogs.case_forms import TransientCaseForm
def _patterns(): # type: ignore[no-untyped-def]

View file

@ -11,15 +11,14 @@ import pytest
pytest.importorskip("PySide6")
from otko.commands import ( # noqa: E402
from otko.commands import (
AddElementsCommand,
AddNodalLoadsCommand,
AddNodesCommand,
DeleteElementsCommand,
DeleteNodesCommand,
SetRestraintCommand,
)
from otko.core import ( # noqa: E402
from otko.core import (
LinearTimeSeries,
NodalLoad,
Node,
@ -27,7 +26,7 @@ from otko.core import ( # noqa: E402
Steel01,
TrussElement,
)
from otko.viewmodels import ProjectViewModel # noqa: E402
from otko.viewmodels import ProjectViewModel
# ─────────────────────────── helpers ────────────────────────────────

View file

@ -6,21 +6,21 @@ import pytest
pytest.importorskip("PySide6")
from otko.commands import ( # noqa: E402
from otko.commands import (
AddElementsCommand,
AddMaterialsCommand,
AddNodesCommand,
AddSectionsCommand,
ConvertElementTypeCommand,
)
from otko.core import ( # noqa: E402
from otko.core import (
ElasticBeamColumn,
ElasticSection,
ElasticUniaxial,
Node,
TrussElement,
)
from otko.viewmodels import ProjectViewModel # noqa: E402
from otko.viewmodels import ProjectViewModel
def _vm_setup() -> ProjectViewModel:

View file

@ -6,9 +6,9 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import LinearTimeSeries, Project # noqa: E402
from otko.views.dialogs.linear_time_series import LinearTimeSeriesDialog # noqa: E402
from otko.views.dialogs.plain_pattern import PlainPatternDialog # noqa: E402
from otko.core import LinearTimeSeries, Project
from otko.views.dialogs.linear_time_series import LinearTimeSeriesDialog
from otko.views.dialogs.plain_pattern import PlainPatternDialog
@pytest.mark.gui

View file

@ -11,11 +11,11 @@ import pytest
pytest.importorskip("PySide6")
from otko.commands import AddNodesCommand # noqa: E402
from otko.core import ElasticBeamColumn, ElasticSection, Node # noqa: E402
from otko.viewmodels import ProjectViewModel # noqa: E402
from otko.views.canvas3d import SelectionState # noqa: E402
from otko.views.tools.draw_frame import DrawFrameTool # noqa: E402
from otko.commands import AddNodesCommand
from otko.core import ElasticBeamColumn, ElasticSection, Node
from otko.viewmodels import ProjectViewModel
from otko.views.canvas3d import SelectionState
from otko.views.tools.draw_frame import DrawFrameTool
class _CanvasStub:

View file

@ -10,10 +10,10 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import GridSystem, Node, Project # noqa: E402
from otko.viewmodels import ProjectViewModel # noqa: E402
from otko.views.canvas3d.selection import SelectionState # noqa: E402
from otko.views.tools.draw_node import ( # noqa: E402
from otko.core import GridSystem, Node
from otko.viewmodels import ProjectViewModel
from otko.views.canvas3d.selection import SelectionState
from otko.views.tools.draw_node import (
DrawNodeTool,
_snap_to_grid,
)

View file

@ -6,18 +6,17 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import ( # noqa: E402
from otko.core import (
CoordinateGridSystem,
CoordinateSystem,
ElasticUniaxial,
GridSystem,
Node,
TrussElement,
make_grid_lines,
)
from otko.viewmodels import ProjectViewModel # noqa: E402
from otko.views.canvas3d.selection import SelectionState # noqa: E402
from otko.views.tools.draw_truss import DrawTrussTool # noqa: E402
from otko.viewmodels import ProjectViewModel
from otko.views.canvas3d.selection import SelectionState
from otko.views.tools.draw_truss import DrawTrussTool
class _CanvasStub:

View file

@ -6,7 +6,7 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import ( # noqa: E402
from otko.core import (
CoordinateGridSystem,
CoordinateSystem,
GridLine,

View file

@ -6,10 +6,10 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import Concrete04 # noqa: E402
from otko.views.dialogs.material_forms import ( # noqa: E402
Concrete04Form,
from otko.core import Concrete04
from otko.views.dialogs.material_forms import (
FORM_REGISTRY,
Concrete04Form,
)

View file

@ -10,7 +10,7 @@ import pytest
pytest.importorskip("PySide6")
from otko.views.dialogs.material_forms import ( # noqa: E402
from otko.views.dialogs.material_forms import (
Concrete01Form,
Concrete02Form,
Steel01Form,

View file

@ -8,17 +8,16 @@ import pytest
pytest.importorskip("PySide6")
from otko.commands import ( # noqa: E402
from otko.commands import (
AddLoadPatternCommand,
AddTimeSeriesCommand,
)
from otko.core import ( # noqa: E402
Node,
from otko.core import (
PathTimeSeries,
Project,
UniformExcitationPattern,
)
from otko.viewmodels import ProjectViewModel # noqa: E402
from otko.viewmodels import ProjectViewModel
# ─────────────── AddTimeSeriesCommand / AddLoadPatternCommand ──────────────

View file

@ -6,7 +6,7 @@ import pytest
pytest.importorskip("PySide6")
from otko.commands import ( # noqa: E402
from otko.commands import (
AddElementsCommand,
AddMaterialsCommand,
AddNodesCommand,
@ -16,7 +16,7 @@ from otko.commands import ( # noqa: E402
UpdateMaterialCommand,
UpdateSectionCommand,
)
from otko.core import ( # noqa: E402
from otko.core import (
ElasticBeamColumn,
ElasticSection,
Node,
@ -24,7 +24,7 @@ from otko.core import ( # noqa: E402
Steel02,
TrussElement,
)
from otko.viewmodels import ProjectViewModel # noqa: E402
from otko.viewmodels import ProjectViewModel
def _vm_with_steel() -> ProjectViewModel:

View file

@ -10,13 +10,10 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import ( # noqa: E402
Concrete01,
from otko.core import (
Concrete02,
ElasticIsotropic,
ElasticPP,
ElasticSection,
ElasticUniaxial,
Steel01,
Steel02,
)
@ -100,10 +97,14 @@ def test_elastic_section_form_round_trip(qtbot) -> None: # type: ignore[no-unty
def test_form_for_dispatches_by_type(qtbot) -> None: # type: ignore[no-untyped-def]
from otko.views.dialogs.material_forms import (
Steel01Form,
)
from otko.views.dialogs.material_forms import (
form_for as material_form_for,
)
from otko.views.dialogs.section_forms import (
ElasticSectionForm,
)
from otko.views.dialogs.section_forms import (
form_for as section_form_for,
)

View file

@ -7,9 +7,9 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import UnitSystem # noqa: E402
from otko.services.results import PushoverResults # noqa: E402
from otko.views.docks.pushover_curve import ( # noqa: E402
from otko.core import UnitSystem
from otko.services.results import PushoverResults
from otko.views.docks.pushover_curve import (
PushoverCurveView,
_is_rotation_dof,
)

View file

@ -8,11 +8,11 @@ import pytest
pytest.importorskip("PySide6")
from PySide6.QtCore import QObject, Signal # noqa: E402
from PySide6.QtCore import QObject, Signal
from otko.core import LinearTimeSeries, PlainLoadPattern, Project, TransientCase # noqa: E402
from otko.viewmodels import ProjectViewModel # noqa: E402
from otko.views.dialogs.run_analysis import RunAnalysisDialog # noqa: E402
from otko.core import LinearTimeSeries, PlainLoadPattern, TransientCase
from otko.viewmodels import ProjectViewModel
from otko.views.dialogs.run_analysis import RunAnalysisDialog
class _FakeRunner(QObject):

View file

@ -6,7 +6,7 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import ( # noqa: E402
from otko.core import (
ElasticBeamColumn,
ElasticSection,
Node,

View file

@ -7,14 +7,14 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import ( # noqa: E402
from otko.core import (
AggregatorDOF,
ElasticSection,
FiberSection,
RectangularPatch,
SectionAggregator,
)
from otko.views.dialogs.section_forms import ( # noqa: E402
from otko.views.dialogs.section_forms import (
FORM_REGISTRY,
form_for,
)

View file

@ -6,7 +6,7 @@ import pytest
pytest.importorskip("PySide6")
from otko.views.canvas3d import SelectionState # noqa: E402
from otko.views.canvas3d import SelectionState
@pytest.mark.gui

View file

@ -6,15 +6,15 @@ import pytest
pytest.importorskip("PySide6")
from otko.commands import ( # noqa: E402
from otko.commands import (
AddElementsCommand,
AddNodesCommand,
MirrorCommand,
MoveNodesCommand,
ReplicateCommand,
)
from otko.core import Node, Steel01, TrussElement # noqa: E402
from otko.viewmodels import ProjectViewModel # noqa: E402
from otko.core import Node, Steel01, TrussElement
from otko.viewmodels import ProjectViewModel
def _populated_vm() -> ProjectViewModel:

View file

@ -6,10 +6,9 @@ import pytest
pytest.importorskip("PySide6")
from PySide6.QtCore import Qt # noqa: E402
from PySide6.QtWidgets import QTreeWidget # noqa: E402
from PySide6.QtWidgets import QTreeWidget
from otko.core import Node # noqa: E402
from otko.core import Node
@pytest.mark.gui

View file

@ -6,7 +6,7 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import UnitSystem # noqa: E402
from otko.core import UnitSystem
@pytest.mark.gui

View file

@ -6,18 +6,18 @@ import pytest
pytest.importorskip("PySide6")
from otko.commands import ( # noqa: E402
from otko.commands import (
AddElementsCommand,
AddMaterialsCommand,
AddNodesCommand,
UpdateElementFieldsCommand,
)
from otko.core import ( # noqa: E402
from otko.core import (
ElasticUniaxial,
Node,
TrussElement,
)
from otko.viewmodels import ProjectViewModel # noqa: E402
from otko.viewmodels import ProjectViewModel
def _vm_with_truss() -> ProjectViewModel:

View file

@ -7,7 +7,7 @@ import pytest
pytest.importorskip("PySide6")
from otko.core import ( # noqa: E402
from otko.core import (
CoordinateGridSystem,
GridSystem,
Project,

View file

@ -11,7 +11,7 @@ import pytest
pytest.importorskip("openseespy")
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow
@ -25,7 +25,7 @@ def test_basic_truss_matches_opensees_tcl_reference() -> None:
a linear rescale of the Tcl model — any deviation would flag a
real solver/translation issue.
"""
from examples.basic_truss import build_basic_truss, IN_TO_M
from examples.basic_truss import IN_TO_M, build_basic_truss
proj = build_basic_truss()
runner = OpenSeesRunner(proj)
@ -45,6 +45,7 @@ def test_basic_truss_matches_opensees_tcl_reference() -> None:
def test_basic_truss_round_trips(tmp_path) -> None: # type: ignore[no-untyped-def]
"""The example project must survive save/load without any information loss."""
from examples.basic_truss import build_basic_truss
from otko.services import load_project, save_project
p = build_basic_truss()

View file

@ -9,8 +9,8 @@ import pytest
pytest.importorskip("openseespy")
from otko.services import load_project, save_project # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
from otko.services import load_project, save_project
from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow

View file

@ -8,7 +8,7 @@ import pytest
ops = pytest.importorskip("openseespy.opensees") # skip if OpenSeesPy not installed
from otko.core import ( # noqa: E402
from otko.core import (
ElasticBeamColumn,
ElasticSection,
LinearTimeSeries,
@ -20,8 +20,8 @@ from otko.core import ( # noqa: E402
Project,
StaticCase,
)
from otko.services import OpenSeesRunner, evaluate_combination # noqa: E402
from otko.services.results import StaticResults # noqa: E402
from otko.services import OpenSeesRunner, evaluate_combination
from otko.services.results import StaticResults
pytestmark = pytest.mark.slow

View file

@ -18,7 +18,7 @@ import pytest
pytest.importorskip("openseespy")
from otko.core import ( # noqa: E402
from otko.core import (
Concrete04,
FiberSection,
ForceBeamColumn,
@ -32,7 +32,7 @@ from otko.core import ( # noqa: E402
StaticCase,
UnitSystem,
)
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow

View file

@ -11,7 +11,7 @@ import pytest
pytest.importorskip("openseespy")
from otko.core import ( # noqa: E402
from otko.core import (
ElasticUniaxial,
LinearTimeSeries,
NodalLoad,
@ -21,7 +21,7 @@ from otko.core import ( # noqa: E402
StaticCase,
TrussElement,
)
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow

View file

@ -8,9 +8,9 @@ import pytest
pytest.importorskip("openseespy")
from otko.core import ModalCase # noqa: E402
from otko.services import load_project, save_project # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
from otko.core import ModalCase
from otko.services import load_project, save_project
from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow

View file

@ -8,9 +8,9 @@ import pytest
pytest.importorskip("openseespy")
from otko.core import ModalCase # noqa: E402
from otko.services import load_project, save_project # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
from otko.core import ModalCase
from otko.services import load_project, save_project
from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow

View file

@ -8,9 +8,9 @@ import pytest
pytest.importorskip("openseespy")
from otko.core import ModalCase, StaticCase # noqa: E402
from otko.services import load_project, save_project # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
from otko.core import ModalCase, StaticCase
from otko.services import load_project, save_project
from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow
@ -29,7 +29,6 @@ def _reload(proj, tmp_path): # type: ignore[no-untyped-def]
def test_elastic_frame_gravity_reactions(tmp_path) -> None: # type: ignore[no-untyped-def]
"""ΣFy at base = total applied gravity (distributed w × beam × floors)."""
from examples.elastic_frame import (
BAY,
LOAD_F1,
LOAD_F2,
LOAD_F3,
@ -70,7 +69,6 @@ def test_elastic_frame_gravity_reactions(tmp_path) -> None: # type: ignore[no-u
def test_elastic_frame_gravity_plus_lateral_reactions(tmp_path) -> None: # type: ignore[no-untyped-def]
"""ΣFx at base must equal -(lateral applied) within PDelta tolerance."""
from examples.elastic_frame import (
BAY,
LOAD_F1,
LOAD_F2,
LOAD_F3,
@ -120,5 +118,5 @@ def test_elastic_frame_modal_periods(tmp_path) -> None: # type: ignore[no-untyp
# 0.1930, 0.1562, 0.130 s. Our solve nails these within 1.5%.
expected = [1.040, 0.3526, 0.1930, 0.1562, 0.130]
periods = [2.0 * math.pi / math.sqrt(v) for v in r.eigenvalues]
for i, (T, T_ref) in enumerate(zip(periods, expected), start=1):
assert T == pytest.approx(T_ref, rel=0.02), f"T{i} = {T:.4f} s, reference {T_ref:.4f} s"
for i, (T, T_ref) in enumerate(zip(periods, expected, strict=False), start=1):
assert pytest.approx(T_ref, rel=0.02) == T, f"T{i} = {T:.4f} s, reference {T_ref:.4f} s"

View file

@ -9,9 +9,9 @@ import pytest
pytest.importorskip("openseespy")
from otko.core import PushoverCase, TransientCase # noqa: E402
from otko.services import load_project, save_project # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
from otko.core import PushoverCase, TransientCase
from otko.services import load_project, save_project
from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow

View file

@ -9,8 +9,8 @@ import pytest
pytest.importorskip("openseespy")
from otko.services import load_project, save_project # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
from otko.services import load_project, save_project
from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow

View file

@ -9,9 +9,9 @@ import pytest
pytest.importorskip("openseespy")
from otko.core import PushoverCase, TransientCase # noqa: E402
from otko.services import load_project, save_project # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
from otko.core import PushoverCase, TransientCase
from otko.services import load_project, save_project
from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow

View file

@ -9,9 +9,9 @@ import pytest
pytest.importorskip("openseespy")
from otko.core import PushoverCase, TransientCase # noqa: E402
from otko.services import load_project, save_project # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
from otko.core import PushoverCase, TransientCase
from otko.services import load_project, save_project
from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow

View file

@ -9,9 +9,9 @@ import pytest
pytest.importorskip("openseespy")
from otko.core import PushoverCase, TransientCase # noqa: E402
from otko.services import load_project, save_project # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
from otko.core import PushoverCase, TransientCase
from otko.services import load_project, save_project
from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow

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