otko/src/otko/views/dialogs/assign_masses.py
smillmorel 612936a00b
Some checks failed
CI / lint (push) Has been cancelled
CI / test (macos-latest, 3.10) (push) Has been cancelled
CI / test (macos-latest, 3.11) (push) Has been cancelled
CI / test (macos-latest, 3.12) (push) Has been cancelled
CI / test (ubuntu-latest, 3.10) (push) Has been cancelled
CI / test (ubuntu-latest, 3.11) (push) Has been cancelled
CI / test (ubuntu-latest, 3.12) (push) Has been cancelled
CI / test (windows-latest, 3.10) (push) Has been cancelled
CI / test (windows-latest, 3.11) (push) Has been cancelled
CI / test (windows-latest, 3.12) (push) Has been cancelled
feat: initial otko import
2026-09-08 02:12:15 -04:00

111 lines
3.9 KiB
Python

"""Assign Masses dialog — set translational (+ rotational) mass on selected nodes.
SAP2000 parity: the Assign → Joint → Masses menu item accumulates the
entered values onto each selected node. We follow the simpler "replace"
semantics (set the mass on each selected node to these values) because
it maps cleanly onto our existing :class:`SetMassCommand`.
"""
from __future__ import annotations
from PySide6.QtWidgets import (
QCheckBox,
QDialog,
QDialogButtonBox,
QDoubleSpinBox,
QFormLayout,
QLabel,
QVBoxLayout,
QWidget,
)
class AssignMassesDialog(QDialog):
"""Modal dialog: enter translational + rotational mass components.
Args:
current: ``(Mx, My, Mz, Mxx, Myy, Mzz)`` values already on the
selection, if known — prefilled instead of zeros.
mixed: True when the selection carries differing masses.
"""
def __init__(
self,
n_selected: int,
ndf: int = 6,
parent: QWidget | None = None,
*,
current: tuple[float, float, float, float, float, float] | None = None,
mixed: bool = False,
) -> None:
super().__init__(parent)
self.setWindowTitle("Assign Masses")
self._ndf = ndf
self._current = current
self._mixed = mixed
self._build_ui(n_selected)
def _build_ui(self, n_selected: int) -> None:
root = QVBoxLayout(self)
header = f"Assign mass values to <b>{n_selected}</b> selected node(s)."
if self._mixed:
header += " <i>Current values: mixed across selection.</i>"
elif self._current is not None:
header += " <i>Fields prefilled with the selection's current values.</i>"
root.addWidget(QLabel(header))
cur = self._current or (0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
form = QFormLayout()
self._mx = self._spin(cur[0]); form.addRow("Translation X:", self._mx)
self._my = self._spin(cur[1]); form.addRow("Translation Y:", self._my)
if self._ndf >= 3:
self._mz = self._spin(cur[2]); form.addRow("Translation Z:", self._mz)
else:
self._mz = self._spin(cur[2])
if self._ndf == 6:
self._mxx = self._spin(cur[3]); form.addRow("Rotation X (Ixx):", self._mxx)
self._myy = self._spin(cur[4]); form.addRow("Rotation Y (Iyy):", self._myy)
self._mzz = self._spin(cur[5]); form.addRow("Rotation Z (Izz):", self._mzz)
else:
self._mxx = self._spin(cur[3])
self._myy = self._spin(cur[4])
self._mzz = self._spin(cur[5])
root.addLayout(form)
self._xy_link = QCheckBox("Tie translation Y to X (lumped horizontal mass)")
self._xy_link.toggled.connect(self._on_xy_link)
root.addWidget(self._xy_link)
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok
| QDialogButtonBox.StandardButton.Cancel,
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
root.addWidget(buttons)
@staticmethod
def _spin(value: float = 0.0) -> QDoubleSpinBox:
sb = QDoubleSpinBox()
sb.setRange(0.0, 1e15)
sb.setDecimals(6)
sb.setSingleStep(100.0)
sb.setValue(float(value))
return sb
def _on_xy_link(self, checked: bool) -> None:
if checked:
self._my.setValue(self._mx.value())
self._mx.valueChanged.connect(self._my.setValue)
else:
try:
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)."""
return (
self._mx.value(), self._my.value(), self._mz.value(),
self._mxx.value(), self._myy.value(), self._mzz.value(),
)