fix(abaqus): map inline DS4 sets and reject beam sections
This commit is contained in:
@@ -916,7 +916,8 @@ class DomainMappingContext::Impl {
|
|||||||
"BEAM GENERAL SECTION follows the part mesh and optional sets.");
|
"BEAM GENERAL SECTION follows the part mesh and optional sets.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (model_element_family_ == ElementFamily::kShell) {
|
if (model_element_family_ == ElementFamily::kShell ||
|
||||||
|
model_element_family_ == ElementFamily::kThermalShell) {
|
||||||
InputFailure(
|
InputFailure(
|
||||||
"unsupported-mixed-element-model", block.location,
|
"unsupported-mixed-element-model", block.location,
|
||||||
block.canonical_name, "",
|
block.canonical_name, "",
|
||||||
@@ -1006,7 +1007,14 @@ class DomainMappingContext::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ParseElements(const KeywordBlock& block, RawPart& part) {
|
void ParseElements(const KeywordBlock& block, RawPart& part) {
|
||||||
if (!ValidateParameters(block, {"TYPE"})) {
|
const auto* type_parameter = Parameter(block, "TYPE");
|
||||||
|
const bool thermal_block =
|
||||||
|
type_parameter && type_parameter->value &&
|
||||||
|
AsciiCaseInsensitiveEquals(*type_parameter->value, "DS4");
|
||||||
|
if (!ValidateParameters(block,
|
||||||
|
thermal_block
|
||||||
|
? std::vector<std::string_view>{"TYPE", "ELSET"}
|
||||||
|
: std::vector<std::string_view>{"TYPE"})) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto* type = RequiredParameterValue(block, "TYPE");
|
const auto* type = RequiredParameterValue(block, "TYPE");
|
||||||
@@ -1047,6 +1055,17 @@ class DomainMappingContext::Impl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
model_element_family_ = element_family;
|
model_element_family_ = element_family;
|
||||||
|
std::optional<RawSet> inline_set;
|
||||||
|
if (Parameter(block, "ELSET")) {
|
||||||
|
const auto* name = RequiredParameterValue(block, "ELSET");
|
||||||
|
if (name == nullptr) return;
|
||||||
|
if (ContainsSetName(part.element_sets, *name)) {
|
||||||
|
InputFailure("duplicate-entity", block.location, block.canonical_name,
|
||||||
|
*name, "Element-set names are unique within their part.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inline_set = RawSet{*name, {}, block.location};
|
||||||
|
}
|
||||||
if (block.data.empty()) {
|
if (block.data.empty()) {
|
||||||
InputFailure(element_family == ElementFamily::kShell
|
InputFailure(element_family == ElementFamily::kShell
|
||||||
? "invalid-shell-connectivity"
|
? "invalid-shell-connectivity"
|
||||||
@@ -1105,8 +1124,12 @@ class DomainMappingContext::Impl {
|
|||||||
"Element labels are unique within a part.");
|
"Element labels are unique within a part.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (inline_set) inline_set->members.push_back(element.label);
|
||||||
part.elements.push_back(std::move(element));
|
part.elements.push_back(std::move(element));
|
||||||
}
|
}
|
||||||
|
// Inline membership follows the block's source rows and uses the same
|
||||||
|
// part-set namespace and section resolution as an explicit ELSET.
|
||||||
|
if (inline_set) part.element_sets.push_back(std::move(*inline_set));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParseShellSection(const KeywordBlock& block, RawPart& part) {
|
void ParseShellSection(const KeywordBlock& block, RawPart& part) {
|
||||||
|
|||||||
@@ -936,4 +936,115 @@ TEST(ThermalDomainMapping, RejectsMixedElementsAndWrongThermalMaterialScopes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ThermalDomainMapping, InlineElementSetResolvesSectionCaseInsensitively) {
|
||||||
|
auto deck = Replace(ThermalDeck(), "*Element, type=DS4",
|
||||||
|
"*Element, type=ds4, elset=inlineShells");
|
||||||
|
deck = Replace(deck, "*Elset, elset=E\n1\n", "");
|
||||||
|
deck = Replace(deck, "*Shell Section, elset=E",
|
||||||
|
"*Shell Section, elset=INLINESHELLS");
|
||||||
|
const TemporaryInput input{deck};
|
||||||
|
const auto parsed = fesa::AbaqusInputReader{}.Read(input.Path());
|
||||||
|
ASSERT_TRUE(parsed.HasValue());
|
||||||
|
const auto result = fesa::AbaqusDomainMapper{}.Map(parsed.Value());
|
||||||
|
ASSERT_TRUE(result.HasValue())
|
||||||
|
<< result.GetStatus().Diagnostics().front().message;
|
||||||
|
const auto& domain = result.Value();
|
||||||
|
ASSERT_EQ(domain.ElementSets().size(), 1U);
|
||||||
|
const auto& set = domain.ElementSets()[0];
|
||||||
|
EXPECT_EQ(set.name, "inlineShells");
|
||||||
|
EXPECT_EQ(set.instance_name, "I");
|
||||||
|
EXPECT_EQ(set.element_indices, std::vector<fesa::EntityIndex>{0U});
|
||||||
|
EXPECT_EQ(set.location.file, input.Path());
|
||||||
|
EXPECT_EQ(set.location.line, LineOf(deck, "*Element"));
|
||||||
|
ASSERT_EQ(domain.ThermalElements().Size(), 1U);
|
||||||
|
EXPECT_EQ(domain.ThermalElements()[0].ThermalSectionIndex(), 0U);
|
||||||
|
EXPECT_EQ(domain.ThermalSections()[0].name, "INLINESHELLS");
|
||||||
|
EXPECT_EQ(domain.ThermalSections()[0].location.line,
|
||||||
|
LineOf(deck, "*Shell Section"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThermalDomainMapping, InlineElementSetPreservesElementRowOrder) {
|
||||||
|
auto deck = Replace(ThermalDeck(), "*Element, type=DS4\n1, 1, 2, 3, 4",
|
||||||
|
"5,2,0,0\n6,3,0,0\n7,3,1,0\n8,2,1,0\n"
|
||||||
|
"*Element, type=DS4, elset=E\n7,1,2,3,4\n3,5,6,7,8");
|
||||||
|
deck = Replace(deck, "*Elset, elset=E\n1\n", "");
|
||||||
|
const auto result = MapText(deck);
|
||||||
|
ASSERT_TRUE(result.HasValue())
|
||||||
|
<< result.GetStatus().Diagnostics().front().message;
|
||||||
|
const auto& domain = result.Value();
|
||||||
|
ASSERT_EQ(domain.ElementSets().size(), 1U);
|
||||||
|
ASSERT_EQ(domain.ElementSets()[0].element_indices.size(), 2U);
|
||||||
|
EXPECT_EQ(domain.ElementSets()[0].element_indices,
|
||||||
|
(std::vector<fesa::EntityIndex>{0U, 1U}));
|
||||||
|
EXPECT_EQ(domain.ThermalElements()[0].SourceId().source_label, 7);
|
||||||
|
EXPECT_EQ(domain.ThermalElements()[1].SourceId().source_label, 3);
|
||||||
|
EXPECT_EQ(domain.ThermalElements()[0].ThermalSectionIndex(), 0U);
|
||||||
|
EXPECT_EQ(domain.ThermalElements()[1].ThermalSectionIndex(), 0U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThermalDomainMapping,
|
||||||
|
InlineElementSetRejectsInvalidAndDuplicateDefinitions) {
|
||||||
|
auto deck = Replace(ThermalDeck(), "*Elset, elset=E\n1\n", "");
|
||||||
|
for (const std::string option :
|
||||||
|
{"elset", "elset=", "elset=E, unknown=1", "elset=E, elset=e"}) {
|
||||||
|
SCOPED_TRACE(option);
|
||||||
|
const auto result = MapText(
|
||||||
|
Replace(deck, "*Element, type=DS4", "*Element, type=DS4, " + option));
|
||||||
|
ASSERT_FALSE(result.HasValue());
|
||||||
|
EXPECT_EQ(result.GetStatus().Diagnostics()[0].keyword, "ELEMENT");
|
||||||
|
EXPECT_EQ(result.GetStatus().Diagnostics()[0].location.line,
|
||||||
|
LineOf(deck, "*Element"));
|
||||||
|
}
|
||||||
|
ExpectRejected(Replace(ThermalDeck(), "*Element, type=DS4",
|
||||||
|
"*Element, type=DS4, elset=e"),
|
||||||
|
"ELSET");
|
||||||
|
deck = Replace(deck, "*Element, type=DS4", "*Element, type=DS4, elset=E");
|
||||||
|
ExpectRejected(
|
||||||
|
Replace(deck, "*Shell Section",
|
||||||
|
"*Element, type=DS4, elset=e\n2,1,2,3,4\n*Shell Section"),
|
||||||
|
"ELEMENT");
|
||||||
|
auto conflicting =
|
||||||
|
Replace(deck, "*Shell Section", "*Elset, elset=Other\n1\n*Shell Section");
|
||||||
|
conflicting =
|
||||||
|
Replace(conflicting, "*End Part",
|
||||||
|
"*Shell Section, elset=Other, material=M\n.5\n*End Part");
|
||||||
|
ExpectRejected(conflicting, "SHELL SECTION");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThermalDomainMapping, RejectsBeamSectionAndBeamOnlyChildrenAtTheirSource) {
|
||||||
|
const std::string beam =
|
||||||
|
"*Beam General Section, elset=E, material=M, "
|
||||||
|
"section=GENERAL\n1,1,0,1,1\n1,0,0\n";
|
||||||
|
for (const std::string suffix :
|
||||||
|
{"", "*Section Points\n0,0\n", "*Transverse Shear Stiffness\n1,1,1\n"}) {
|
||||||
|
const auto deck =
|
||||||
|
Replace(ThermalDeck(), "*End Part", beam + suffix + "*End Part");
|
||||||
|
const auto result = MapText(deck);
|
||||||
|
ASSERT_FALSE(result.HasValue());
|
||||||
|
EXPECT_EQ(result.GetStatus().Diagnostics()[0].keyword,
|
||||||
|
"BEAM GENERAL SECTION");
|
||||||
|
EXPECT_EQ(result.GetStatus().Diagnostics()[0].location.line,
|
||||||
|
LineOf(deck, "*Beam General Section"));
|
||||||
|
}
|
||||||
|
ExpectRejected(
|
||||||
|
Replace(ThermalDeck(), "*End Part", "*Section Points\n0,0\n*End Part"),
|
||||||
|
"SECTION POINTS");
|
||||||
|
ExpectRejected(Replace(ThermalDeck(), "*End Part",
|
||||||
|
"*Transverse Shear Stiffness\n1,1,1\n*End Part"),
|
||||||
|
"TRANSVERSE SHEAR STIFFNESS");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThermalDomainMapping, RejectsMalformedDs4ConnectivityAndElementOptions) {
|
||||||
|
for (const std::string row :
|
||||||
|
{"1,1,2,3", "1,1,2,3,4,1", "1,1,2,3,3", "1,0,2,3,4", "1,-1,2,3,4",
|
||||||
|
"1,1.5,2,3,4", "1,1,2,3,99", "0,1,2,3,4", "1.5,1,2,3,4",
|
||||||
|
"1,1,2,3,4\n1,1,2,3,4"}) {
|
||||||
|
SCOPED_TRACE(row);
|
||||||
|
ExpectRejected(Replace(ThermalDeck(), "1, 1, 2, 3, 4", row), "ELEMENT");
|
||||||
|
}
|
||||||
|
ExpectRejected(Replace(ThermalDeck(), "*Element, type=DS4",
|
||||||
|
"*Element, type=DS4, unknown=1"),
|
||||||
|
"ELEMENT");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
Reference in New Issue
Block a user