feat: show default node/element names in table
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
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
This commit is contained in:
parent
8f1aae4370
commit
13bb5d1d91
8 changed files with 172 additions and 21 deletions
|
|
@ -35,6 +35,7 @@ from otko.core.geometry import (
|
||||||
DispBeamColumn,
|
DispBeamColumn,
|
||||||
ElasticBeamColumn,
|
ElasticBeamColumn,
|
||||||
Element,
|
Element,
|
||||||
|
ElementBase,
|
||||||
ForceBeamColumn,
|
ForceBeamColumn,
|
||||||
GridLine,
|
GridLine,
|
||||||
GridSystem,
|
GridSystem,
|
||||||
|
|
@ -114,6 +115,7 @@ __all__ = [
|
||||||
"ensure_default_truss_material",
|
"ensure_default_truss_material",
|
||||||
"ensure_default_timeseries_and_pattern",
|
"ensure_default_timeseries_and_pattern",
|
||||||
"Element",
|
"Element",
|
||||||
|
"ElementBase",
|
||||||
"TrussElement",
|
"TrussElement",
|
||||||
"CorotTrussElement",
|
"CorotTrussElement",
|
||||||
"ElasticBeamColumn",
|
"ElasticBeamColumn",
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,31 @@ class Entity(BaseModel):
|
||||||
"""Base class for every persisted domain object."""
|
"""Base class for every persisted domain object."""
|
||||||
|
|
||||||
model_config = ConfigDict(
|
model_config = ConfigDict(
|
||||||
frozen=False, # individual setters allowed; we lock at the Project boundary
|
frozen=False, # individual setters allowed; we lock at the Project boundary
|
||||||
extra="forbid", # unknown JSON keys are an error, not a silent ignore
|
extra="forbid", # unknown JSON keys are an error, not a silent ignore
|
||||||
validate_assignment=True,
|
validate_assignment=True,
|
||||||
populate_by_name=True,
|
populate_by_name=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
id: PositiveInt = Field(..., description="Unique tag within its kind. Used as the OpenSees tag.")
|
id: PositiveInt = Field(
|
||||||
|
..., description="Unique tag within its kind. Used as the OpenSees tag."
|
||||||
|
)
|
||||||
name: str = Field(default="", description="Optional human-readable label.")
|
name: str = Field(default="", description="Optional human-readable label.")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_name(self) -> str:
|
||||||
|
"""Fallback label used when ``name`` is blank.
|
||||||
|
|
||||||
|
Display-only — reading it never writes back into ``name`` and it
|
||||||
|
is never persisted. Subclasses override this with their prefix
|
||||||
|
(e.g. ``N<id>`` for nodes, ``E<id>`` for elements).
|
||||||
|
"""
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_name(self) -> str:
|
||||||
|
"""Explicit ``name`` stripped, or ``default_name`` when blank.
|
||||||
|
|
||||||
|
Display-only fallback — reading this never mutates ``name``.
|
||||||
|
"""
|
||||||
|
return self.name.strip() or self.default_name
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from otko.core.geometry.elements import (
|
||||||
DispBeamColumn,
|
DispBeamColumn,
|
||||||
ElasticBeamColumn,
|
ElasticBeamColumn,
|
||||||
Element,
|
Element,
|
||||||
|
ElementBase,
|
||||||
ForceBeamColumn,
|
ForceBeamColumn,
|
||||||
QuadElement,
|
QuadElement,
|
||||||
TrussElement,
|
TrussElement,
|
||||||
|
|
@ -25,6 +26,7 @@ from otko.core.geometry.node import Node
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Node",
|
"Node",
|
||||||
"Element",
|
"Element",
|
||||||
|
"ElementBase",
|
||||||
"TrussElement",
|
"TrussElement",
|
||||||
"CorotTrussElement",
|
"CorotTrussElement",
|
||||||
"ElasticBeamColumn",
|
"ElasticBeamColumn",
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,21 @@ from pydantic import Field, PositiveFloat, PositiveInt
|
||||||
from otko.core._base import Entity
|
from otko.core._base import Entity
|
||||||
|
|
||||||
|
|
||||||
class TrussElement(Entity):
|
class ElementBase(Entity):
|
||||||
|
"""Shared base for all structural elements.
|
||||||
|
|
||||||
|
Carries the display-only ``E<id>`` fallback label so every element
|
||||||
|
type (including Quad) shows the same default name on the canvas
|
||||||
|
and in the table without persisting it into ``name``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_name(self) -> str:
|
||||||
|
"""Fallback label for unnamed elements (``E<id>``)."""
|
||||||
|
return f"E{self.id}"
|
||||||
|
|
||||||
|
|
||||||
|
class TrussElement(ElementBase):
|
||||||
"""Two-node truss with axial stiffness only — ``element truss``."""
|
"""Two-node truss with axial stiffness only — ``element truss``."""
|
||||||
|
|
||||||
type: Literal["Truss"] = "Truss"
|
type: Literal["Truss"] = "Truss"
|
||||||
|
|
@ -27,7 +41,7 @@ class TrussElement(Entity):
|
||||||
rho: float = Field(default=0.0, ge=0.0, description="Mass per unit length.")
|
rho: float = Field(default=0.0, ge=0.0, description="Mass per unit length.")
|
||||||
|
|
||||||
|
|
||||||
class CorotTrussElement(Entity):
|
class CorotTrussElement(ElementBase):
|
||||||
"""Co-rotational truss for large displacements — ``element corotTruss``."""
|
"""Co-rotational truss for large displacements — ``element corotTruss``."""
|
||||||
|
|
||||||
type: Literal["CorotTruss"] = "CorotTruss"
|
type: Literal["CorotTruss"] = "CorotTruss"
|
||||||
|
|
@ -37,7 +51,7 @@ class CorotTrussElement(Entity):
|
||||||
rho: float = Field(default=0.0, ge=0.0)
|
rho: float = Field(default=0.0, ge=0.0)
|
||||||
|
|
||||||
|
|
||||||
class ElasticBeamColumn(Entity):
|
class ElasticBeamColumn(ElementBase):
|
||||||
"""Linear-elastic frame element — ``element elasticBeamColumn``.
|
"""Linear-elastic frame element — ``element elasticBeamColumn``.
|
||||||
|
|
||||||
Pure-section style: provide section_id; OpenSees pulls EA, EI from
|
Pure-section style: provide section_id; OpenSees pulls EA, EI from
|
||||||
|
|
@ -60,7 +74,7 @@ class ElasticBeamColumn(Entity):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ForceBeamColumn(Entity):
|
class ForceBeamColumn(ElementBase):
|
||||||
"""Force-based fibre frame element — ``element forceBeamColumn``."""
|
"""Force-based fibre frame element — ``element forceBeamColumn``."""
|
||||||
|
|
||||||
type: Literal["ForceBeamColumn"] = "ForceBeamColumn"
|
type: Literal["ForceBeamColumn"] = "ForceBeamColumn"
|
||||||
|
|
@ -72,7 +86,7 @@ class ForceBeamColumn(Entity):
|
||||||
tolerance: float = Field(default=1e-12, gt=0.0)
|
tolerance: float = Field(default=1e-12, gt=0.0)
|
||||||
|
|
||||||
|
|
||||||
class DispBeamColumn(Entity):
|
class DispBeamColumn(ElementBase):
|
||||||
"""Displacement-based fibre frame element — ``element dispBeamColumn``."""
|
"""Displacement-based fibre frame element — ``element dispBeamColumn``."""
|
||||||
|
|
||||||
type: Literal["DispBeamColumn"] = "DispBeamColumn"
|
type: Literal["DispBeamColumn"] = "DispBeamColumn"
|
||||||
|
|
@ -82,7 +96,7 @@ class DispBeamColumn(Entity):
|
||||||
geom_transf: Literal["Linear", "PDelta", "Corotational"] = "Linear"
|
geom_transf: Literal["Linear", "PDelta", "Corotational"] = "Linear"
|
||||||
|
|
||||||
|
|
||||||
class ZeroLengthElement(Entity):
|
class ZeroLengthElement(ElementBase):
|
||||||
"""Two coincident nodes connected by uniaxial materials per DOF.
|
"""Two coincident nodes connected by uniaxial materials per DOF.
|
||||||
|
|
||||||
Foundation building block for plastic hinges and isolators.
|
Foundation building block for plastic hinges and isolators.
|
||||||
|
|
@ -91,7 +105,9 @@ class ZeroLengthElement(Entity):
|
||||||
type: Literal["ZeroLength"] = "ZeroLength"
|
type: Literal["ZeroLength"] = "ZeroLength"
|
||||||
nodes: tuple[PositiveInt, PositiveInt]
|
nodes: tuple[PositiveInt, PositiveInt]
|
||||||
material_ids: tuple[PositiveInt, ...] = Field(..., min_length=1)
|
material_ids: tuple[PositiveInt, ...] = Field(..., min_length=1)
|
||||||
dofs: tuple[int, ...] = Field(..., min_length=1, description="DOF directions, 1-indexed (1..6).")
|
dofs: tuple[int, ...] = Field(
|
||||||
|
..., min_length=1, description="DOF directions, 1-indexed (1..6)."
|
||||||
|
)
|
||||||
do_rayleigh: bool = Field(
|
do_rayleigh: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
description=(
|
description=(
|
||||||
|
|
@ -104,7 +120,7 @@ class ZeroLengthElement(Entity):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ZeroLengthSectionElement(Entity):
|
class ZeroLengthSectionElement(ElementBase):
|
||||||
"""Two coincident nodes connected by a full :class:`Section` —
|
"""Two coincident nodes connected by a full :class:`Section` —
|
||||||
OpenSees ``element zeroLengthSection``.
|
OpenSees ``element zeroLengthSection``.
|
||||||
|
|
||||||
|
|
@ -121,10 +137,12 @@ class ZeroLengthSectionElement(Entity):
|
||||||
|
|
||||||
type: Literal["ZeroLengthSection"] = "ZeroLengthSection"
|
type: Literal["ZeroLengthSection"] = "ZeroLengthSection"
|
||||||
nodes: tuple[PositiveInt, PositiveInt]
|
nodes: tuple[PositiveInt, PositiveInt]
|
||||||
section_id: PositiveInt = Field(..., description="Section attached to the two coincident nodes.")
|
section_id: PositiveInt = Field(
|
||||||
|
..., description="Section attached to the two coincident nodes."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BeamWithHingesElement(Entity):
|
class BeamWithHingesElement(ElementBase):
|
||||||
"""Force-based beam with lumped plasticity at both ends —
|
"""Force-based beam with lumped plasticity at both ends —
|
||||||
``element beamWithHinges``.
|
``element beamWithHinges``.
|
||||||
|
|
||||||
|
|
@ -156,7 +174,7 @@ class BeamWithHingesElement(Entity):
|
||||||
geom_transf: Literal["Linear", "PDelta", "Corotational"] = "Linear"
|
geom_transf: Literal["Linear", "PDelta", "Corotational"] = "Linear"
|
||||||
|
|
||||||
|
|
||||||
class QuadElement(Entity):
|
class QuadElement(ElementBase):
|
||||||
"""Four-node 2D quadrilateral (plane-stress / plane-strain) —
|
"""Four-node 2D quadrilateral (plane-stress / plane-strain) —
|
||||||
``element quad`` / ``bbarQuad`` / ``enhancedQuad``.
|
``element quad`` / ``bbarQuad`` / ``enhancedQuad``.
|
||||||
|
|
||||||
|
|
@ -179,7 +197,8 @@ class QuadElement(Entity):
|
||||||
description="Surface pressure applied over the element (force / area).",
|
description="Surface pressure applied over the element (force / area).",
|
||||||
)
|
)
|
||||||
rho: float = Field(
|
rho: float = Field(
|
||||||
default=0.0, ge=0.0,
|
default=0.0,
|
||||||
|
ge=0.0,
|
||||||
description="Mass density override (kip·s²/in⁴). Leave 0 to use material rho.",
|
description="Mass density override (kip·s²/in⁴). Leave 0 to use material rho.",
|
||||||
)
|
)
|
||||||
b1: float = Field(
|
b1: float = Field(
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@ class Node(Entity):
|
||||||
mass: Mass6 = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
|
mass: Mass6 = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
|
||||||
restraint: Restraint6 = (False, False, False, False, False, False)
|
restraint: Restraint6 = (False, False, False, False, False, False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_name(self) -> str:
|
||||||
|
"""Fallback label for unnamed nodes (``N<id>``)."""
|
||||||
|
return f"N{self.id}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_restrained(self) -> bool:
|
def is_restrained(self) -> bool:
|
||||||
"""True if any DOF is fixed."""
|
"""True if any DOF is fixed."""
|
||||||
|
|
|
||||||
|
|
@ -1140,9 +1140,7 @@ class ModelRenderer:
|
||||||
if self._node_pd is None or len(project.nodes) == 0:
|
if self._node_pd is None or len(project.nodes) == 0:
|
||||||
return None
|
return None
|
||||||
pts = np.asarray(self._node_pd.points)
|
pts = np.asarray(self._node_pd.points)
|
||||||
labels = [
|
labels = [node.display_name for node in project.nodes]
|
||||||
(node.name.strip() if node.name.strip() else f"N{node.id}") for node in project.nodes
|
|
||||||
]
|
|
||||||
return self._plotter.add_point_labels(
|
return self._plotter.add_point_labels(
|
||||||
pts,
|
pts,
|
||||||
labels,
|
labels,
|
||||||
|
|
@ -1170,7 +1168,7 @@ class ModelRenderer:
|
||||||
if i is None or j is None:
|
if i is None or j is None:
|
||||||
continue
|
continue
|
||||||
centers.append((pts[i] + pts[j]) / 2.0)
|
centers.append((pts[i] + pts[j]) / 2.0)
|
||||||
labels.append(el.name.strip() if el.name.strip() else f"E{el.id}")
|
labels.append(el.display_name)
|
||||||
if not centers:
|
if not centers:
|
||||||
return None
|
return None
|
||||||
return self._plotter.add_point_labels(
|
return self._plotter.add_point_labels(
|
||||||
|
|
|
||||||
|
|
@ -492,7 +492,7 @@ class TableDock(QWidget):
|
||||||
table.setRowCount(len(nodes))
|
table.setRowCount(len(nodes))
|
||||||
for r, n in enumerate(nodes):
|
for r, n in enumerate(nodes):
|
||||||
self._put(table, r, 0, str(n.id), editable=False, data=n.id)
|
self._put(table, r, 0, str(n.id), editable=False, data=n.id)
|
||||||
self._put(table, r, 1, n.name, data=n.id)
|
self._put(table, r, 1, n.display_name, data=n.id)
|
||||||
for c, v in enumerate(n.coords, start=2):
|
for c, v in enumerate(n.coords, start=2):
|
||||||
self._put(table, r, c, f"{v:g}", data=n.id)
|
self._put(table, r, c, f"{v:g}", data=n.id)
|
||||||
for c, v in enumerate(n.mass, start=5):
|
for c, v in enumerate(n.mass, start=5):
|
||||||
|
|
@ -506,6 +506,12 @@ class TableDock(QWidget):
|
||||||
nid = self._row_key(table, row)
|
nid = self._row_key(table, row)
|
||||||
node = project.node(nid)
|
node = project.node(nid)
|
||||||
if col == 1:
|
if col == 1:
|
||||||
|
if not node.name.strip() and item.text().strip() == node.display_name:
|
||||||
|
# Confirmed the displayed fallback unchanged — not a real
|
||||||
|
# rename, so leave the stored name blank instead of
|
||||||
|
# persisting "N<id>" (and keep it off the undo stack).
|
||||||
|
self.refresh()
|
||||||
|
return
|
||||||
updated = self._dry_run(node, {"name": item.text()})
|
updated = self._dry_run(node, {"name": item.text()})
|
||||||
self._dispatch(UpdateNodeFieldsCommand(vm, nid, {"name": updated.name}))
|
self._dispatch(UpdateNodeFieldsCommand(vm, nid, {"name": updated.name}))
|
||||||
elif 2 <= col <= 4:
|
elif 2 <= col <= 4:
|
||||||
|
|
@ -537,7 +543,7 @@ class TableDock(QWidget):
|
||||||
table.setRowCount(len(elements))
|
table.setRowCount(len(elements))
|
||||||
for r, el in enumerate(elements):
|
for r, el in enumerate(elements):
|
||||||
self._put(table, r, 0, str(el.id), editable=False, data=el.id)
|
self._put(table, r, 0, str(el.id), editable=False, data=el.id)
|
||||||
self._put(table, r, 1, el.name, data=el.id)
|
self._put(table, r, 1, el.display_name, data=el.id)
|
||||||
self._put(table, r, 2, el.type, editable=False, data=el.id)
|
self._put(table, r, 2, el.type, editable=False, data=el.id)
|
||||||
self._put(table, r, 3, ", ".join(str(n) for n in el.nodes), data=el.id)
|
self._put(table, r, 3, ", ".join(str(n) for n in el.nodes), data=el.id)
|
||||||
self._put(
|
self._put(
|
||||||
|
|
@ -583,6 +589,12 @@ class TableDock(QWidget):
|
||||||
eid = self._row_key(table, row)
|
eid = self._row_key(table, row)
|
||||||
el = project.element(eid)
|
el = project.element(eid)
|
||||||
if col == 1:
|
if col == 1:
|
||||||
|
if not el.name.strip() and item.text().strip() == el.display_name:
|
||||||
|
# Confirmed the displayed fallback unchanged — not a real
|
||||||
|
# rename, so leave the stored name blank instead of
|
||||||
|
# persisting "E<id>" (and keep it off the undo stack).
|
||||||
|
self.refresh()
|
||||||
|
return
|
||||||
updated = self._dry_run(el, {"name": item.text()})
|
updated = self._dry_run(el, {"name": item.text()})
|
||||||
self._dispatch(UpdateElementFieldsCommand(vm, eid, {"name": updated.name}))
|
self._dispatch(UpdateElementFieldsCommand(vm, eid, {"name": updated.name}))
|
||||||
elif col == 3:
|
elif col == 3:
|
||||||
|
|
|
||||||
93
tests/unit/test_display_name.py
Normal file
93
tests/unit/test_display_name.py
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
"""Unit tests for the display-only ``display_name`` fallback on entities."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from otko.core import (
|
||||||
|
BeamWithHingesElement,
|
||||||
|
CorotTrussElement,
|
||||||
|
DispBeamColumn,
|
||||||
|
ElasticBeamColumn,
|
||||||
|
ElementBase,
|
||||||
|
ForceBeamColumn,
|
||||||
|
Node,
|
||||||
|
QuadElement,
|
||||||
|
TrussElement,
|
||||||
|
ZeroLengthElement,
|
||||||
|
ZeroLengthSectionElement,
|
||||||
|
)
|
||||||
|
from otko.core._base import Entity
|
||||||
|
|
||||||
|
_ALL_ELEMENT_TYPES = (
|
||||||
|
TrussElement,
|
||||||
|
CorotTrussElement,
|
||||||
|
ElasticBeamColumn,
|
||||||
|
ForceBeamColumn,
|
||||||
|
DispBeamColumn,
|
||||||
|
ZeroLengthElement,
|
||||||
|
ZeroLengthSectionElement,
|
||||||
|
BeamWithHingesElement,
|
||||||
|
QuadElement,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestEntityBase:
|
||||||
|
def test_base_fallback_is_empty(self) -> None:
|
||||||
|
e = Entity(id=1)
|
||||||
|
assert e.default_name == ""
|
||||||
|
assert e.display_name == ""
|
||||||
|
|
||||||
|
def test_display_name_is_not_persisted(self) -> None:
|
||||||
|
assert "display_name" not in Entity(id=1).model_dump()
|
||||||
|
assert "default_name" not in Entity(id=1).model_dump()
|
||||||
|
assert "display_name" not in Node(id=1, coords=(0, 0, 0)).model_dump()
|
||||||
|
|
||||||
|
|
||||||
|
class TestNodeDisplayName:
|
||||||
|
def test_blank_name_falls_back(self) -> None:
|
||||||
|
n = Node(id=1, coords=(0, 0, 0))
|
||||||
|
assert n.display_name == "N1"
|
||||||
|
|
||||||
|
def test_explicit_name_is_returned(self) -> None:
|
||||||
|
n = Node(id=1, name="Support A", coords=(0, 0, 0))
|
||||||
|
assert n.display_name == "Support A"
|
||||||
|
|
||||||
|
def test_whitespace_only_name_falls_back(self) -> None:
|
||||||
|
n = Node(id=2, name=" ", coords=(0, 0, 0))
|
||||||
|
assert n.display_name == "N2"
|
||||||
|
|
||||||
|
def test_explicit_name_is_stripped(self) -> None:
|
||||||
|
n = Node(id=1, name=" A1 ", coords=(0, 0, 0))
|
||||||
|
assert n.display_name == "A1"
|
||||||
|
|
||||||
|
def test_display_only_never_writes_back(self) -> None:
|
||||||
|
n = Node(id=1, coords=(0, 0, 0))
|
||||||
|
assert n.display_name == "N1"
|
||||||
|
assert n.name == ""
|
||||||
|
|
||||||
|
|
||||||
|
class TestElementDisplayName:
|
||||||
|
def test_all_element_types_share_element_base(self) -> None:
|
||||||
|
for cls in _ALL_ELEMENT_TYPES:
|
||||||
|
assert issubclass(cls, ElementBase)
|
||||||
|
|
||||||
|
def test_blank_truss_name_falls_back(self) -> None:
|
||||||
|
t = TrussElement(id=1, nodes=(1, 2), area=0.01, material_id=1)
|
||||||
|
assert t.display_name == "E1"
|
||||||
|
|
||||||
|
def test_explicit_truss_name_is_returned(self) -> None:
|
||||||
|
t = TrussElement(id=1, name="Chord", nodes=(1, 2), area=0.01, material_id=1)
|
||||||
|
assert t.display_name == "Chord"
|
||||||
|
|
||||||
|
def test_whitespace_only_truss_name_falls_back(self) -> None:
|
||||||
|
t = TrussElement(id=3, name=" ", nodes=(1, 2), area=0.01, material_id=1)
|
||||||
|
assert t.display_name == "E3"
|
||||||
|
|
||||||
|
def test_blank_quad_name_falls_back(self) -> None:
|
||||||
|
q = QuadElement(id=1, nodes=(1, 2, 3, 4), thickness=0.1, material_id=1)
|
||||||
|
assert q.display_name == "E1"
|
||||||
|
|
||||||
|
def test_display_only_never_writes_back(self) -> None:
|
||||||
|
t = TrussElement(id=1, nodes=(1, 2), area=0.01, material_id=1)
|
||||||
|
assert t.display_name == "E1"
|
||||||
|
assert t.name == ""
|
||||||
|
assert "display_name" not in t.model_dump()
|
||||||
Loading…
Reference in a new issue