feat(abaqus-subset-completion): step 3 — material-section-and-shear-defaults
This commit is contained in:
@@ -1,14 +1,19 @@
|
|||||||
#include <fesa/io/abaqus/semantic_mapper.hpp>
|
#include <fesa/io/abaqus/semantic_mapper.hpp>
|
||||||
|
|
||||||
#include <fesa/io/abaqus/active_input.hpp>
|
#include <fesa/io/abaqus/active_input.hpp>
|
||||||
|
#include <fesa/io/abaqus/set_resolver.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
|
#include <cmath>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <iterator>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <set>
|
||||||
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -28,6 +33,24 @@ const std::string* parameter(
|
|||||||
return found == record.parameters.end() ? nullptr : &found->second;
|
return found == record.parameters.end() ? nullptr : &found->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SourceLocation data_source(
|
||||||
|
const DeckRecord& record,
|
||||||
|
const std::size_t row) {
|
||||||
|
return row < record.data_sources.size()
|
||||||
|
? record.data_sources[row]
|
||||||
|
: record.source;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_number(const std::string_view text, double& value) {
|
||||||
|
const auto parsed = std::from_chars(
|
||||||
|
text.data(),
|
||||||
|
text.data() + text.size(),
|
||||||
|
value,
|
||||||
|
std::chars_format::general);
|
||||||
|
return parsed.ec == std::errc{} &&
|
||||||
|
parsed.ptr == text.data() + text.size() && std::isfinite(value);
|
||||||
|
}
|
||||||
|
|
||||||
class DeckMapper final {
|
class DeckMapper final {
|
||||||
public:
|
public:
|
||||||
explicit DeckMapper(const ParsedDeck& deck) : deck_{deck} {}
|
explicit DeckMapper(const ParsedDeck& deck) : deck_{deck} {}
|
||||||
@@ -39,15 +62,20 @@ public:
|
|||||||
return failure();
|
return failure();
|
||||||
}
|
}
|
||||||
active_records_ = selected.input->part_records;
|
active_records_ = selected.input->part_records;
|
||||||
assembly_records_ = selected.input->assembly_records;
|
flat_ = selected.input->flat;
|
||||||
part_name_ = std::move(selected.input->part_name);
|
part_name_ = std::move(selected.input->part_name);
|
||||||
instance_name_ = std::move(selected.input->instance_name);
|
instance_name_ = std::move(selected.input->instance_name);
|
||||||
|
|
||||||
collect_materials();
|
collect_materials();
|
||||||
collect_nodes();
|
collect_nodes();
|
||||||
collect_raw_sets(active_records_, false);
|
SetResolutionResult resolved_sets = resolve_sets(deck_);
|
||||||
if (!assembly_records_.empty()) {
|
if (!resolved_sets.diagnostics.empty()) {
|
||||||
collect_raw_sets(assembly_records_, true);
|
diagnostics_.insert(
|
||||||
|
diagnostics_.end(),
|
||||||
|
std::make_move_iterator(resolved_sets.diagnostics.begin()),
|
||||||
|
std::make_move_iterator(resolved_sets.diagnostics.end()));
|
||||||
|
} else {
|
||||||
|
collect_resolved_sets(resolved_sets.sets);
|
||||||
}
|
}
|
||||||
collect_sections();
|
collect_sections();
|
||||||
collect_elements();
|
collect_elements();
|
||||||
@@ -66,6 +94,19 @@ private:
|
|||||||
SectionId section;
|
SectionId section;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MaterialProperties final {
|
||||||
|
MaterialId id;
|
||||||
|
double young;
|
||||||
|
double poisson;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MaterialDefinition final {
|
||||||
|
std::string name;
|
||||||
|
SourceLocation source;
|
||||||
|
const DeckRecord* elastic = nullptr;
|
||||||
|
bool duplicate = false;
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] DomainBuildResult failure() {
|
[[nodiscard]] DomainBuildResult failure() {
|
||||||
return {std::nullopt, std::move(diagnostics_)};
|
return {std::nullopt, std::move(diagnostics_)};
|
||||||
}
|
}
|
||||||
@@ -158,47 +199,105 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void collect_materials() {
|
void collect_materials() {
|
||||||
const std::string* current_name = nullptr;
|
std::vector<MaterialDefinition> definitions;
|
||||||
|
std::map<std::string, std::size_t, std::less<>> first_definition;
|
||||||
|
std::optional<std::size_t> current_definition;
|
||||||
|
|
||||||
for (const DeckRecord& record : deck_.global_records) {
|
for (const DeckRecord& record : deck_.global_records) {
|
||||||
if (record.keyword == "MATERIAL") {
|
if (record.keyword == "MATERIAL") {
|
||||||
current_name = require_parameter(record, "NAME");
|
const std::string* name = require_parameter(record, "NAME");
|
||||||
|
if (name == nullptr) {
|
||||||
|
current_definition.reset();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool duplicate = first_definition.contains(*name);
|
||||||
|
if (duplicate) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.duplicate_material",
|
||||||
|
"Material '" + *name +
|
||||||
|
"' is defined more than once.",
|
||||||
|
record.source);
|
||||||
|
}
|
||||||
|
definitions.push_back({
|
||||||
|
*name,
|
||||||
|
record.source,
|
||||||
|
nullptr,
|
||||||
|
duplicate,
|
||||||
|
});
|
||||||
|
current_definition = definitions.size() - 1U;
|
||||||
|
first_definition.try_emplace(*name, *current_definition);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (record.keyword != "ELASTIC") {
|
if (record.keyword != "ELASTIC") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (current_name == nullptr) {
|
if (!current_definition.has_value()) {
|
||||||
add_error(
|
add_error(
|
||||||
"abaqus.semantic.elastic_without_material",
|
"abaqus.semantic.elastic_without_material",
|
||||||
"*ELASTIC requires a preceding *MATERIAL.",
|
"*ELASTIC requires a preceding *MATERIAL.",
|
||||||
record.source);
|
record.source);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (record.data.empty() || record.data[0].size() < 2U) {
|
|
||||||
|
MaterialDefinition& definition =
|
||||||
|
definitions[*current_definition];
|
||||||
|
if (definition.elastic != nullptr) {
|
||||||
add_error(
|
add_error(
|
||||||
"abaqus.semantic.invalid_elastic_data",
|
"abaqus.semantic.invalid_elastic_data",
|
||||||
"*ELASTIC requires Young's modulus and Poisson ratio.",
|
"A material accepts exactly one *ELASTIC record.",
|
||||||
record.source);
|
record.source);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
definition.elastic = &record;
|
||||||
|
}
|
||||||
|
|
||||||
const auto young =
|
for (const MaterialDefinition& definition : definitions) {
|
||||||
parse_real(record.data[0][0], record, "Young's modulus");
|
if (definition.duplicate) {
|
||||||
const auto poisson =
|
continue;
|
||||||
parse_real(record.data[0][1], record, "Poisson ratio");
|
}
|
||||||
if (!young.has_value() || !poisson.has_value()) {
|
if (definition.elastic == nullptr) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.missing_elastic",
|
||||||
|
"Material '" + definition.name +
|
||||||
|
"' requires one *ELASTIC record.",
|
||||||
|
definition.source);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeckRecord& elastic = *definition.elastic;
|
||||||
|
const SourceLocation source =
|
||||||
|
elastic.data.empty() ? elastic.source
|
||||||
|
: data_source(elastic, 0U);
|
||||||
|
if (elastic.data.size() != 1U ||
|
||||||
|
elastic.data[0].size() != 2U ||
|
||||||
|
elastic.data[0][0].empty() ||
|
||||||
|
elastic.data[0][1].empty()) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.invalid_elastic_data",
|
||||||
|
"*ELASTIC requires exactly one E, nu data row.",
|
||||||
|
source);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double young = 0.0;
|
||||||
|
double poisson = 0.0;
|
||||||
|
if (!parse_number(elastic.data[0][0], young) ||
|
||||||
|
!parse_number(elastic.data[0][1], poisson) ||
|
||||||
|
young <= 0.0 || poisson <= -1.0 || poisson >= 0.5) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.invalid_elastic_data",
|
||||||
|
"*ELASTIC requires finite E > 0 and -1 < nu < 0.5.",
|
||||||
|
source);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaterialId id{next_material_id_++};
|
const MaterialId id{next_material_id_++};
|
||||||
if (!material_ids_.emplace(*current_name, id).second) {
|
materials_.emplace(
|
||||||
add_error(
|
definition.name,
|
||||||
"abaqus.semantic.duplicate_material",
|
MaterialProperties{id, young, poisson});
|
||||||
"Material '" + *current_name + "' is defined more than once.",
|
builder_.add_material(
|
||||||
record.source);
|
{id, definition.name, young, poisson});
|
||||||
continue;
|
|
||||||
}
|
|
||||||
builder_.add_material({id, *current_name, *young, *poisson});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,125 +335,267 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void collect_raw_sets(
|
void collect_resolved_sets(const std::vector<ResolvedSet>& sets) {
|
||||||
const std::span<const DeckRecord> records,
|
for (const ResolvedSet& set : sets) {
|
||||||
const bool assembly_scope) {
|
const bool mesh_scope =
|
||||||
for (const DeckRecord& record : records) {
|
(flat_ && set.scope == ResolvedSetScope::global) ||
|
||||||
const bool is_node_set = record.keyword == "NSET";
|
(!flat_ && set.scope == ResolvedSetScope::part &&
|
||||||
const bool is_element_set = record.keyword == "ELSET";
|
set.scope_name == part_name_);
|
||||||
if (!is_node_set && !is_element_set) {
|
const bool assembly_scope =
|
||||||
|
!flat_ && set.scope == ResolvedSetScope::assembly &&
|
||||||
|
deck_.assembly.has_value() &&
|
||||||
|
set.scope_name == deck_.assembly->name;
|
||||||
|
if (!mesh_scope && !assembly_scope) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string* name =
|
RawSetMap& target = set.kind == ResolvedSetKind::node
|
||||||
require_parameter(record, is_node_set ? "NSET" : "ELSET");
|
? raw_node_sets_
|
||||||
if (name == nullptr) {
|
: raw_element_sets_;
|
||||||
continue;
|
std::vector<std::int64_t>& labels = target[set.set_name];
|
||||||
}
|
labels.insert(
|
||||||
if (assembly_scope) {
|
labels.end(),
|
||||||
const std::string* instance = parameter(record, "INSTANCE");
|
set.sorted_unique_labels.begin(),
|
||||||
if (instance == nullptr || *instance != instance_name_) {
|
set.sorted_unique_labels.end());
|
||||||
add_error(
|
std::ranges::sort(labels);
|
||||||
"abaqus.semantic.wrong_instance",
|
labels.erase(std::ranges::unique(labels).begin(), labels.end());
|
||||||
"Assembly set '" + *name +
|
|
||||||
"' must reference the active Instance.",
|
|
||||||
record.source);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::int64_t> labels;
|
if (mesh_scope && set.kind == ResolvedSetKind::element) {
|
||||||
for (const auto& row : record.data) {
|
active_element_sets_[set.set_name] =
|
||||||
for (const std::string& field : row) {
|
set.sorted_unique_labels;
|
||||||
if (field.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
const auto label =
|
|
||||||
parse_label(field, record, "set member");
|
|
||||||
if (label.has_value()) {
|
|
||||||
labels.push_back(*label);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RawSetMap& sets = is_node_set ? raw_node_sets_ : raw_element_sets_;
|
|
||||||
sets[*name] = std::move(labels);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void collect_sections() {
|
void collect_sections() {
|
||||||
for (const DeckRecord& record : active_records_) {
|
for (std::size_t index = 0; index < active_records_.size(); ++index) {
|
||||||
|
const DeckRecord& record = active_records_[index];
|
||||||
|
if (record.keyword == "TRANSVERSE SHEAR STIFFNESS") {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.orphan_transverse_shear",
|
||||||
|
"*TRANSVERSE SHEAR STIFFNESS must immediately follow "
|
||||||
|
"*BEAM GENERAL SECTION.",
|
||||||
|
record.source);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (record.keyword != "BEAM GENERAL SECTION") {
|
if (record.keyword != "BEAM GENERAL SECTION") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DeckRecord* shear = nullptr;
|
||||||
|
if (index + 1U < active_records_.size() &&
|
||||||
|
active_records_[index + 1U].keyword ==
|
||||||
|
"TRANSVERSE SHEAR STIFFNESS") {
|
||||||
|
shear = &active_records_[index + 1U];
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool valid = true;
|
||||||
|
const std::string* section_type =
|
||||||
|
require_parameter(record, "SECTION");
|
||||||
const std::string* element_set =
|
const std::string* element_set =
|
||||||
require_parameter(record, "ELSET");
|
require_parameter(record, "ELSET");
|
||||||
const std::string* material_name =
|
const std::string* material_name =
|
||||||
require_parameter(record, "MATERIAL");
|
require_parameter(record, "MATERIAL");
|
||||||
if (element_set == nullptr || material_name == nullptr) {
|
if (section_type == nullptr || element_set == nullptr ||
|
||||||
continue;
|
material_name == nullptr) {
|
||||||
}
|
valid = false;
|
||||||
const auto material = material_ids_.find(*material_name);
|
} else if (*section_type != "GENERAL") {
|
||||||
if (material == material_ids_.end()) {
|
|
||||||
add_error(
|
add_error(
|
||||||
"abaqus.semantic.missing_material",
|
"abaqus.semantic.unsupported_section",
|
||||||
"Beam section references missing Material '" +
|
"Phase 1 supports only SECTION=GENERAL.",
|
||||||
*material_name + "'.",
|
|
||||||
record.source);
|
record.source);
|
||||||
continue;
|
valid = false;
|
||||||
}
|
|
||||||
if (record.data.size() < 2U ||
|
|
||||||
record.data[0].size() < 5U ||
|
|
||||||
record.data[1].size() < 3U) {
|
|
||||||
add_error(
|
|
||||||
"abaqus.semantic.invalid_section_data",
|
|
||||||
"*BEAM GENERAL SECTION requires general properties and "
|
|
||||||
"an orientation vector.",
|
|
||||||
record.source);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto area = parse_real(record.data[0][0], record, "area");
|
if (element_set != nullptr &&
|
||||||
const auto iy = parse_real(record.data[0][1], record, "Iy");
|
!assigned_element_set_names_.insert(*element_set).second) {
|
||||||
const auto iz = parse_real(record.data[0][3], record, "Iz");
|
|
||||||
const auto torsion =
|
|
||||||
parse_real(record.data[0][4], record, "torsion J");
|
|
||||||
const auto ox =
|
|
||||||
parse_real(record.data[1][0], record, "orientation");
|
|
||||||
const auto oy =
|
|
||||||
parse_real(record.data[1][1], record, "orientation");
|
|
||||||
const auto oz =
|
|
||||||
parse_real(record.data[1][2], record, "orientation");
|
|
||||||
if (!area.has_value() || !iy.has_value() || !iz.has_value() ||
|
|
||||||
!torsion.has_value() || !ox.has_value() || !oy.has_value() ||
|
|
||||||
!oz.has_value()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SectionId section_id{next_section_id_++};
|
|
||||||
if (!section_assignments_
|
|
||||||
.emplace(
|
|
||||||
*element_set,
|
|
||||||
SectionAssignment{material->second, section_id})
|
|
||||||
.second) {
|
|
||||||
add_error(
|
add_error(
|
||||||
"abaqus.semantic.duplicate_section_assignment",
|
"abaqus.semantic.duplicate_section_assignment",
|
||||||
"Element set '" + *element_set +
|
"Element set '" + *element_set +
|
||||||
"' has more than one section assignment.",
|
"' has more than one section assignment.",
|
||||||
record.source);
|
record.source);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto material = materials_.end();
|
||||||
|
if (material_name != nullptr) {
|
||||||
|
material = materials_.find(*material_name);
|
||||||
|
if (material == materials_.end()) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.missing_material",
|
||||||
|
"Beam section references missing Material '" +
|
||||||
|
*material_name + "'.",
|
||||||
|
record.source);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto resolved_set = active_element_sets_.end();
|
||||||
|
if (element_set != nullptr) {
|
||||||
|
resolved_set = active_element_sets_.find(*element_set);
|
||||||
|
if (resolved_set == active_element_sets_.end()) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.missing_element_set",
|
||||||
|
"Beam section references missing element set '" +
|
||||||
|
*element_set + "'.",
|
||||||
|
record.source);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<double, 5> properties{};
|
||||||
|
std::array<double, 3> orientation{};
|
||||||
|
if (record.data.size() != 2U ||
|
||||||
|
record.data[0].size() != properties.size() ||
|
||||||
|
record.data[1].size() != orientation.size()) {
|
||||||
|
SourceLocation section_data_source = record.source;
|
||||||
|
if (!record.data.empty()) {
|
||||||
|
if (record.data[0].size() != properties.size()) {
|
||||||
|
section_data_source = data_source(record, 0U);
|
||||||
|
} else if (record.data.size() > 2U) {
|
||||||
|
section_data_source = data_source(record, 2U);
|
||||||
|
} else if (record.data.size() == 2U) {
|
||||||
|
section_data_source = data_source(record, 1U);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.invalid_section_data",
|
||||||
|
"*BEAM GENERAL SECTION requires exactly A, Iy, Iyz, Iz, "
|
||||||
|
"J and one three-component orientation row.",
|
||||||
|
section_data_source);
|
||||||
|
valid = false;
|
||||||
|
} else {
|
||||||
|
bool valid_properties = true;
|
||||||
|
for (std::size_t field = 0; field < properties.size(); ++field) {
|
||||||
|
valid_properties = parse_number(
|
||||||
|
record.data[0][field],
|
||||||
|
properties[field]) &&
|
||||||
|
valid_properties;
|
||||||
|
}
|
||||||
|
valid_properties =
|
||||||
|
valid_properties && properties[0] > 0.0 &&
|
||||||
|
properties[1] > 0.0 && properties[2] == 0.0 &&
|
||||||
|
properties[3] > 0.0 && properties[4] > 0.0;
|
||||||
|
if (!valid_properties) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.invalid_section_data",
|
||||||
|
"General Beam properties require finite positive A, "
|
||||||
|
"Iy, Iz, J and Iyz=0.",
|
||||||
|
data_source(record, 0U));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool valid_orientation = true;
|
||||||
|
for (std::size_t field = 0; field < orientation.size(); ++field) {
|
||||||
|
valid_orientation = parse_number(
|
||||||
|
record.data[1][field],
|
||||||
|
orientation[field]) &&
|
||||||
|
valid_orientation;
|
||||||
|
}
|
||||||
|
if (!valid_orientation) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.invalid_section_data",
|
||||||
|
"Beam section orientation components must be finite.",
|
||||||
|
data_source(record, 1U));
|
||||||
|
}
|
||||||
|
valid = valid && valid_properties && valid_orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
double shear_area_y = 0.0;
|
||||||
|
double shear_area_z = 0.0;
|
||||||
|
ShearPropertySource shear_source =
|
||||||
|
ShearPropertySource::phase1_default;
|
||||||
|
if (shear == nullptr) {
|
||||||
|
shear_area_y = 5.0 * properties[0] / 6.0;
|
||||||
|
shear_area_z = shear_area_y;
|
||||||
|
} else {
|
||||||
|
const SourceLocation shear_source_location =
|
||||||
|
shear->data.empty() ? shear->source
|
||||||
|
: data_source(*shear, 0U);
|
||||||
|
std::array<double, 3> values{};
|
||||||
|
bool valid_shear =
|
||||||
|
shear->data.size() == 1U &&
|
||||||
|
shear->data[0].size() == values.size();
|
||||||
|
if (valid_shear) {
|
||||||
|
for (std::size_t field = 0; field < values.size(); ++field) {
|
||||||
|
valid_shear = parse_number(
|
||||||
|
shear->data[0][field], values[field]) &&
|
||||||
|
valid_shear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!valid_shear || values[0] <= 0.0 || values[1] <= 0.0) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.invalid_transverse_shear_data",
|
||||||
|
"*TRANSVERSE SHEAR STIFFNESS requires finite positive "
|
||||||
|
"K23, K13 and numeric SCF=0.",
|
||||||
|
shear_source_location);
|
||||||
|
valid = false;
|
||||||
|
} else if (values[2] != 0.0) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.nonzero_scf",
|
||||||
|
"Phase 1 supports only SCF=0.",
|
||||||
|
shear_source_location);
|
||||||
|
valid = false;
|
||||||
|
} else if (material != materials_.end()) {
|
||||||
|
const double shear_modulus =
|
||||||
|
material->second.young /
|
||||||
|
(2.0 * (1.0 + material->second.poisson));
|
||||||
|
shear_area_y = values[0] / shear_modulus;
|
||||||
|
shear_area_z = values[1] / shear_modulus;
|
||||||
|
if (!std::isfinite(shear_area_y) ||
|
||||||
|
!std::isfinite(shear_area_z) ||
|
||||||
|
shear_area_y <= 0.0 || shear_area_z <= 0.0) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.invalid_transverse_shear_data",
|
||||||
|
"Transverse stiffness produces an invalid effective "
|
||||||
|
"shear area.",
|
||||||
|
shear_source_location);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
shear_source = ShearPropertySource::input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid || element_set == nullptr ||
|
||||||
|
material == materials_.end() ||
|
||||||
|
resolved_set == active_element_sets_.end()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SectionId section_id{next_section_id_++};
|
||||||
|
const SectionAssignment assignment{
|
||||||
|
material->second.id,
|
||||||
|
section_id,
|
||||||
|
};
|
||||||
|
bool duplicate_member = false;
|
||||||
|
for (const std::int64_t label : resolved_set->second) {
|
||||||
|
if (element_assignments_.contains(label)) {
|
||||||
|
duplicate_member = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (duplicate_member) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.duplicate_section_assignment",
|
||||||
|
"An element has more than one section assignment.",
|
||||||
|
record.source);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const std::int64_t label : resolved_set->second) {
|
||||||
|
element_assignments_.emplace(label, assignment);
|
||||||
|
}
|
||||||
|
|
||||||
builder_.add_section({
|
builder_.add_section({
|
||||||
section_id,
|
section_id,
|
||||||
*element_set,
|
*element_set,
|
||||||
*area,
|
properties[0],
|
||||||
*iy,
|
properties[1],
|
||||||
*iz,
|
properties[3],
|
||||||
*torsion,
|
properties[4],
|
||||||
5.0 * *area / 6.0,
|
shear_area_y,
|
||||||
5.0 * *area / 6.0,
|
shear_area_z,
|
||||||
ShearPropertySource::phase1_default,
|
shear_source,
|
||||||
Vec3{*ox, *oy, *oz},
|
Vec3{orientation[0], orientation[1], orientation[2]},
|
||||||
{},
|
{},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -367,9 +608,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::string* type = require_parameter(record, "TYPE");
|
const std::string* type = require_parameter(record, "TYPE");
|
||||||
const std::string* element_set =
|
if (type == nullptr) {
|
||||||
require_parameter(record, "ELSET");
|
|
||||||
if (type == nullptr || element_set == nullptr) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (*type != "B31") {
|
if (*type != "B31") {
|
||||||
@@ -379,15 +618,6 @@ private:
|
|||||||
record.source);
|
record.source);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto assignment = section_assignments_.find(*element_set);
|
|
||||||
if (assignment == section_assignments_.end()) {
|
|
||||||
add_error(
|
|
||||||
"abaqus.semantic.missing_section",
|
|
||||||
"Element set '" + *element_set +
|
|
||||||
"' has no Beam section assignment.",
|
|
||||||
record.source);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& row : record.data) {
|
for (const auto& row : record.data) {
|
||||||
if (row.size() < 3U) {
|
if (row.size() < 3U) {
|
||||||
@@ -407,6 +637,16 @@ private:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto assignment = element_assignments_.find(*label);
|
||||||
|
if (assignment == element_assignments_.end()) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.missing_section",
|
||||||
|
"B31 element " + std::to_string(*label) +
|
||||||
|
" has no Beam section assignment.",
|
||||||
|
record.source);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const auto first = node_ids_.find(*first_label);
|
const auto first = node_ids_.find(*first_label);
|
||||||
const auto second = node_ids_.find(*second_label);
|
const auto second = node_ids_.find(*second_label);
|
||||||
if (first == node_ids_.end() || second == node_ids_.end()) {
|
if (first == node_ids_.end() || second == node_ids_.end()) {
|
||||||
@@ -601,14 +841,16 @@ private:
|
|||||||
DomainBuilder builder_;
|
DomainBuilder builder_;
|
||||||
std::vector<Diagnostic> diagnostics_;
|
std::vector<Diagnostic> diagnostics_;
|
||||||
std::span<const DeckRecord> active_records_;
|
std::span<const DeckRecord> active_records_;
|
||||||
std::span<const DeckRecord> assembly_records_;
|
bool flat_ = true;
|
||||||
std::string part_name_;
|
std::string part_name_;
|
||||||
std::string instance_name_;
|
std::string instance_name_;
|
||||||
LabelMap node_ids_;
|
LabelMap node_ids_;
|
||||||
ElementLabelMap element_ids_;
|
ElementLabelMap element_ids_;
|
||||||
std::map<std::string, MaterialId, std::less<>> material_ids_;
|
std::map<std::string, MaterialProperties, std::less<>> materials_;
|
||||||
std::map<std::string, SectionAssignment, std::less<>>
|
std::map<std::string, std::vector<std::int64_t>, std::less<>>
|
||||||
section_assignments_;
|
active_element_sets_;
|
||||||
|
std::map<std::int64_t, SectionAssignment> element_assignments_;
|
||||||
|
std::set<std::string, std::less<>> assigned_element_set_names_;
|
||||||
RawSetMap raw_node_sets_;
|
RawSetMap raw_node_sets_;
|
||||||
RawSetMap raw_element_sets_;
|
RawSetMap raw_element_sets_;
|
||||||
std::map<std::string, std::vector<NodeId>, std::less<>> node_sets_;
|
std::map<std::string, std::vector<NodeId>, std::less<>> node_sets_;
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ add_test(
|
|||||||
add_executable(fesa_abaqus_parser_tests
|
add_executable(fesa_abaqus_parser_tests
|
||||||
unit/io/abaqus/active_input_test.cpp
|
unit/io/abaqus/active_input_test.cpp
|
||||||
unit/io/abaqus/input_contract_test.cpp
|
unit/io/abaqus/input_contract_test.cpp
|
||||||
|
unit/io/abaqus/material_section_mapping_test.cpp
|
||||||
unit/io/abaqus/parser_test.cpp
|
unit/io/abaqus/parser_test.cpp
|
||||||
unit/io/abaqus/set_resolution_test.cpp
|
unit/io/abaqus/set_resolution_test.cpp
|
||||||
)
|
)
|
||||||
@@ -188,6 +189,24 @@ add_test(
|
|||||||
--gtest_filter=SemanticScope.*
|
--gtest_filter=SemanticScope.*
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_test(
|
||||||
|
NAME MaterialMapping
|
||||||
|
COMMAND "$<TARGET_FILE:fesa_abaqus_parser_tests>"
|
||||||
|
--gtest_filter=MaterialMapping.*
|
||||||
|
)
|
||||||
|
|
||||||
|
add_test(
|
||||||
|
NAME BeamSection
|
||||||
|
COMMAND "$<TARGET_FILE:fesa_abaqus_parser_tests>"
|
||||||
|
--gtest_filter=BeamSection.*
|
||||||
|
)
|
||||||
|
|
||||||
|
add_test(
|
||||||
|
NAME ShearDefault
|
||||||
|
COMMAND "$<TARGET_FILE:fesa_abaqus_parser_tests>"
|
||||||
|
--gtest_filter=ShearDefault.*
|
||||||
|
)
|
||||||
|
|
||||||
add_executable(fesa_deck_to_domain_tests
|
add_executable(fesa_deck_to_domain_tests
|
||||||
integration/io/minimal_deck_to_domain_test.cpp
|
integration/io/minimal_deck_to_domain_test.cpp
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
*ELASTIC
|
*ELASTIC
|
||||||
210000.0, 0.3
|
210000.0, 0.3
|
||||||
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
|
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
|
||||||
1.0, 1.0, 1.0, 1.0, 1.0
|
1.0, 1.0, 0.0, 1.0, 1.0
|
||||||
0.0, 1.0, 0.0
|
0.0, 1.0, 0.0
|
||||||
*BOUNDARY
|
*BOUNDARY
|
||||||
Fixed, 1, 6
|
Fixed, 1, 6
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
*ELSET, ELSET=Beam
|
*ELSET, ELSET=Beam
|
||||||
1
|
1
|
||||||
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
|
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
|
||||||
1.0, 1.0, 1.0, 1.0, 1.0
|
1.0, 1.0, 0.0, 1.0, 1.0
|
||||||
0.0, 1.0, 0.0
|
0.0, 1.0, 0.0
|
||||||
*END PART
|
*END PART
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,338 @@
|
|||||||
|
#include <fesa/io/abaqus/parser.hpp>
|
||||||
|
#include <fesa/io/abaqus/semantic_mapper.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <system_error>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class TemporaryDeck final {
|
||||||
|
public:
|
||||||
|
TemporaryDeck(std::string_view name, std::string_view contents)
|
||||||
|
: path_{std::filesystem::path{testing::TempDir()} / name} {
|
||||||
|
std::ofstream output{path_, std::ios::binary};
|
||||||
|
output.write(
|
||||||
|
contents.data(), static_cast<std::streamsize>(contents.size()));
|
||||||
|
if (!output) {
|
||||||
|
throw std::runtime_error{"Failed to write temporary Abaqus deck."};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~TemporaryDeck() {
|
||||||
|
std::error_code error;
|
||||||
|
std::filesystem::remove(path_, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TemporaryDeck(const TemporaryDeck&) = delete;
|
||||||
|
TemporaryDeck& operator=(const TemporaryDeck&) = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] const std::filesystem::path& path() const noexcept {
|
||||||
|
return path_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::filesystem::path path_;
|
||||||
|
};
|
||||||
|
|
||||||
|
fesa::DomainBuildResult parse_and_map(const TemporaryDeck& input) {
|
||||||
|
auto parsed = fesa::parse_deck(input.path());
|
||||||
|
if (!parsed.deck.has_value()) {
|
||||||
|
return {std::nullopt, std::move(parsed.diagnostics)};
|
||||||
|
}
|
||||||
|
return fesa::map_deck_to_domain(*parsed.deck);
|
||||||
|
}
|
||||||
|
|
||||||
|
void expect_diagnostic(
|
||||||
|
const fesa::DomainBuildResult& result,
|
||||||
|
const std::string_view code,
|
||||||
|
const std::size_t line) {
|
||||||
|
EXPECT_FALSE(result.domain.has_value());
|
||||||
|
const auto diagnostic = std::ranges::find_if(
|
||||||
|
result.diagnostics,
|
||||||
|
[code, line](const fesa::Diagnostic& candidate) {
|
||||||
|
return candidate.stage == fesa::DiagnosticStage::semantic &&
|
||||||
|
candidate.code == code && candidate.source.has_value() &&
|
||||||
|
candidate.source->line == line;
|
||||||
|
});
|
||||||
|
EXPECT_NE(diagnostic, result.diagnostics.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view valid_flat_prefix() {
|
||||||
|
return
|
||||||
|
"*NODE\n"
|
||||||
|
"1, 0.0, 0.0, 0.0\n"
|
||||||
|
"2, 1.0, 0.0, 0.0\n"
|
||||||
|
"*ELEMENT, TYPE=B31, ELSET=Beam\n"
|
||||||
|
"1, 1, 2\n"
|
||||||
|
"*ELSET, ELSET=Beam\n"
|
||||||
|
"1\n"
|
||||||
|
"*MATERIAL, NAME=Steel\n"
|
||||||
|
"*ELASTIC\n"
|
||||||
|
"200.0, 0.25\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string complete_flat_deck(std::string_view section_and_shear) {
|
||||||
|
std::string contents{valid_flat_prefix()};
|
||||||
|
contents.append(section_and_shear);
|
||||||
|
contents.append(
|
||||||
|
"*STEP\n"
|
||||||
|
"*STATIC\n"
|
||||||
|
"*END STEP\n");
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MaterialMapping, ResolvesForwardMaterialAndExplicitElsetAssignment) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-forward-material.inp",
|
||||||
|
"*PART, NAME=BeamPart\n"
|
||||||
|
"*NODE\n"
|
||||||
|
"1, 0.0, 0.0, 0.0\n"
|
||||||
|
"2, 1.0, 0.0, 0.0\n"
|
||||||
|
"*ELEMENT, TYPE=B31\n"
|
||||||
|
"1, 1, 2\n"
|
||||||
|
"*ELSET, ELSET=Beam\n"
|
||||||
|
"1\n"
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||||
|
"1.2, 2.0, 0.0, 3.0, 4.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n"
|
||||||
|
"*END PART\n"
|
||||||
|
"*ASSEMBLY, NAME=Assembly\n"
|
||||||
|
"*INSTANCE, NAME=Beam-1, PART=BeamPart\n"
|
||||||
|
"*END INSTANCE\n"
|
||||||
|
"*END ASSEMBLY\n"
|
||||||
|
"*MATERIAL, NAME=Steel\n"
|
||||||
|
"*ELASTIC\n"
|
||||||
|
"200.0, 0.25\n"
|
||||||
|
"*STEP\n"
|
||||||
|
"*STATIC\n"
|
||||||
|
"*END STEP\n"};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
ASSERT_TRUE(result.domain.has_value());
|
||||||
|
ASSERT_TRUE(result.diagnostics.empty());
|
||||||
|
ASSERT_EQ(result.domain->materials().size(), 1U);
|
||||||
|
EXPECT_DOUBLE_EQ(result.domain->materials()[0].young, 200.0);
|
||||||
|
EXPECT_DOUBLE_EQ(result.domain->materials()[0].poisson, 0.25);
|
||||||
|
ASSERT_EQ(result.domain->sections().size(), 1U);
|
||||||
|
EXPECT_EQ(
|
||||||
|
result.domain->beam_elements()[0].material,
|
||||||
|
result.domain->materials()[0].id);
|
||||||
|
EXPECT_EQ(
|
||||||
|
result.domain->beam_elements()[0].section,
|
||||||
|
result.domain->sections()[0].id);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ShearDefault, PreservesPhase1DefaultEffectiveAreasAndSource) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-default-shear.inp",
|
||||||
|
complete_flat_deck(
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||||
|
"1.2, 2.0, 0.0, 3.0, 4.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n")};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
ASSERT_TRUE(result.domain.has_value());
|
||||||
|
ASSERT_EQ(result.domain->sections().size(), 1U);
|
||||||
|
const fesa::BeamSection& section = result.domain->sections()[0];
|
||||||
|
EXPECT_DOUBLE_EQ(section.shear_area_y, 1.0);
|
||||||
|
EXPECT_DOUBLE_EQ(section.shear_area_z, 1.0);
|
||||||
|
EXPECT_EQ(
|
||||||
|
section.shear_source,
|
||||||
|
fesa::ShearPropertySource::phase1_default);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ShearDefault, ConvertsExplicitK23AndK13UsingIsotropicShearModulus) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-explicit-shear.inp",
|
||||||
|
complete_flat_deck(
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||||
|
"1.2, 2.0, 0.0, 3.0, 4.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n"
|
||||||
|
"*TRANSVERSE SHEAR STIFFNESS\n"
|
||||||
|
"64.0, 40.0, 0.0\n")};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
ASSERT_TRUE(result.domain.has_value());
|
||||||
|
ASSERT_EQ(result.domain->sections().size(), 1U);
|
||||||
|
const fesa::BeamSection& section = result.domain->sections()[0];
|
||||||
|
EXPECT_DOUBLE_EQ(section.shear_area_y, 0.8);
|
||||||
|
EXPECT_DOUBLE_EQ(section.shear_area_z, 0.5);
|
||||||
|
EXPECT_EQ(section.shear_source, fesa::ShearPropertySource::input);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BeamSection, RejectsInvalidElasticPropertiesAtTheDataRow) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-invalid-elastic.inp",
|
||||||
|
"*MATERIAL, NAME=Steel\n"
|
||||||
|
"*ELASTIC\n"
|
||||||
|
"-1.0, 0.25\n"
|
||||||
|
"*STEP\n"
|
||||||
|
"*STATIC\n"
|
||||||
|
"*END STEP\n"};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
expect_diagnostic(
|
||||||
|
result, "abaqus.semantic.invalid_elastic_data", 3U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BeamSection, RejectsMaterialWithoutElasticData) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-missing-elastic.inp",
|
||||||
|
"*MATERIAL, NAME=Steel\n"
|
||||||
|
"*STEP\n"
|
||||||
|
"*STATIC\n"
|
||||||
|
"*END STEP\n"};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
expect_diagnostic(result, "abaqus.semantic.missing_elastic", 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MaterialMapping, RejectsDuplicateMaterialDefinition) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-duplicate-material.inp",
|
||||||
|
"*MATERIAL, NAME=Steel\n"
|
||||||
|
"*ELASTIC\n"
|
||||||
|
"200.0, 0.25\n"
|
||||||
|
"*MATERIAL, NAME=Steel\n"
|
||||||
|
"*ELASTIC\n"
|
||||||
|
"210.0, 0.3\n"
|
||||||
|
"*STEP\n"
|
||||||
|
"*STATIC\n"
|
||||||
|
"*END STEP\n"};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
expect_diagnostic(
|
||||||
|
result, "abaqus.semantic.duplicate_material", 4U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MaterialMapping, RejectsSectionWithMissingMaterialReference) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-missing-material.inp",
|
||||||
|
complete_flat_deck(
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Missing\n"
|
||||||
|
"1.2, 2.0, 0.0, 3.0, 4.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n")};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
expect_diagnostic(result, "abaqus.semantic.missing_material", 11U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BeamSection, RejectsNonzeroProductMomentAtTheDataRow) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-nonzero-iyz.inp",
|
||||||
|
complete_flat_deck(
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||||
|
"1.2, 2.0, 0.1, 3.0, 4.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n")};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
expect_diagnostic(
|
||||||
|
result, "abaqus.semantic.invalid_section_data", 12U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BeamSection, RejectsNonpositiveGeneralPropertyAtTheDataRow) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-invalid-area.inp",
|
||||||
|
complete_flat_deck(
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||||
|
"0.0, 2.0, 0.0, 3.0, 4.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n")};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
expect_diagnostic(
|
||||||
|
result, "abaqus.semantic.invalid_section_data", 12U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BeamSection, RejectsNonfiniteOrientationAtTheOrientationRow) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-nonfinite-orientation.inp",
|
||||||
|
complete_flat_deck(
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||||
|
"1.2, 2.0, 0.0, 3.0, 4.0\n"
|
||||||
|
"0.0, nan, 0.0\n")};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
expect_diagnostic(
|
||||||
|
result, "abaqus.semantic.invalid_section_data", 13U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BeamSection, RejectsSectionWhoseElementSetDoesNotExist) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-missing-section-elset.inp",
|
||||||
|
complete_flat_deck(
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Missing, MATERIAL=Steel\n"
|
||||||
|
"1.2, 2.0, 0.0, 3.0, 4.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n")};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
expect_diagnostic(
|
||||||
|
result, "abaqus.semantic.missing_element_set", 11U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BeamSection, RejectsNonzeroScfAtTheDataRow) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-nonzero-scf.inp",
|
||||||
|
complete_flat_deck(
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||||
|
"1.2, 2.0, 0.0, 3.0, 4.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n"
|
||||||
|
"*TRANSVERSE SHEAR STIFFNESS\n"
|
||||||
|
"64.0, 40.0, 0.25\n")};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
expect_diagnostic(result, "abaqus.semantic.nonzero_scf", 15U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BeamSection, RejectsElementWithoutSectionAssignment) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-missing-section.inp",
|
||||||
|
complete_flat_deck(
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Other, MATERIAL=Steel\n"
|
||||||
|
"1.2, 2.0, 0.0, 3.0, 4.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n"
|
||||||
|
"*ELSET, ELSET=Other\n")};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
expect_diagnostic(result, "abaqus.semantic.missing_section", 4U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BeamSection, RejectsDuplicateSectionAssignment) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-duplicate-section.inp",
|
||||||
|
complete_flat_deck(
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||||
|
"1.2, 2.0, 0.0, 3.0, 4.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n"
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||||
|
"1.2, 2.0, 0.0, 3.0, 4.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n")};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
|
expect_diagnostic(
|
||||||
|
result, "abaqus.semantic.duplicate_section_assignment", 14U);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
Reference in New Issue
Block a user