feat(abaqus-subset-completion): step 2 — single-instance-semantic-validation
This commit is contained in:
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
Reference in New Issue
Block a user