From b67175966314831933019eb28536893c6738424e Mon Sep 17 00:00:00 2001 From: "KOKO\\Mimi" Date: Wed, 12 Aug 2026 18:46:02 +0900 Subject: [PATCH] feat(linear-static-mitc4-shell): step 0 - shell-semantic-model --- include/fesa/model/domain.hpp | 3 + include/fesa/model/model_types.hpp | 34 ++++++++++++ src/fesa/model/domain.cpp | 12 ++++ tests/unit/model/domain_test.cpp | 79 +++++++++++++++++++++++++++ tests/unit/model/model_types_test.cpp | 43 +++++++++++++++ 5 files changed, 171 insertions(+) diff --git a/include/fesa/model/domain.hpp b/include/fesa/model/domain.hpp index 2e5a012..c7f9dad 100644 --- a/include/fesa/model/domain.hpp +++ b/include/fesa/model/domain.hpp @@ -17,8 +17,11 @@ public: const std::vector& nodes() const noexcept; const std::vector& elements() const noexcept; + const std::vector& shellElements() const noexcept; const std::vector& materials() const noexcept; const std::vector& sections() const noexcept; + const std::vector& shellSections() const noexcept; + const std::vector& shellNodeInitialFrames() const noexcept; const std::vector& nodeSets() const noexcept; const std::vector& elementSets() const noexcept; const std::vector& steps() const noexcept; diff --git a/include/fesa/model/model_types.hpp b/include/fesa/model/model_types.hpp index 09d6c6d..4fff7c3 100644 --- a/include/fesa/model/model_types.hpp +++ b/include/fesa/model/model_types.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace fesa { @@ -40,6 +41,36 @@ struct GeneralBeamSection { SourceLocation location; }; +enum class ShellSourceElementType { + s4, + s4r +}; + +inline constexpr std::string_view kMitc4InternalFormulation{"FESA-MITC4"}; + +struct ShellSection { + std::string name; + double thickness; + EntityIndex materialIndex; + SourceLocation location; +}; + +struct Mitc4ShellDefinition { + SourceEntityId sourceId; + ShellSourceElementType sourceType; + std::array nodeIndices; + EntityIndex materialIndex; + EntityIndex sectionIndex; + SourceLocation location; +}; + +struct ShellNodeInitialFrame { + EntityIndex nodeIndex; + std::array director; + std::array tangentA; + std::array tangentB; +}; + struct EulerBeam3DDefinition { SourceEntityId sourceId; std::array nodeIndices; @@ -118,8 +149,11 @@ struct ModelDefinition { std::string heading; std::vector nodes; std::vector elements; + std::vector shellElements; std::vector materials; std::vector sections; + std::vector shellSections; + std::vector shellNodeInitialFrames; std::vector nodeSets; std::vector elementSets; std::vector parts; diff --git a/src/fesa/model/domain.cpp b/src/fesa/model/domain.cpp index c22c3e5..81dc7ea 100644 --- a/src/fesa/model/domain.cpp +++ b/src/fesa/model/domain.cpp @@ -16,6 +16,10 @@ const std::vector& Domain::elements() const noexcept { return definition_.elements; } +const std::vector& Domain::shellElements() const noexcept { + return definition_.shellElements; +} + const std::vector& Domain::materials() const noexcept { return definition_.materials; } @@ -24,6 +28,14 @@ const std::vector& Domain::sections() const noexcept { return definition_.sections; } +const std::vector& Domain::shellSections() const noexcept { + return definition_.shellSections; +} + +const std::vector& Domain::shellNodeInitialFrames() const noexcept { + return definition_.shellNodeInitialFrames; +} + const std::vector& Domain::nodeSets() const noexcept { return definition_.nodeSets; } diff --git a/tests/unit/model/domain_test.cpp b/tests/unit/model/domain_test.cpp index b329576..f2a9d27 100644 --- a/tests/unit/model/domain_test.cpp +++ b/tests/unit/model/domain_test.cpp @@ -182,3 +182,82 @@ TEST(DomainModel, MultipleIdentityInstancesDoNotMerge) { EXPECT_EQ(domain.elements()[0].nodeIndices[0], 0U); EXPECT_EQ(domain.elements()[1].nodeIndices[0], 2U); } + +// MITC4-MODEL-002 +TEST(DomainModel, ShellOwnershipPreservesResolvedAssignmentsAndOptionalFrames) { + fesa::ModelDefinition definition{}; + definition.sourcePath = "models/shell-owned.inp"; + definition.sourceContentIdentity = "fnv1a64:1234567890abcdef"; + definition.materials = { + {"Material-B", 70.0e9, 0.33, {"models/shell-owned.inp", 31U}}, + {"Material-A", 210.0e9, 0.3, {"models/shell-owned.inp", 30U}}}; + definition.shellSections = { + {"Section-B", 0.02, 0U, {"models/shell-owned.inp", 41U}}, + {"Section-A", 0.01, 1U, {"models/shell-owned.inp", 40U}}}; + definition.shellElements = { + {{"Shell-Instance", 20, "0020"}, + fesa::ShellSourceElementType::s4r, + {4U, 5U, 6U, 7U}, + 0U, + 0U, + {"models/shell-owned.inp", 21U}}, + {{"Shell-Instance", 10, "0010"}, + fesa::ShellSourceElementType::s4, + {0U, 1U, 2U, 3U}, + 1U, + 1U, + {"models/shell-owned.inp", 20U}}}; + definition.shellNodeInitialFrames = { + {4U, {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}}, + {0U, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 0.0, -1.0}}}; + + auto result = fesa::Domain::create(definition); + ASSERT_TRUE(result.hasValue()); + + definition.shellSections[0].thickness = -1.0; + definition.shellElements[0].sourceId.sourceLabelText = "mutated"; + definition.shellNodeInitialFrames[0].director[2] = -1.0; + + const fesa::Domain& domain = result.value(); + static_assert(std::is_same_v< + decltype(std::declval().shellElements()), + const std::vector&>); + static_assert(std::is_same_v< + decltype(std::declval().shellSections()), + const std::vector&>); + static_assert(std::is_same_v< + decltype(std::declval().shellNodeInitialFrames()), + const std::vector&>); + + ASSERT_EQ(domain.shellElements().size(), 2U); + EXPECT_EQ(domain.shellElements()[0].sourceId.sourceLabel, 20); + EXPECT_EQ(domain.shellElements()[0].sourceId.sourceLabelText, "0020"); + EXPECT_EQ(domain.shellElements()[1].sourceId.sourceLabel, 10); + EXPECT_EQ(domain.shellElements()[0].sourceType, fesa::ShellSourceElementType::s4r); + EXPECT_EQ(domain.shellElements()[1].sourceType, fesa::ShellSourceElementType::s4); + EXPECT_EQ(domain.shellElements()[0].nodeIndices[3], 7U); + EXPECT_EQ(domain.shellElements()[0].materialIndex, 0U); + EXPECT_EQ(domain.shellElements()[0].sectionIndex, 0U); + + ASSERT_EQ(domain.shellSections().size(), 2U); + EXPECT_EQ(domain.shellSections()[0].name, "Section-B"); + EXPECT_DOUBLE_EQ(domain.shellSections()[0].thickness, 0.02); + EXPECT_EQ(domain.shellSections()[0].materialIndex, 0U); + EXPECT_EQ(domain.shellSections()[1].name, "Section-A"); + + ASSERT_EQ(domain.shellNodeInitialFrames().size(), 2U); + EXPECT_EQ(domain.shellNodeInitialFrames()[0].nodeIndex, 4U); + EXPECT_EQ( + domain.shellNodeInitialFrames()[0].director, + (std::array{0.0, 0.0, 1.0})); + EXPECT_EQ( + domain.shellNodeInitialFrames()[0].tangentA, + (std::array{1.0, 0.0, 0.0})); + EXPECT_EQ( + domain.shellNodeInitialFrames()[0].tangentB, + (std::array{0.0, 1.0, 0.0})); + + auto noFramesResult = fesa::Domain::create(fesa::ModelDefinition{}); + ASSERT_TRUE(noFramesResult.hasValue()); + EXPECT_TRUE(noFramesResult.value().shellNodeInitialFrames().empty()); +} diff --git a/tests/unit/model/model_types_test.cpp b/tests/unit/model/model_types_test.cpp index 37e0bce..b0d07a6 100644 --- a/tests/unit/model/model_types_test.cpp +++ b/tests/unit/model/model_types_test.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -142,3 +143,45 @@ TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) { static_assert(!HasEquationId::value); static_assert(!HasEquationIds::value); } + +// MITC4-MODEL-001 +TEST(DomainModel, Mitc4ShellRecordsPreserveSourceAndInternalIdentity) { + const fesa::Mitc4ShellDefinition s4Element{ + {"Shell-Instance", 41, "0041"}, + fesa::ShellSourceElementType::s4, + {fesa::EntityIndex{3}, + fesa::EntityIndex{5}, + fesa::EntityIndex{8}, + fesa::EntityIndex{13}}, + fesa::EntityIndex{2}, + fesa::EntityIndex{7}, + {"models/shell.inp", 20U}}; + const fesa::Mitc4ShellDefinition s4rElement{ + {"Shell-Instance", 42, "0042"}, + fesa::ShellSourceElementType::s4r, + {fesa::EntityIndex{13}, + fesa::EntityIndex{8}, + fesa::EntityIndex{5}, + fesa::EntityIndex{3}}, + fesa::EntityIndex{2}, + fesa::EntityIndex{7}, + {"models/shell.inp", 21U}}; + + EXPECT_EQ(s4Element.sourceId.instanceName, "Shell-Instance"); + EXPECT_EQ(s4Element.sourceId.sourceLabel, 41); + EXPECT_EQ(s4Element.sourceId.sourceLabelText, "0041"); + EXPECT_EQ(s4Element.sourceType, fesa::ShellSourceElementType::s4); + EXPECT_EQ(s4rElement.sourceType, fesa::ShellSourceElementType::s4r); + EXPECT_NE(s4Element.sourceType, s4rElement.sourceType); + EXPECT_EQ( + fesa::kMitc4InternalFormulation, + std::string_view{"FESA-MITC4"}); + EXPECT_EQ( + s4Element.nodeIndices, + (std::array{3U, 5U, 8U, 13U})); + EXPECT_EQ(s4Element.materialIndex, 2U); + EXPECT_EQ(s4Element.sectionIndex, 7U); + + static_assert(!HasEquationId::value); + static_assert(!HasEquationIds::value); +}