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).
This commit is contained in:
smillmorel 2026-09-16 13:20:36 -04:00
commit 61b850bf46
126 changed files with 359 additions and 397 deletions

View file

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

View file

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

View file

@ -27,16 +27,16 @@ class ProjectCommand(QUndoCommand):
:meth:`_notify` exactly once at the end of each. :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) super().__init__(text)
self._vm = vm self._vm = vm
@property @property
def vm(self) -> "ProjectViewModel": def vm(self) -> ProjectViewModel:
return self._vm return self._vm
@property @property
def project(self) -> "Project": def project(self) -> Project:
if self._vm.project is None: if self._vm.project is None:
raise RuntimeError(f"Cannot apply '{self.text()}': no active project.") raise RuntimeError(f"Cannot apply '{self.text()}': no active project.")
return self._vm.project return self._vm.project

View file

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

View file

@ -14,7 +14,7 @@ if TYPE_CHECKING:
class AddEqualDOFConstraintCommand(ProjectCommand): class AddEqualDOFConstraintCommand(ProjectCommand):
"""Append an ``equalDOF`` constraint to the project (undoable).""" """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__( super().__init__(
vm, vm,
f"Add equalDOF {constraint.retained_node}->{constraint.constrained_node}", f"Add equalDOF {constraint.retained_node}->{constraint.constrained_node}",
@ -48,7 +48,7 @@ class UpdateEqualDOFConstraintCommand(ProjectCommand):
""" """
def __init__( def __init__(
self, vm: "ProjectViewModel", index: int, new_constraint: EqualDOFConstraint self, vm: ProjectViewModel, index: int, new_constraint: EqualDOFConstraint
) -> None: ) -> None:
super().__init__(vm, f"Edit equalDOF row {index + 1}") super().__init__(vm, f"Edit equalDOF row {index + 1}")
self._index = index self._index = index
@ -71,7 +71,7 @@ class UpdateEqualDOFConstraintCommand(ProjectCommand):
class DeleteEqualDOFConstraintCommand(ProjectCommand): class DeleteEqualDOFConstraintCommand(ProjectCommand):
"""Remove ``equalDOF`` constraints by table-row index (no cascade).""" """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)") super().__init__(vm, f"Delete {len(indices)} constraint(s)")
self._indices = set(indices) self._indices = set(indices)
self._removed: list[tuple[int, EqualDOFConstraint]] = [] 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.""" """Add one or more elements in a single undoable step."""
def __init__( def __init__(
self, vm: "ProjectViewModel", elements: list[Any], *, text: str | None = None self, vm: ProjectViewModel, elements: list[Any], *, text: str | None = None
) -> None: ) -> None:
super().__init__(vm, text or f"Add {len(elements)} element(s)") super().__init__(vm, text or f"Add {len(elements)} element(s)")
self._elements = list(elements) self._elements = list(elements)
@ -40,7 +40,7 @@ class AddElementsCommand(ProjectCommand):
class DeleteElementsCommand(ProjectCommand): class DeleteElementsCommand(ProjectCommand):
"""Remove a set of elements (no cascade — nodes are not affected).""" """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)") super().__init__(vm, f"Delete {len(element_ids)} element(s)")
self._element_ids = set(element_ids) self._element_ids = set(element_ids)
self._removed: list[tuple[int, Any]] = [] self._removed: list[tuple[int, Any]] = []
@ -68,7 +68,7 @@ class AssignSectionCommand(ProjectCommand):
(e.g. trusses, zero-length elements). (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)") super().__init__(vm, f"Assign section {section_id} to {len(element_ids)} element(s)")
self._element_ids = set(element_ids) self._element_ids = set(element_ids)
self._section_id = section_id self._section_id = section_id
@ -106,7 +106,7 @@ class ConvertElementTypeCommand(ProjectCommand):
def __init__( def __init__(
self, self,
vm: "ProjectViewModel", vm: ProjectViewModel,
element_ids: set[int], element_ids: set[int],
target_type: str, target_type: str,
defaults: dict[str, Any] | None = None, defaults: dict[str, Any] | None = None,
@ -185,7 +185,7 @@ class ReplaceElementsCommand(ProjectCommand):
for another while preserving the id and node connectivity. 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)") super().__init__(vm, f"Replace {len(replacements)} element(s)")
self._replacements = {el.id: el for el in replacements} self._replacements = {el.id: el for el in replacements}
self._previous: dict[int, Any] = {} self._previous: dict[int, Any] = {}
@ -217,7 +217,7 @@ class UpdateElementFieldsCommand(ProjectCommand):
def __init__( def __init__(
self, self,
vm: "ProjectViewModel", vm: ProjectViewModel,
element_id: int, element_id: int,
fields: dict[str, Any], fields: dict[str, Any],
) -> None: ) -> None:
@ -254,7 +254,7 @@ class UpdateElementFieldsCommand(ProjectCommand):
class AssignMaterialCommand(ProjectCommand): class AssignMaterialCommand(ProjectCommand):
"""Set ``material_id`` on a set of elements (truss-style).""" """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)") super().__init__(vm, f"Assign material {material_id} to {len(element_ids)} element(s)")
self._element_ids = set(element_ids) self._element_ids = set(element_ids)
self._material_id = material_id self._material_id = material_id

View file

@ -20,7 +20,7 @@ class SetCoordSystemsCommand(ProjectCommand):
def __init__( def __init__(
self, self,
vm: "ProjectViewModel", vm: ProjectViewModel,
new_systems: list[CoordinateGridSystem], new_systems: list[CoordinateGridSystem],
) -> None: ) -> None:
super().__init__(vm, "Update coordinate/grid systems") super().__init__(vm, "Update coordinate/grid systems")
@ -46,7 +46,7 @@ class SetGridSystemCommand(ProjectCommand):
systems at once. 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") super().__init__(vm, "Update grid system")
self._new_grid = new_grid self._new_grid = new_grid
self._previous: GridSystem | None = None self._previous: GridSystem | None = None

View file

@ -35,7 +35,7 @@ class AddNodalLoadsCommand(ProjectCommand):
def __init__( def __init__(
self, self,
vm: "ProjectViewModel", vm: ProjectViewModel,
node_ids: set[int], node_ids: set[int],
forces: tuple[float, float, float, float, float, float], forces: tuple[float, float, float, float, float, float],
pattern_id: int | None = None, pattern_id: int | None = None,
@ -122,7 +122,7 @@ class AddElementLoadsCommand(ProjectCommand):
def __init__( def __init__(
self, self,
vm: "ProjectViewModel", vm: ProjectViewModel,
element_ids: set[int], element_ids: set[int],
wy: float = 0.0, wy: float = 0.0,
wz: 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.""" """Add one or more materials in a single undoable step."""
def __init__( def __init__(
self, vm: "ProjectViewModel", materials: list[Any], *, text: str | None = None self, vm: ProjectViewModel, materials: list[Any], *, text: str | None = None
) -> None: ) -> None:
super().__init__(vm, text or f"Add {len(materials)} material(s)") super().__init__(vm, text or f"Add {len(materials)} material(s)")
self._materials = list(materials) self._materials = list(materials)
@ -36,7 +36,7 @@ class AddMaterialsCommand(ProjectCommand):
class DeleteMaterialsCommand(ProjectCommand): class DeleteMaterialsCommand(ProjectCommand):
"""Remove materials (no cascade).""" """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)") super().__init__(vm, f"Delete {len(material_ids)} material(s)")
self._material_ids = set(material_ids) self._material_ids = set(material_ids)
self._removed: list[tuple[int, Any]] = [] self._removed: list[tuple[int, Any]] = []
@ -64,7 +64,7 @@ class UpdateMaterialCommand(ProjectCommand):
otherwise this is an Add+Delete, not an update. 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}") super().__init__(vm, f"Edit material {new_material.id}")
self._new = new_material self._new = new_material
self._old: Any | None = None self._old: Any | None = None

View file

@ -14,9 +14,7 @@ if TYPE_CHECKING:
class AddNodesCommand(ProjectCommand): class AddNodesCommand(ProjectCommand):
"""Add one or more nodes in a single undoable step.""" """Add one or more nodes in a single undoable step."""
def __init__( def __init__(self, vm: ProjectViewModel, nodes: list[Node], *, text: str | None = None) -> None:
self, vm: "ProjectViewModel", nodes: list[Node], *, text: str | None = None
) -> None:
super().__init__(vm, text or f"Add {len(nodes)} node(s)") super().__init__(vm, text or f"Add {len(nodes)} node(s)")
self._nodes = list(nodes) self._nodes = list(nodes)
@ -41,7 +39,7 @@ class DeleteNodesCommand(ProjectCommand):
them in their original positions. 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)") super().__init__(vm, f"Delete {len(node_ids)} node(s)")
self._node_ids = set(node_ids) self._node_ids = set(node_ids)
self._removed_nodes: list[tuple[int, Node]] = [] # (index, node) self._removed_nodes: list[tuple[int, Node]] = [] # (index, node)
@ -95,7 +93,7 @@ class SetRestraintCommand(ProjectCommand):
def __init__( def __init__(
self, self,
vm: "ProjectViewModel", vm: ProjectViewModel,
node_ids: set[int], node_ids: set[int],
restraint: tuple[bool, bool, bool, bool, bool, bool], restraint: tuple[bool, bool, bool, bool, bool, bool],
) -> None: ) -> None:
@ -130,7 +128,7 @@ class UpdateNodeFieldsCommand(ProjectCommand):
def __init__( def __init__(
self, self,
vm: "ProjectViewModel", vm: ProjectViewModel,
node_id: int, node_id: int,
fields: dict[str, Any], fields: dict[str, Any],
) -> None: ) -> None:
@ -168,7 +166,7 @@ class SetMassCommand(ProjectCommand):
def __init__( def __init__(
self, self,
vm: "ProjectViewModel", vm: ProjectViewModel,
node_ids: set[int], node_ids: set[int],
mass: tuple[float, float, float, float, float, float], mass: tuple[float, float, float, float, float, float],
) -> None: ) -> None:

View file

@ -19,7 +19,7 @@ class SetUnitsCommand(ProjectCommand):
so both redo and undo notify (dirty + modelMutated) to refresh. 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}") super().__init__(vm, f"Set display units to {units.value}")
self._units = units self._units = units
self._previous: UnitSystem | None = None self._previous: UnitSystem | None = None

View file

@ -14,7 +14,7 @@ class AddSectionsCommand(ProjectCommand):
"""Add one or more sections in a single undoable step.""" """Add one or more sections in a single undoable step."""
def __init__( def __init__(
self, vm: "ProjectViewModel", sections: list[Any], *, text: str | None = None self, vm: ProjectViewModel, sections: list[Any], *, text: str | None = None
) -> None: ) -> None:
super().__init__(vm, text or f"Add {len(sections)} section(s)") super().__init__(vm, text or f"Add {len(sections)} section(s)")
self._sections = list(sections) self._sections = list(sections)
@ -36,7 +36,7 @@ class AddSectionsCommand(ProjectCommand):
class DeleteSectionsCommand(ProjectCommand): class DeleteSectionsCommand(ProjectCommand):
"""Remove sections (no cascade — elements referencing them must be cleaned separately).""" """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)") super().__init__(vm, f"Delete {len(section_ids)} section(s)")
self._section_ids = set(section_ids) self._section_ids = set(section_ids)
self._removed: list[tuple[int, Any]] = [] self._removed: list[tuple[int, Any]] = []
@ -60,7 +60,7 @@ class DeleteSectionsCommand(ProjectCommand):
class UpdateSectionCommand(ProjectCommand): class UpdateSectionCommand(ProjectCommand):
"""Replace a section's parameters at a given id.""" """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}") super().__init__(vm, f"Edit section {new_section.id}")
self._new = new_section self._new = new_section
self._old: Any | None = None self._old: Any | None = None

View file

@ -19,7 +19,7 @@ if TYPE_CHECKING:
class AddTimeSeriesCommand(ProjectCommand): class AddTimeSeriesCommand(ProjectCommand):
"""Append a :class:`TimeSeries` to the project (undoable).""" """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}'") super().__init__(vm, f"Add time series '{ts.name or ts.id}'")
self._ts = ts self._ts = ts
@ -41,7 +41,7 @@ class UpdateTimeSeriesCommand(ProjectCommand):
The new series must carry the same id as the one being replaced. 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}") super().__init__(vm, f"Edit time series {new_ts.id}")
self._new = new_ts self._new = new_ts
self._old: Any | None = None self._old: Any | None = None
@ -67,7 +67,7 @@ class DeleteTimeSeriesCommand(ProjectCommand):
"""Remove time series (no cascade — patterns referencing them must be """Remove time series (no cascade — patterns referencing them must be
cleaned separately; ``validate_references`` catches strays before save).""" 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)") super().__init__(vm, f"Delete {len(ts_ids)} time serie(s)")
self._ts_ids = set(ts_ids) self._ts_ids = set(ts_ids)
self._removed: list[tuple[int, Any]] = [] self._removed: list[tuple[int, Any]] = []
@ -91,7 +91,7 @@ class DeleteTimeSeriesCommand(ProjectCommand):
class AddLoadPatternCommand(ProjectCommand): class AddLoadPatternCommand(ProjectCommand):
"""Append a :class:`LoadPattern` to the project (undoable).""" """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}'") super().__init__(vm, f"Add pattern '{pattern.name or pattern.id}'")
self._pattern = pattern self._pattern = pattern
@ -117,7 +117,7 @@ class UpdateLoadPatternCommand(ProjectCommand):
drops the pattern's stored loads. 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}") super().__init__(vm, f"Edit pattern {new_pattern.id}")
self._new = new_pattern self._new = new_pattern
self._old: Any | None = None self._old: Any | None = None
@ -142,7 +142,7 @@ class UpdateLoadPatternCommand(ProjectCommand):
class DeleteLoadPatternCommand(ProjectCommand): class DeleteLoadPatternCommand(ProjectCommand):
"""Remove load patterns (their child nodal/element loads go with them).""" """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)") super().__init__(vm, f"Delete {len(pattern_ids)} pattern(s)")
self._pattern_ids = set(pattern_ids) self._pattern_ids = set(pattern_ids)
self._removed: list[tuple[int, Any]] = [] 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 __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal from typing import TYPE_CHECKING, Literal
from otko.commands.base import ProjectCommand from otko.commands.base import ProjectCommand
from otko.core import Node
if TYPE_CHECKING: if TYPE_CHECKING:
from otko.viewmodels import ProjectViewModel from otko.viewmodels import ProjectViewModel
@ -30,7 +29,7 @@ class MoveNodesCommand(ProjectCommand):
def __init__( def __init__(
self, self,
vm: "ProjectViewModel", vm: ProjectViewModel,
node_ids: set[int], node_ids: set[int],
offset: tuple[float, float, float], offset: tuple[float, float, float],
) -> None: ) -> None:
@ -63,7 +62,7 @@ class ReplicateCommand(ProjectCommand):
def __init__( def __init__(
self, self,
vm: "ProjectViewModel", vm: ProjectViewModel,
node_ids: set[int], node_ids: set[int],
element_ids: set[int], element_ids: set[int],
offset: tuple[float, float, float], offset: tuple[float, float, float],
@ -147,7 +146,7 @@ class MirrorCommand(ProjectCommand):
def __init__( def __init__(
self, self,
vm: "ProjectViewModel", vm: ProjectViewModel,
node_ids: set[int], node_ids: set[int],
element_ids: set[int], element_ids: set[int],
plane: Plane, plane: Plane,

View file

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

View file

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

View file

@ -97,7 +97,7 @@ class GridSystem(BaseModel):
return data return data
@model_validator(mode="after") @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"): for name in ("x_grid_lines", "y_grid_lines", "z_grid_lines"):
lines: list[GridLine] = list(getattr(self, name)) lines: list[GridLine] = list(getattr(self, name))
lines.sort(key=lambda ln: ln.ordinate) 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 __future__ import annotations
from typing import Annotated, Literal, Union from typing import Annotated, Literal
from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator
@ -110,7 +110,7 @@ class ResponseSpectrum(Entity):
TimeSeries = Annotated[ TimeSeries = Annotated[
Union[LinearTimeSeries, ConstantTimeSeries, PathTimeSeries], LinearTimeSeries | ConstantTimeSeries | PathTimeSeries,
Field(discriminator="type"), Field(discriminator="type"),
] ]
@ -229,7 +229,7 @@ class LoadCombination(Entity):
) )
@model_validator(mode="after") @model_validator(mode="after")
def _check_items(self) -> "LoadCombination": def _check_items(self) -> LoadCombination:
"""Reject duplicate case ids and non-finite factors.""" """Reject duplicate case ids and non-finite factors."""
import math import math
@ -250,6 +250,6 @@ class LoadCombination(Entity):
LoadPattern = Annotated[ LoadPattern = Annotated[
Union[PlainLoadPattern, UniformExcitationPattern, ImposedSupportMotionPattern], PlainLoadPattern | UniformExcitationPattern | ImposedSupportMotionPattern,
Field(discriminator="type"), Field(discriminator="type"),
] ]

View file

@ -11,7 +11,7 @@ https://openseespydoc.readthedocs.io/en/latest/src/uniaxialMaterial.html
from __future__ import annotations from __future__ import annotations
from typing import Annotated, Literal, Union from typing import Annotated, Literal
from pydantic import Field, PositiveFloat, model_validator from pydantic import Field, PositiveFloat, model_validator
@ -130,7 +130,7 @@ class Concrete04(Entity):
) )
@model_validator(mode="after") @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: if self.fct is not None and self.et is None:
raise ValueError("et is required when fct is given.") raise ValueError("et is required when fct is given.")
if self.et is not None and self.fct is None: if self.et is not None and self.fct is None:
@ -229,18 +229,16 @@ class HystereticSM(Entity):
# ──────────────────────────── Discriminated union ──────────────────────────── # ──────────────────────────── Discriminated union ────────────────────────────
Material = Annotated[ Material = Annotated[
Union[ ElasticIsotropic
ElasticIsotropic, | ElasticUniaxial
ElasticUniaxial, | Steel01
Steel01, | Steel02
Steel02, | Concrete01
Concrete01, | Concrete02
Concrete02, | Concrete04
Concrete04, | ElasticPP
ElasticPP, | HystereticMaterial
HystereticMaterial, | HystereticSM,
HystereticSM,
],
Field(discriminator="type"), Field(discriminator="type"),
] ]
"""Tagged union of every material kind. Pydantic uses ``type`` to dispatch on JSON load.""" """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 return data
@model_validator(mode="after") @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.""" """Guarantee that a 'Global' entry exists as the first coord system."""
has_global = any(cs.name == "Global" for cs in self.coord_systems) has_global = any(cs.name == "Global" for cs in self.coord_systems)
if not has_global: if not has_global:
@ -174,7 +174,7 @@ class Project(BaseModel):
return self return self
@model_validator(mode="after") @model_validator(mode="after")
def _ensure_indexed(self) -> "Project": def _ensure_indexed(self) -> Project:
"""Wrap every collection in an :class:`_IndexedList` and drop caches. """Wrap every collection in an :class:`_IndexedList` and drop caches.
Runs after each validation pass (construction, ``model_validate``, Runs after each validation pass (construction, ``model_validate``,
@ -223,7 +223,7 @@ class Project(BaseModel):
# ─────────────────── invariants ─────────────────── # ─────────────────── invariants ───────────────────
@model_validator(mode="after") @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)} valid = {(2, 2), (2, 3), (3, 3), (3, 6)}
if (self.ndm, self.ndf) not in valid: if (self.ndm, self.ndf) not in valid:
raise ValueError( raise ValueError(
@ -233,7 +233,7 @@ class Project(BaseModel):
return self return self
@model_validator(mode="after") @model_validator(mode="after")
def _check_unique_ids(self) -> "Project": def _check_unique_ids(self) -> Project:
for label, items in ( for label, items in (
("node", self.nodes), ("node", self.nodes),
("material", self.materials), ("material", self.materials),

View file

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

View file

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

View file

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

View file

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

View file

@ -54,7 +54,7 @@ def _angle_in_arc(angle: float, start: float, span: float) -> bool:
def bbox_for_section( def bbox_for_section(
section: object, section: object,
project: "Project | None" = None, project: Project | None = None,
) -> tuple[float, float] | None: ) -> tuple[float, float] | None:
"""Return ``(width_y, height_z)`` of the section's local bounding box. """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 ( from otko.core.sections import (
CircularPatch, CircularPatch,
FiberSection, FiberSection,
Fibre,
RectangularPatch, RectangularPatch,
StraightLayer, StraightLayer,
) )

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,6 +8,8 @@ it maps cleanly onto our existing :class:`SetMassCommand`.
from __future__ import annotations from __future__ import annotations
import contextlib
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QCheckBox, QCheckBox,
QDialog, QDialog,
@ -103,10 +105,8 @@ class AssignMassesDialog(QDialog):
self._my.setValue(self._mx.value()) self._my.setValue(self._mx.value())
self._mx.valueChanged.connect(self._my.setValue) self._mx.valueChanged.connect(self._my.setValue)
else: else:
try: with contextlib.suppress(RuntimeError, TypeError):
self._mx.valueChanged.disconnect(self._my.setValue) self._mx.valueChanged.disconnect(self._my.setValue)
except (RuntimeError, TypeError):
pass
def mass_vector(self) -> tuple[float, float, float, float, float, float]: def mass_vector(self) -> tuple[float, float, float, float, float, float]:
"""Return the 6-tuple (Mx, My, Mz, Mxx, Myy, Mzz).""" """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 __future__ import annotations
from PySide6.QtCore import Qt
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QButtonGroup, QButtonGroup,
QCheckBox, QCheckBox,
@ -22,7 +21,6 @@ from PySide6.QtWidgets import (
QWidget, QWidget,
) )
# Preset → 6-tuple (Ux, Uy, Uz, Rx, Ry, Rz) # Preset → 6-tuple (Ux, Uy, Uz, Rx, Ry, Rz)
PRESETS: dict[str, tuple[bool, bool, bool, bool, bool, bool]] = { PRESETS: dict[str, tuple[bool, bool, bool, bool, bool, bool]] = {
"Free": (False, False, False, False, False, False), "Free": (False, False, False, False, False, False),

View file

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

View file

@ -4,6 +4,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from pydantic import ValidationError
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QDialog, QDialog,
@ -19,7 +20,6 @@ from PySide6.QtWidgets import (
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,
) )
from pydantic import ValidationError
from otko.commands import ( from otko.commands import (
AddAnalysisCasesCommand, AddAnalysisCasesCommand,
@ -34,7 +34,7 @@ from otko.core import (
TransientCase, TransientCase,
) )
from otko.viewmodels import ProjectViewModel 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: if TYPE_CHECKING:
from otko.core import Project from otko.core import Project

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -197,7 +197,7 @@ def _fmt(value: Any) -> str:
return "—" return "—"
if isinstance(value, float): if isinstance(value, float):
return f"{value:g}" return f"{value:g}"
if isinstance(value, (list, tuple)): if isinstance(value, list | tuple):
return ", ".join(_fmt(v) for v in value) return ", ".join(_fmt(v) for v in value)
return str(value) return str(value)
@ -1035,7 +1035,7 @@ class TableDock(QWidget):
try: try:
key = int(pid) key = int(pid)
label = name_of.get(key, str(key)) or str(key) 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): except (TypeError, ValueError):
continue continue
return ", ".join(parts) if parts else "—" 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 from __future__ import annotations
import contextlib
from typing import Any from typing import Any
import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
from PySide6.QtCore import Signal from PySide6.QtCore import Signal
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
@ -208,9 +208,7 @@ class TimeHistoryView(QWidget):
def _clear_traces(self) -> None: def _clear_traces(self) -> None:
for _, _, item in self._traces: for _, _, item in self._traces:
try: with contextlib.suppress(Exception):
self._plot.removeItem(item) self._plot.removeItem(item)
except Exception:
pass
self._traces.clear() self._traces.clear()
self._trace_list.clear() self._trace_list.clear()

View file

@ -105,7 +105,7 @@ class MainWindow(
if state is not None: if state is not None:
self.restoreState(state) 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. """Prompt to save unsaved work, then persist the window layout.
The prompt is shown only for a visible window: Qt also closes hidden The prompt is shown only for a visible window: Qt also closes hidden

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,13 +6,13 @@ import pytest
pytest.importorskip("PySide6") pytest.importorskip("PySide6")
from otko.core import ( # noqa: E402 from otko.core import (
NodalLoad, NodalLoad,
PlainLoadPattern, PlainLoadPattern,
StaticCase, StaticCase,
TransientCase, 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] def _patterns(): # type: ignore[no-untyped-def]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,11 +8,11 @@ import pytest
pytest.importorskip("PySide6") 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.core import LinearTimeSeries, PlainLoadPattern, TransientCase
from otko.viewmodels import ProjectViewModel # noqa: E402 from otko.viewmodels import ProjectViewModel
from otko.views.dialogs.run_analysis import RunAnalysisDialog # noqa: E402 from otko.views.dialogs.run_analysis import RunAnalysisDialog
class _FakeRunner(QObject): class _FakeRunner(QObject):

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,7 +11,7 @@ import pytest
pytest.importorskip("openseespy") pytest.importorskip("openseespy")
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402 from otko.services.opensees_runner import OpenSeesRunner
pytestmark = pytest.mark.slow 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 a linear rescale of the Tcl model — any deviation would flag a
real solver/translation issue. 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() proj = build_basic_truss()
runner = OpenSeesRunner(proj) 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] 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.""" """The example project must survive save/load without any information loss."""
from examples.basic_truss import build_basic_truss from examples.basic_truss import build_basic_truss
from otko.services import load_project, save_project from otko.services import load_project, save_project
p = build_basic_truss() p = build_basic_truss()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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