fix(input): validate inactive Part records
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
"step": 0,
|
"step": 0,
|
||||||
"name": "abaqus-input-contract",
|
"name": "abaqus-input-contract",
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"summary": "Defined the normative Phase 1 Abaqus subset and expanded the public parser/mapper fixture matrix to 69 cases covering strict scopes, parameter forms, data ownership, exact mesh rows, duplicate Parts, Assembly-only hierarchical targets, and source-accurate diagnostics.",
|
"summary": "Defined the normative Phase 1 Abaqus subset and expanded the public parser/mapper fixture matrix to 74 cases covering strict scopes, parameter forms, data ownership, exact mesh rows and references in active and inactive Parts, duplicate Parts, Assembly-only hierarchical targets, and source-accurate diagnostics.",
|
||||||
"started_at": "2026-08-01T02:16:39+0900",
|
"started_at": "2026-08-01T02:16:39+0900",
|
||||||
"completed_at": "2026-08-01T02:24:23+0900"
|
"completed_at": "2026-08-01T02:24:23+0900"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -77,18 +77,21 @@ public:
|
|||||||
instance_name_ = std::move(selected.input->instance_name);
|
instance_name_ = std::move(selected.input->instance_name);
|
||||||
|
|
||||||
collect_materials();
|
collect_materials();
|
||||||
collect_nodes();
|
collect_nodes(active_records_, node_ids_, true);
|
||||||
SetResolutionResult resolved_sets = resolve_sets(deck_);
|
SetResolutionResult resolved_sets = resolve_sets(deck_);
|
||||||
if (!resolved_sets.diagnostics.empty()) {
|
if (!resolved_sets.diagnostics.empty()) {
|
||||||
diagnostics_.insert(
|
diagnostics_.insert(
|
||||||
diagnostics_.end(),
|
diagnostics_.end(),
|
||||||
std::make_move_iterator(resolved_sets.diagnostics.begin()),
|
std::make_move_iterator(resolved_sets.diagnostics.begin()),
|
||||||
std::make_move_iterator(resolved_sets.diagnostics.end()));
|
std::make_move_iterator(resolved_sets.diagnostics.end()));
|
||||||
} else {
|
}
|
||||||
|
validate_inactive_parts(resolved_sets.sets);
|
||||||
|
if (resolved_sets.diagnostics.empty()) {
|
||||||
collect_resolved_sets(resolved_sets.sets);
|
collect_resolved_sets(resolved_sets.sets);
|
||||||
}
|
}
|
||||||
collect_sections();
|
collect_sections(active_records_, active_element_sets_, true);
|
||||||
collect_elements();
|
collect_elements(
|
||||||
|
active_records_, node_ids_, element_ids_, true);
|
||||||
emit_sets();
|
emit_sets();
|
||||||
collect_step();
|
collect_step();
|
||||||
|
|
||||||
@@ -318,8 +321,11 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void collect_nodes() {
|
void collect_nodes(
|
||||||
for (const DeckRecord& record : active_records_) {
|
const std::span<const DeckRecord> records,
|
||||||
|
LabelMap& node_ids,
|
||||||
|
const bool emit) {
|
||||||
|
for (const DeckRecord& record : records) {
|
||||||
if (record.keyword != "NODE") {
|
if (record.keyword != "NODE") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -356,7 +362,7 @@ private:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node_ids_.contains(*label)) {
|
if (node_ids.contains(*label)) {
|
||||||
add_error(
|
add_error(
|
||||||
"abaqus.semantic.duplicate_node_label",
|
"abaqus.semantic.duplicate_node_label",
|
||||||
"Node label " + std::to_string(*label) +
|
"Node label " + std::to_string(*label) +
|
||||||
@@ -365,17 +371,151 @@ private:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeId id{next_node_id_++};
|
const NodeId id = emit
|
||||||
node_ids_.emplace(*label, id);
|
? NodeId{next_node_id_++}
|
||||||
builder_.add_node({
|
: NodeId{static_cast<std::int64_t>(
|
||||||
id,
|
node_ids.size())};
|
||||||
EntityOrigin{part_name_, instance_name_, *label},
|
node_ids.emplace(*label, id);
|
||||||
Vec3{*x, *y, *z},
|
if (emit) {
|
||||||
});
|
builder_.add_node({
|
||||||
|
id,
|
||||||
|
EntityOrigin{part_name_, instance_name_, *label},
|
||||||
|
Vec3{*x, *y, *z},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void validate_inactive_mesh(const ParsedPart& part) {
|
||||||
|
LabelMap node_ids;
|
||||||
|
ElementLabelMap element_ids;
|
||||||
|
collect_nodes(part.records, node_ids, false);
|
||||||
|
collect_elements(part.records, node_ids, element_ids, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SectionData final {
|
||||||
|
std::array<double, 5> properties{};
|
||||||
|
std::array<double, 3> orientation{};
|
||||||
|
std::optional<std::array<double, 3>> shear_values;
|
||||||
|
bool valid = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
SectionData validate_section_data(
|
||||||
|
const DeckRecord& record,
|
||||||
|
const DeckRecord* shear) {
|
||||||
|
SectionData result;
|
||||||
|
if (record.data.size() != 2U ||
|
||||||
|
record.data[0].size() != result.properties.size() ||
|
||||||
|
record.data[1].size() != result.orientation.size()) {
|
||||||
|
SourceLocation source = record.source;
|
||||||
|
if (!record.data.empty()) {
|
||||||
|
source = record.data[0].size() != result.properties.size()
|
||||||
|
? data_source(record, 0U)
|
||||||
|
: data_source(
|
||||||
|
record,
|
||||||
|
record.data.size() > 2U ? 2U : 1U);
|
||||||
|
}
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.invalid_section_data",
|
||||||
|
"*BEAM GENERAL SECTION requires exactly A, Iy, Iyz, Iz, J "
|
||||||
|
"and one three-component orientation row.",
|
||||||
|
source);
|
||||||
|
result.valid = false;
|
||||||
|
} else {
|
||||||
|
bool valid_properties = true;
|
||||||
|
for (std::size_t field = 0; field < result.properties.size();
|
||||||
|
++field) {
|
||||||
|
valid_properties =
|
||||||
|
parse_number(
|
||||||
|
record.data[0][field], result.properties[field]) &&
|
||||||
|
valid_properties;
|
||||||
|
}
|
||||||
|
valid_properties =
|
||||||
|
valid_properties && result.properties[0] > 0.0 &&
|
||||||
|
result.properties[1] > 0.0 && result.properties[2] == 0.0 &&
|
||||||
|
result.properties[3] > 0.0 && result.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 < result.orientation.size();
|
||||||
|
++field) {
|
||||||
|
valid_orientation =
|
||||||
|
parse_number(
|
||||||
|
record.data[1][field], result.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));
|
||||||
|
}
|
||||||
|
result.valid = valid_properties && valid_orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shear == nullptr) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const SourceLocation source =
|
||||||
|
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.",
|
||||||
|
source);
|
||||||
|
result.valid = false;
|
||||||
|
} else if (values[2] != 0.0) {
|
||||||
|
add_error(
|
||||||
|
"abaqus.semantic.nonzero_scf",
|
||||||
|
"Phase 1 supports only SCF=0.",
|
||||||
|
source);
|
||||||
|
result.valid = false;
|
||||||
|
} else {
|
||||||
|
result.shear_values = values;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void validate_inactive_parts(
|
||||||
|
const std::vector<ResolvedSet>& resolved_sets) {
|
||||||
|
if (flat_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const ParsedPart& part : deck_.parts) {
|
||||||
|
if (part.name == part_name_) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
validate_inactive_mesh(part);
|
||||||
|
RawSetMap element_sets;
|
||||||
|
for (const ResolvedSet& set : resolved_sets) {
|
||||||
|
if (set.scope == ResolvedSetScope::part &&
|
||||||
|
set.scope_name == part.name &&
|
||||||
|
set.kind == ResolvedSetKind::element) {
|
||||||
|
element_sets[set.set_name] = set.sorted_unique_labels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collect_sections(part.records, element_sets, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void collect_resolved_sets(const std::vector<ResolvedSet>& sets) {
|
void collect_resolved_sets(const std::vector<ResolvedSet>& sets) {
|
||||||
for (const ResolvedSet& set : sets) {
|
for (const ResolvedSet& set : sets) {
|
||||||
const bool mesh_scope =
|
const bool mesh_scope =
|
||||||
@@ -417,9 +557,14 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void collect_sections() {
|
void collect_sections(
|
||||||
for (std::size_t index = 0; index < active_records_.size(); ++index) {
|
const std::span<const DeckRecord> records,
|
||||||
const DeckRecord& record = active_records_[index];
|
const RawSetMap& element_sets,
|
||||||
|
const bool emit) {
|
||||||
|
std::set<std::string, std::less<>> assigned_set_names;
|
||||||
|
std::set<std::int64_t> assigned_elements;
|
||||||
|
for (std::size_t index = 0; index < records.size(); ++index) {
|
||||||
|
const DeckRecord& record = records[index];
|
||||||
if (record.keyword == "TRANSVERSE SHEAR STIFFNESS") {
|
if (record.keyword == "TRANSVERSE SHEAR STIFFNESS") {
|
||||||
add_error(
|
add_error(
|
||||||
"abaqus.semantic.orphan_transverse_shear",
|
"abaqus.semantic.orphan_transverse_shear",
|
||||||
@@ -433,10 +578,10 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DeckRecord* shear = nullptr;
|
const DeckRecord* shear = nullptr;
|
||||||
if (index + 1U < active_records_.size() &&
|
if (index + 1U < records.size() &&
|
||||||
active_records_[index + 1U].keyword ==
|
records[index + 1U].keyword ==
|
||||||
"TRANSVERSE SHEAR STIFFNESS") {
|
"TRANSVERSE SHEAR STIFFNESS") {
|
||||||
shear = &active_records_[index + 1U];
|
shear = &records[index + 1U];
|
||||||
++index;
|
++index;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,7 +604,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (element_set != nullptr &&
|
if (element_set != nullptr &&
|
||||||
!assigned_element_set_names_.insert(*element_set).second) {
|
!assigned_set_names.insert(*element_set).second) {
|
||||||
add_error(
|
add_error(
|
||||||
"abaqus.semantic.duplicate_section_assignment",
|
"abaqus.semantic.duplicate_section_assignment",
|
||||||
"Element set '" + *element_set +
|
"Element set '" + *element_set +
|
||||||
@@ -481,10 +626,10 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto resolved_set = active_element_sets_.end();
|
auto resolved_set = element_sets.end();
|
||||||
if (element_set != nullptr) {
|
if (element_set != nullptr) {
|
||||||
resolved_set = active_element_sets_.find(*element_set);
|
resolved_set = element_sets.find(*element_set);
|
||||||
if (resolved_set == active_element_sets_.end()) {
|
if (resolved_set == element_sets.end()) {
|
||||||
add_error(
|
add_error(
|
||||||
"abaqus.semantic.missing_element_set",
|
"abaqus.semantic.missing_element_set",
|
||||||
"Beam section references missing element set '" +
|
"Beam section references missing element set '" +
|
||||||
@@ -494,132 +639,48 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<double, 5> properties{};
|
SectionData section_data = validate_section_data(record, shear);
|
||||||
std::array<double, 3> orientation{};
|
valid = valid && section_data.valid;
|
||||||
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_y = 0.0;
|
||||||
double shear_area_z = 0.0;
|
double shear_area_z = 0.0;
|
||||||
ShearPropertySource shear_source =
|
ShearPropertySource shear_source =
|
||||||
ShearPropertySource::phase1_default;
|
ShearPropertySource::phase1_default;
|
||||||
if (shear == nullptr) {
|
if (shear == nullptr) {
|
||||||
shear_area_y = 5.0 * properties[0] / 6.0;
|
shear_area_y = 5.0 * section_data.properties[0] / 6.0;
|
||||||
shear_area_z = shear_area_y;
|
shear_area_z = shear_area_y;
|
||||||
} else {
|
} else if (section_data.shear_values.has_value() &&
|
||||||
|
material != materials_.end()) {
|
||||||
const SourceLocation shear_source_location =
|
const SourceLocation shear_source_location =
|
||||||
shear->data.empty() ? shear->source
|
shear->data.empty() ? shear->source
|
||||||
: data_source(*shear, 0U);
|
: data_source(*shear, 0U);
|
||||||
std::array<double, 3> values{};
|
const double shear_modulus =
|
||||||
bool valid_shear =
|
material->second.young /
|
||||||
shear->data.size() == 1U &&
|
(2.0 * (1.0 + material->second.poisson));
|
||||||
shear->data[0].size() == values.size();
|
shear_area_y = (*section_data.shear_values)[0] / shear_modulus;
|
||||||
if (valid_shear) {
|
shear_area_z = (*section_data.shear_values)[1] / shear_modulus;
|
||||||
for (std::size_t field = 0; field < values.size(); ++field) {
|
if (!std::isfinite(shear_area_y) ||
|
||||||
valid_shear = parse_number(
|
!std::isfinite(shear_area_z) || shear_area_y <= 0.0 ||
|
||||||
shear->data[0][field], values[field]) &&
|
shear_area_z <= 0.0) {
|
||||||
valid_shear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!valid_shear || values[0] <= 0.0 || values[1] <= 0.0) {
|
|
||||||
add_error(
|
add_error(
|
||||||
"abaqus.semantic.invalid_transverse_shear_data",
|
"abaqus.semantic.invalid_transverse_shear_data",
|
||||||
"*TRANSVERSE SHEAR STIFFNESS requires finite positive "
|
"Transverse stiffness produces an invalid effective "
|
||||||
"K23, K13 and numeric SCF=0.",
|
"shear area.",
|
||||||
shear_source_location);
|
shear_source_location);
|
||||||
valid = false;
|
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;
|
|
||||||
}
|
}
|
||||||
|
shear_source = ShearPropertySource::input;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!valid || element_set == nullptr ||
|
if (!valid || element_set == nullptr ||
|
||||||
material == materials_.end() ||
|
material == materials_.end() ||
|
||||||
resolved_set == active_element_sets_.end()) {
|
resolved_set == element_sets.end()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SectionId section_id{next_section_id_++};
|
|
||||||
const SectionAssignment assignment{
|
|
||||||
material->second.id,
|
|
||||||
section_id,
|
|
||||||
};
|
|
||||||
bool duplicate_member = false;
|
bool duplicate_member = false;
|
||||||
for (const std::int64_t label : resolved_set->second) {
|
for (const std::int64_t label : resolved_set->second) {
|
||||||
if (element_assignments_.contains(label)) {
|
if (assigned_elements.contains(label)) {
|
||||||
duplicate_member = true;
|
duplicate_member = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -631,6 +692,18 @@ private:
|
|||||||
record.source);
|
record.source);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
for (const std::int64_t label : resolved_set->second) {
|
||||||
|
assigned_elements.insert(label);
|
||||||
|
}
|
||||||
|
if (!emit) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SectionId section_id{next_section_id_++};
|
||||||
|
const SectionAssignment assignment{
|
||||||
|
material->second.id,
|
||||||
|
section_id,
|
||||||
|
};
|
||||||
for (const std::int64_t label : resolved_set->second) {
|
for (const std::int64_t label : resolved_set->second) {
|
||||||
element_assignments_.emplace(label, assignment);
|
element_assignments_.emplace(label, assignment);
|
||||||
}
|
}
|
||||||
@@ -638,21 +711,28 @@ private:
|
|||||||
builder_.add_section({
|
builder_.add_section({
|
||||||
section_id,
|
section_id,
|
||||||
*element_set,
|
*element_set,
|
||||||
properties[0],
|
section_data.properties[0],
|
||||||
properties[1],
|
section_data.properties[1],
|
||||||
properties[3],
|
section_data.properties[3],
|
||||||
properties[4],
|
section_data.properties[4],
|
||||||
shear_area_y,
|
shear_area_y,
|
||||||
shear_area_z,
|
shear_area_z,
|
||||||
shear_source,
|
shear_source,
|
||||||
Vec3{orientation[0], orientation[1], orientation[2]},
|
Vec3{
|
||||||
|
section_data.orientation[0],
|
||||||
|
section_data.orientation[1],
|
||||||
|
section_data.orientation[2]},
|
||||||
{},
|
{},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void collect_elements() {
|
void collect_elements(
|
||||||
for (const DeckRecord& record : active_records_) {
|
const std::span<const DeckRecord> records,
|
||||||
|
const LabelMap& node_ids,
|
||||||
|
ElementLabelMap& element_ids,
|
||||||
|
const bool emit) {
|
||||||
|
for (const DeckRecord& record : records) {
|
||||||
if (record.keyword != "ELEMENT") {
|
if (record.keyword != "ELEMENT") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -702,7 +782,7 @@ private:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element_ids_.contains(*label)) {
|
if (element_ids.contains(*label)) {
|
||||||
add_error(
|
add_error(
|
||||||
"abaqus.semantic.duplicate_element_label",
|
"abaqus.semantic.duplicate_element_label",
|
||||||
"Element label " + std::to_string(*label) +
|
"Element label " + std::to_string(*label) +
|
||||||
@@ -711,19 +791,21 @@ private:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto assignment = element_assignments_.find(*label);
|
auto assignment = element_assignments_.end();
|
||||||
if (assignment == element_assignments_.end()) {
|
if (emit) {
|
||||||
add_error(
|
assignment = element_assignments_.find(*label);
|
||||||
"abaqus.semantic.missing_section",
|
if (assignment == element_assignments_.end()) {
|
||||||
"B31 element " + std::to_string(*label) +
|
add_error(
|
||||||
" has no Beam section assignment.",
|
"abaqus.semantic.missing_section",
|
||||||
record.source);
|
"B31 element " + std::to_string(*label) +
|
||||||
continue;
|
" has no Beam section assignment.",
|
||||||
|
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()) {
|
|
||||||
add_error(
|
add_error(
|
||||||
"abaqus.semantic.missing_node",
|
"abaqus.semantic.missing_node",
|
||||||
"B31 element references a missing node label.",
|
"B31 element references a missing node label.",
|
||||||
@@ -731,8 +813,14 @@ private:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ElementId id{next_element_id_++};
|
const ElementId id = emit
|
||||||
element_ids_.emplace(*label, id);
|
? ElementId{next_element_id_++}
|
||||||
|
: ElementId{static_cast<std::int64_t>(
|
||||||
|
element_ids.size())};
|
||||||
|
element_ids.emplace(*label, id);
|
||||||
|
if (!emit) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
builder_.add_beam_element({
|
builder_.add_beam_element({
|
||||||
id,
|
id,
|
||||||
EntityOrigin{part_name_, instance_name_, *label},
|
EntityOrigin{part_name_, instance_name_, *label},
|
||||||
@@ -1041,7 +1129,6 @@ private:
|
|||||||
std::map<std::string, std::vector<std::int64_t>, std::less<>>
|
std::map<std::string, std::vector<std::int64_t>, std::less<>>
|
||||||
active_element_sets_;
|
active_element_sets_;
|
||||||
std::map<std::int64_t, SectionAssignment> element_assignments_;
|
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_assembly_node_sets_;
|
RawSetMap raw_assembly_node_sets_;
|
||||||
RawSetMap raw_element_sets_;
|
RawSetMap raw_element_sets_;
|
||||||
|
|||||||
Vendored
+5
@@ -68,3 +68,8 @@ hierarchical_part_target_invalid invalid invalid/hierarchical_part_set_target.in
|
|||||||
material_data_invalid invalid invalid/material_data.inp syntax abaqus.syntax.data_without_keyword 2 - - - - - - - - - - -
|
material_data_invalid invalid invalid/material_data.inp syntax abaqus.syntax.data_without_keyword 2 - - - - - - - - - - -
|
||||||
node_coordinate_invalid invalid invalid/node_invalid_coordinate.inp semantic abaqus.semantic.invalid_number 2 - - - - - - - - - - -
|
node_coordinate_invalid invalid invalid/node_invalid_coordinate.inp semantic abaqus.semantic.invalid_number 2 - - - - - - - - - - -
|
||||||
element_missing_node_invalid invalid invalid/element_missing_node.inp semantic abaqus.semantic.missing_node 4 - - - - - - - - - - -
|
element_missing_node_invalid invalid invalid/element_missing_node.inp semantic abaqus.semantic.missing_node 4 - - - - - - - - - - -
|
||||||
|
inactive_part_node_invalid invalid invalid/inactive_part_node_surplus.inp semantic abaqus.semantic.invalid_node_data 3 - - - - - - - - - - -
|
||||||
|
inactive_part_element_invalid invalid invalid/inactive_part_element_type.inp semantic abaqus.semantic.unsupported_element 2 - - - - - - - - - - -
|
||||||
|
inactive_part_section_invalid invalid invalid/inactive_part_section_data.inp semantic abaqus.semantic.invalid_section_data 3 - - - - - - - - - - -
|
||||||
|
inactive_part_duplicate_node_invalid invalid invalid/inactive_part_duplicate_node.inp semantic abaqus.semantic.duplicate_node_label 4 - - - - - - - - - - -
|
||||||
|
inactive_part_missing_node_invalid invalid invalid/inactive_part_missing_node.inp semantic abaqus.semantic.missing_node 5 - - - - - - - - - - -
|
||||||
|
|||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
*PART, NAME=Unused
|
||||||
|
*NODE
|
||||||
|
1, 0.0, 0.0, 0.0
|
||||||
|
1, 1.0, 0.0, 0.0
|
||||||
|
*END PART
|
||||||
|
*PART, NAME=Active
|
||||||
|
*END PART
|
||||||
|
*ASSEMBLY, NAME=RootAssembly
|
||||||
|
*INSTANCE, NAME=Active-1, PART=Active
|
||||||
|
*END INSTANCE
|
||||||
|
*END ASSEMBLY
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
*PART, NAME=Unused
|
||||||
|
*ELEMENT, TYPE=B32
|
||||||
|
1, 1, 2
|
||||||
|
*END PART
|
||||||
|
*PART, NAME=Active
|
||||||
|
*END PART
|
||||||
|
*ASSEMBLY, NAME=RootAssembly
|
||||||
|
*INSTANCE, NAME=Active-1, PART=Active
|
||||||
|
*END INSTANCE
|
||||||
|
*END ASSEMBLY
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
*PART, NAME=Unused
|
||||||
|
*NODE
|
||||||
|
1, 0.0, 0.0, 0.0
|
||||||
|
*ELEMENT, TYPE=B31
|
||||||
|
1, 1, 2
|
||||||
|
*END PART
|
||||||
|
*PART, NAME=Active
|
||||||
|
*END PART
|
||||||
|
*ASSEMBLY, NAME=RootAssembly
|
||||||
|
*INSTANCE, NAME=Active-1, PART=Active
|
||||||
|
*END INSTANCE
|
||||||
|
*END ASSEMBLY
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
*PART, NAME=Unused
|
||||||
|
*NODE
|
||||||
|
1, 0.0, 0.0, 0.0, 9.0
|
||||||
|
*END PART
|
||||||
|
*PART, NAME=Active
|
||||||
|
*END PART
|
||||||
|
*ASSEMBLY, NAME=RootAssembly
|
||||||
|
*INSTANCE, NAME=Active-1, PART=Active
|
||||||
|
*END INSTANCE
|
||||||
|
*END ASSEMBLY
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
*PART, NAME=Unused
|
||||||
|
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
|
||||||
|
0.0, 1.0, 0.0, 1.0, 1.0
|
||||||
|
0.0, 1.0, 0.0
|
||||||
|
*END PART
|
||||||
|
*PART, NAME=Active
|
||||||
|
*END PART
|
||||||
|
*ASSEMBLY, NAME=RootAssembly
|
||||||
|
*INSTANCE, NAME=Active-1, PART=Active
|
||||||
|
*END INSTANCE
|
||||||
|
*END ASSEMBLY
|
||||||
@@ -333,6 +333,92 @@ TEST(ActiveInstance, ExcludesPartsNotReferencedByTheInstance) {
|
|||||||
EXPECT_EQ(result.domain->nodes()[1].origin.part_name, "BeamPart");
|
EXPECT_EQ(result.domain->nodes()[1].origin.part_name, "BeamPart");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ActiveInstance, RejectsInvalidRecordsInUnreferencedParts) {
|
||||||
|
struct Case final {
|
||||||
|
std::string_view name;
|
||||||
|
std::string_view unused_records;
|
||||||
|
std::string_view code;
|
||||||
|
std::size_t line;
|
||||||
|
};
|
||||||
|
const Case cases[]{
|
||||||
|
{
|
||||||
|
"node-surplus",
|
||||||
|
"*NODE\n1, 0.0, 0.0, 0.0, 9.0\n",
|
||||||
|
"abaqus.semantic.invalid_node_data",
|
||||||
|
3U,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"element-type",
|
||||||
|
"*ELEMENT, TYPE=B32\n1, 1, 2\n",
|
||||||
|
"abaqus.semantic.unsupported_element",
|
||||||
|
2U,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"duplicate-node",
|
||||||
|
"*NODE\n1, 0.0, 0.0, 0.0\n1, 1.0, 0.0, 0.0\n",
|
||||||
|
"abaqus.semantic.duplicate_node_label",
|
||||||
|
4U,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"missing-node",
|
||||||
|
"*NODE\n1, 0.0, 0.0, 0.0\n"
|
||||||
|
"*ELEMENT, TYPE=B31\n1, 1, 2\n",
|
||||||
|
"abaqus.semantic.missing_node",
|
||||||
|
5U,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section-data",
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, "
|
||||||
|
"MATERIAL=Steel\n"
|
||||||
|
"0.0, 1.0, 0.0, 1.0, 1.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n",
|
||||||
|
"abaqus.semantic.invalid_section_data",
|
||||||
|
3U,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const std::string active_model{
|
||||||
|
"*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, ELSET=Beam\n"
|
||||||
|
"1, 1, 2\n"
|
||||||
|
"*ELSET, ELSET=Beam\n"
|
||||||
|
"1\n"
|
||||||
|
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||||
|
"1.0, 1.0, 0.0, 1.0, 1.0\n"
|
||||||
|
"0.0, 1.0, 0.0\n"
|
||||||
|
"*END PART\n"
|
||||||
|
"*ASSEMBLY, NAME=RootAssembly\n"
|
||||||
|
"*INSTANCE, NAME=Beam-1, PART=BeamPart\n"
|
||||||
|
"*END INSTANCE\n"
|
||||||
|
"*END ASSEMBLY\n"
|
||||||
|
"*MATERIAL, NAME=Steel\n"
|
||||||
|
"*ELASTIC\n"
|
||||||
|
"210000.0, 0.3\n"
|
||||||
|
"*STEP\n"
|
||||||
|
"*STATIC\n"
|
||||||
|
"*END STEP\n"};
|
||||||
|
|
||||||
|
for (const auto& test_case : cases) {
|
||||||
|
const TemporaryDeck input{
|
||||||
|
"fesa-inactive-part-" + std::string{test_case.name} + ".inp",
|
||||||
|
"*PART, NAME=Unused\n" + std::string{test_case.unused_records} +
|
||||||
|
"*END PART\n" + active_model,
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = parse_and_map(input.path());
|
||||||
|
|
||||||
|
SCOPED_TRACE(test_case.name);
|
||||||
|
EXPECT_FALSE(result.domain.has_value());
|
||||||
|
const fesa::Diagnostic* diagnostic =
|
||||||
|
find_diagnostic(result, test_case.code);
|
||||||
|
ASSERT_NE(diagnostic, nullptr);
|
||||||
|
ASSERT_TRUE(diagnostic->source.has_value());
|
||||||
|
EXPECT_EQ(diagnostic->source->line, test_case.line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ActiveInstance, RejectsInstanceTransformWithSourceDiagnostic) {
|
TEST(ActiveInstance, RejectsInstanceTransformWithSourceDiagnostic) {
|
||||||
const TemporaryDeck input{
|
const TemporaryDeck input{
|
||||||
"fesa-instance-transform.inp",
|
"fesa-instance-transform.inp",
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ TEST(BeamSection, RejectsElementWithoutSectionAssignment) {
|
|||||||
|
|
||||||
const auto result = parse_and_map(input);
|
const auto result = parse_and_map(input);
|
||||||
|
|
||||||
expect_diagnostic(result, "abaqus.semantic.missing_section", 4U);
|
expect_diagnostic(result, "abaqus.semantic.missing_section", 5U);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BeamSection, RejectsDuplicateSectionAssignment) {
|
TEST(BeamSection, RejectsDuplicateSectionAssignment) {
|
||||||
|
|||||||
Reference in New Issue
Block a user