feat(abaqus-subset-completion): step 2 — single-instance-semantic-validation

This commit is contained in:
KOKO\Mimi
2026-08-01 02:44:01 +09:00
parent 3eeab2fbe4
commit e6bc708be3
9 changed files with 417 additions and 82 deletions
+135
View File
@@ -0,0 +1,135 @@
#include <fesa/io/abaqus/active_input.hpp>
#include <algorithm>
#include <string>
#include <string_view>
#include <utility>
namespace fesa {
namespace {
const std::string* parameter(
const DeckRecord& record,
const std::string_view name) {
const auto found = record.parameters.find(name);
return found == record.parameters.end() ? nullptr : &found->second;
}
bool is_mesh_record(const DeckRecord& record) {
return record.keyword == "NODE" || record.keyword == "ELEMENT";
}
ActiveInputResult failure(
std::string code,
std::string message,
const SourceLocation& source) {
return {
std::nullopt,
{{
DiagnosticStage::semantic,
Severity::error,
std::move(code),
std::move(message),
source,
}},
};
}
} // namespace
ActiveInputResult select_active_input(const ParsedDeck& deck) {
const bool hierarchical =
!deck.parts.empty() || deck.assembly.has_value();
if (!hierarchical) {
return {
ActiveInputView{
true,
{},
{},
deck.global_records,
{},
},
{},
};
}
const auto flat_mesh =
std::ranges::find_if(deck.global_records, is_mesh_record);
if (flat_mesh != deck.global_records.end()) {
return failure(
"abaqus.semantic.mixed_mesh_organization",
"Flat mesh records cannot be mixed with Part/Assembly input.",
flat_mesh->source);
}
if (!deck.assembly.has_value()) {
const SourceLocation source =
deck.parts.empty() ? SourceLocation{} : deck.parts.front().source;
return failure(
"abaqus.semantic.assembly_count",
"Hierarchical Phase 1 input requires exactly one Assembly.",
source);
}
const ParsedAssembly& assembly = *deck.assembly;
if (assembly.instances.size() != 1U) {
const SourceLocation& source = assembly.instances.size() > 1U
? assembly.instances[1].source
: assembly.source;
return failure(
"abaqus.semantic.instance_count",
"Phase 1 requires exactly one Instance.",
source);
}
const ParsedInstance& instance = assembly.instances.front();
if (!instance.transform_data.empty()) {
const SourceLocation& source = instance.transform_sources.empty()
? instance.source
: instance.transform_sources.front();
return failure(
"abaqus.semantic.instance_transform",
"Instance translation and rotation data are unsupported.",
source);
}
const auto part =
std::ranges::find(deck.parts, instance.part_name, &ParsedPart::name);
if (part == deck.parts.end()) {
return failure(
"abaqus.semantic.missing_part",
"Instance '" + instance.name + "' references missing Part '" +
instance.part_name + "'.",
instance.source);
}
for (const DeckRecord& record : assembly.records) {
if (record.keyword != "NSET" && record.keyword != "ELSET") {
continue;
}
const std::string* record_instance = parameter(record, "INSTANCE");
if (record_instance == nullptr || *record_instance != instance.name) {
const std::string* name = parameter(
record, record.keyword == "NSET" ? "NSET" : "ELSET");
return failure(
"abaqus.semantic.wrong_instance",
"Assembly set '" +
(name == nullptr ? std::string{} : *name) +
"' must reference the active Instance.",
record.source);
}
}
return {
ActiveInputView{
false,
part->name,
instance.name,
part->records,
assembly.records,
},
{},
};
}
} // namespace fesa
+6 -1
View File
@@ -214,6 +214,11 @@ ParseDeckResult parse_deck(const std::filesystem::path& path) {
const std::vector<std::string> fields = split_fields(content);
if (scope == Scope::instance) {
current_instance->transform_data.push_back(fields);
current_instance->transform_sources.push_back(SourceLocation{
path,
line_number,
first_nonspace + 1U,
});
} else if (current_record != nullptr) {
current_record->data.push_back(fields);
current_record->data_sources.push_back(SourceLocation{
@@ -327,7 +332,7 @@ ParseDeckResult parse_deck(const std::filesystem::path& path) {
return missing_parameter(keyword, "PART");
}
current_instance =
ParsedInstance{*name, *part_name, {}, source};
ParsedInstance{*name, *part_name, {}, {}, source};
scope = Scope::instance;
continue;
}
+18 -79
View File
@@ -1,5 +1,7 @@
#include <fesa/io/abaqus/semantic_mapper.hpp>
#include <fesa/io/abaqus/active_input.hpp>
#include <algorithm>
#include <array>
#include <charconv>
@@ -26,24 +28,26 @@ const std::string* parameter(
return found == record.parameters.end() ? nullptr : &found->second;
}
bool is_mesh_record(const DeckRecord& record) {
return record.keyword == "NODE" || record.keyword == "ELEMENT";
}
class DeckMapper final {
public:
explicit DeckMapper(const ParsedDeck& deck) : deck_{deck} {}
[[nodiscard]] DomainBuildResult map() {
if (!select_active_scope()) {
ActiveInputResult selected = select_active_input(deck_);
if (!selected.input.has_value()) {
diagnostics_ = std::move(selected.diagnostics);
return failure();
}
active_records_ = selected.input->part_records;
assembly_records_ = selected.input->assembly_records;
part_name_ = std::move(selected.input->part_name);
instance_name_ = std::move(selected.input->instance_name);
collect_materials();
collect_nodes();
collect_raw_sets(*active_records_, false);
if (active_assembly_ != nullptr) {
collect_raw_sets(active_assembly_->records, true);
collect_raw_sets(active_records_, false);
if (!assembly_records_.empty()) {
collect_raw_sets(assembly_records_, true);
}
collect_sections();
collect_elements();
@@ -153,71 +157,6 @@ private:
return value;
}
bool select_active_scope() {
const bool has_hierarchical_input =
!deck_.parts.empty() || deck_.assembly.has_value();
if (!has_hierarchical_input) {
active_records_ = &deck_.global_records;
return true;
}
if (std::ranges::any_of(deck_.global_records, is_mesh_record)) {
add_error(
"abaqus.semantic.mixed_mesh_organization",
"Flat mesh records cannot be mixed with Part/Assembly input.",
std::ranges::find_if(
deck_.global_records, is_mesh_record)->source);
return false;
}
if (!deck_.assembly.has_value()) {
const SourceLocation source =
deck_.parts.empty() ? SourceLocation{} : deck_.parts[0].source;
add_error(
"abaqus.semantic.assembly_count",
"Hierarchical Phase 1 input requires exactly one Assembly.",
source);
return false;
}
active_assembly_ = &*deck_.assembly;
if (active_assembly_->instances.size() != 1U) {
const SourceLocation& source =
active_assembly_->instances.size() > 1U
? active_assembly_->instances[1].source
: active_assembly_->source;
add_error(
"abaqus.semantic.instance_count",
"Phase 1 requires exactly one Instance.",
source);
return false;
}
const ParsedInstance& instance = active_assembly_->instances.front();
if (!instance.transform_data.empty()) {
add_error(
"abaqus.semantic.instance_transform",
"Instance translation and rotation data are unsupported.",
instance.source);
return false;
}
const auto part = std::ranges::find(
deck_.parts, instance.part_name, &ParsedPart::name);
if (part == deck_.parts.end()) {
add_error(
"abaqus.semantic.missing_part",
"Instance '" + instance.name + "' references missing Part '" +
instance.part_name + "'.",
instance.source);
return false;
}
active_records_ = &part->records;
part_name_ = part->name;
instance_name_ = instance.name;
return true;
}
void collect_materials() {
const std::string* current_name = nullptr;
for (const DeckRecord& record : deck_.global_records) {
@@ -264,7 +203,7 @@ private:
}
void collect_nodes() {
for (const DeckRecord& record : *active_records_) {
for (const DeckRecord& record : active_records_) {
if (record.keyword != "NODE") {
continue;
}
@@ -298,7 +237,7 @@ private:
}
void collect_raw_sets(
const std::vector<DeckRecord>& records,
const std::span<const DeckRecord> records,
const bool assembly_scope) {
for (const DeckRecord& record : records) {
const bool is_node_set = record.keyword == "NSET";
@@ -343,7 +282,7 @@ private:
}
void collect_sections() {
for (const DeckRecord& record : *active_records_) {
for (const DeckRecord& record : active_records_) {
if (record.keyword != "BEAM GENERAL SECTION") {
continue;
}
@@ -422,7 +361,7 @@ private:
}
void collect_elements() {
for (const DeckRecord& record : *active_records_) {
for (const DeckRecord& record : active_records_) {
if (record.keyword != "ELEMENT") {
continue;
}
@@ -661,8 +600,8 @@ private:
const ParsedDeck& deck_;
DomainBuilder builder_;
std::vector<Diagnostic> diagnostics_;
const std::vector<DeckRecord>* active_records_ = nullptr;
const ParsedAssembly* active_assembly_ = nullptr;
std::span<const DeckRecord> active_records_;
std::span<const DeckRecord> assembly_records_;
std::string part_name_;
std::string instance_name_;
LabelMap node_ids_;