chore: adopt remaining local development state
Catch-all for the intermixed residue of the unpushed otko-development work ported into this tree: combinations/console-dock/quick-guide wiring across commands, core, services, views and tests; repo-wide ruff-format normalization; README/CONTRIBUTING updates; and the toolbar default (both toolbars now open in the top area, quick guide text updated). Splitting this further would require hunk-level surgery with low confidence; the preceding commits in this branch isolate the self-contained features.
This commit is contained in:
parent
cb4bf8fccd
commit
ba783718d4
152 changed files with 3394 additions and 1651 deletions
|
|
@ -29,10 +29,17 @@ from otko.services.section_properties import (
|
|||
def test_rectangular_patch_expands_to_correct_count() -> None:
|
||||
sec = FiberSection(
|
||||
id=1,
|
||||
patches=[RectangularPatch(
|
||||
material_id=1, n_fib_y=5, n_fib_z=4,
|
||||
y_i=-0.1, z_i=-0.1, y_j=0.1, z_j=0.1,
|
||||
)],
|
||||
patches=[
|
||||
RectangularPatch(
|
||||
material_id=1,
|
||||
n_fib_y=5,
|
||||
n_fib_z=4,
|
||||
y_i=-0.1,
|
||||
z_i=-0.1,
|
||||
y_j=0.1,
|
||||
z_j=0.1,
|
||||
)
|
||||
],
|
||||
)
|
||||
fibres = expand_fibres(sec)
|
||||
assert fibres.shape == (20, 3)
|
||||
|
|
@ -43,10 +50,17 @@ def test_rectangular_patch_expands_to_correct_count() -> None:
|
|||
def test_rectangular_patch_centroid_at_origin() -> None:
|
||||
sec = FiberSection(
|
||||
id=1,
|
||||
patches=[RectangularPatch(
|
||||
material_id=1, n_fib_y=8, n_fib_z=8,
|
||||
y_i=-0.15, z_i=-0.15, y_j=0.15, z_j=0.15,
|
||||
)],
|
||||
patches=[
|
||||
RectangularPatch(
|
||||
material_id=1,
|
||||
n_fib_y=8,
|
||||
n_fib_z=8,
|
||||
y_i=-0.15,
|
||||
z_i=-0.15,
|
||||
y_j=0.15,
|
||||
z_j=0.15,
|
||||
)
|
||||
],
|
||||
)
|
||||
props = compute_section_props(sec)
|
||||
assert props.centroid_y == pytest.approx(0.0, abs=1e-12)
|
||||
|
|
@ -58,13 +72,18 @@ def test_circular_patch_area_approaches_pi_r_squared() -> None:
|
|||
r = 0.15
|
||||
sec = FiberSection(
|
||||
id=1,
|
||||
patches=[CircularPatch(
|
||||
material_id=1, n_fib_circ=32, n_fib_rad=8,
|
||||
r_inner=0.0, r_outer=r,
|
||||
)],
|
||||
patches=[
|
||||
CircularPatch(
|
||||
material_id=1,
|
||||
n_fib_circ=32,
|
||||
n_fib_rad=8,
|
||||
r_inner=0.0,
|
||||
r_outer=r,
|
||||
)
|
||||
],
|
||||
)
|
||||
props = compute_section_props(sec)
|
||||
expected = math.pi * r ** 2
|
||||
expected = math.pi * r**2
|
||||
# With 32×8 = 256 fibres, area should be close (within ~1%).
|
||||
assert props.area == pytest.approx(expected, rel=0.01)
|
||||
assert props.centroid_y == pytest.approx(0.0, abs=1e-6)
|
||||
|
|
@ -75,11 +94,17 @@ def test_circular_patch_area_approaches_pi_r_squared() -> None:
|
|||
def test_straight_layer_expands_correctly() -> None:
|
||||
sec = FiberSection(
|
||||
id=1,
|
||||
layers=[StraightLayer(
|
||||
material_id=2, n_bars=4, bar_area=0.0005,
|
||||
y_start=-0.1, z_start=-0.12,
|
||||
y_end=0.1, z_end=-0.12,
|
||||
)],
|
||||
layers=[
|
||||
StraightLayer(
|
||||
material_id=2,
|
||||
n_bars=4,
|
||||
bar_area=0.0005,
|
||||
y_start=-0.1,
|
||||
z_start=-0.12,
|
||||
y_end=0.1,
|
||||
z_end=-0.12,
|
||||
)
|
||||
],
|
||||
)
|
||||
fibres = expand_fibres(sec)
|
||||
assert fibres.shape == (4, 3)
|
||||
|
|
@ -91,7 +116,8 @@ def test_straight_layer_expands_correctly() -> None:
|
|||
# ── SectionAggregator ───────────────────────────────────────────────
|
||||
def test_section_aggregator_schema() -> None:
|
||||
agg = SectionAggregator(
|
||||
id=5, name="Fiber+Torsion",
|
||||
id=5,
|
||||
name="Fiber+Torsion",
|
||||
section_id=1,
|
||||
pairings=[AggregatorDOF(material_id=3, dof="T")],
|
||||
)
|
||||
|
|
@ -102,20 +128,27 @@ def test_section_aggregator_schema() -> None:
|
|||
# ── Mixed section → Iy / Iz ─────────────────────────────────────────
|
||||
def test_Iz_of_rect_matches_analytical() -> None:
|
||||
"""Iz = b·h³/12 for a rectangle centred at origin."""
|
||||
b, h = 0.3, 0.5 # y-range: -0.15..0.15, z-range: -0.25..0.25
|
||||
b, h = 0.3, 0.5 # y-range: -0.15..0.15, z-range: -0.25..0.25
|
||||
sec = FiberSection(
|
||||
id=1,
|
||||
patches=[RectangularPatch(
|
||||
material_id=1, n_fib_y=20, n_fib_z=20,
|
||||
y_i=-b / 2, z_i=-h / 2, y_j=b / 2, z_j=h / 2,
|
||||
)],
|
||||
patches=[
|
||||
RectangularPatch(
|
||||
material_id=1,
|
||||
n_fib_y=20,
|
||||
n_fib_z=20,
|
||||
y_i=-b / 2,
|
||||
z_i=-h / 2,
|
||||
y_j=b / 2,
|
||||
z_j=h / 2,
|
||||
)
|
||||
],
|
||||
)
|
||||
props = compute_section_props(sec)
|
||||
# Iz = about z-axis = b·h³/12? No — our convention:
|
||||
# Iz = Σ A_i · (y_i - ȳ)² (second moment about the z-axis).
|
||||
# For rect: Iz = h·b³/12.
|
||||
Iz_exact = h * b ** 3 / 12.0
|
||||
Iy_exact = b * h ** 3 / 12.0
|
||||
Iz_exact = h * b**3 / 12.0
|
||||
Iy_exact = b * h**3 / 12.0
|
||||
assert props.Iz == pytest.approx(Iz_exact, rel=0.01)
|
||||
assert props.Iy == pytest.approx(Iy_exact, rel=0.01)
|
||||
|
||||
|
|
@ -123,29 +156,50 @@ def test_Iz_of_rect_matches_analytical() -> None:
|
|||
# ── Round-trip persistence ───────────────────────────────────────────
|
||||
def test_fiber_section_with_patches_round_trips(tmp_path) -> None: # type: ignore[no-untyped-def]
|
||||
from otko.core import Node, ElasticBeamColumn
|
||||
|
||||
p = Project(
|
||||
nodes=[Node(id=1, coords=(0, 0, 0)), Node(id=2, coords=(1, 0, 0))],
|
||||
sections=[
|
||||
FiberSection(
|
||||
id=1, name="RC-Beam",
|
||||
patches=[RectangularPatch(
|
||||
material_id=1, n_fib_y=4, n_fib_z=4,
|
||||
y_i=-0.15, z_i=-0.25, y_j=0.15, z_j=0.25,
|
||||
)],
|
||||
layers=[StraightLayer(
|
||||
material_id=2, n_bars=3, bar_area=0.0005,
|
||||
y_start=-0.12, z_start=-0.22,
|
||||
y_end=0.12, z_end=-0.22,
|
||||
)],
|
||||
id=1,
|
||||
name="RC-Beam",
|
||||
patches=[
|
||||
RectangularPatch(
|
||||
material_id=1,
|
||||
n_fib_y=4,
|
||||
n_fib_z=4,
|
||||
y_i=-0.15,
|
||||
z_i=-0.25,
|
||||
y_j=0.15,
|
||||
z_j=0.25,
|
||||
)
|
||||
],
|
||||
layers=[
|
||||
StraightLayer(
|
||||
material_id=2,
|
||||
n_bars=3,
|
||||
bar_area=0.0005,
|
||||
y_start=-0.12,
|
||||
z_start=-0.22,
|
||||
y_end=0.12,
|
||||
z_end=-0.22,
|
||||
)
|
||||
],
|
||||
),
|
||||
SectionAggregator(
|
||||
id=2, name="Agg", section_id=1,
|
||||
id=2,
|
||||
name="Agg",
|
||||
section_id=1,
|
||||
pairings=[AggregatorDOF(material_id=3, dof="T")],
|
||||
),
|
||||
],
|
||||
elements=[ElasticBeamColumn(
|
||||
id=1, nodes=(1, 2), section_id=1,
|
||||
)],
|
||||
elements=[
|
||||
ElasticBeamColumn(
|
||||
id=1,
|
||||
nodes=(1, 2),
|
||||
section_id=1,
|
||||
)
|
||||
],
|
||||
)
|
||||
path = tmp_path / "fib.osmodel"
|
||||
save_project(p, path)
|
||||
|
|
|
|||
Loading…
Reference in a new issue