241 lines
10 KiB
C++
241 lines
10 KiB
C++
#include "fesa/elements/element_factory.h"
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "fesa/elements/euler_beam_3d.h"
|
|
#include "fesa/elements/mitc4_shell.h"
|
|
#include "fesa/materials/material.h"
|
|
#include "fesa/model/domain.h"
|
|
#include "fesa/properties/element_property.h"
|
|
|
|
namespace fesa {
|
|
namespace {
|
|
|
|
/// @brief Creates one deterministic factory failure for a semantic element.
|
|
Result<std::unique_ptr<Element>> FactoryFailure(
|
|
const ElementDefinition& definition, const Domain& domain, std::string code,
|
|
std::string message) {
|
|
return Result<std::unique_ptr<Element>>::Failure(Status::Failure(
|
|
FailureCategory::kModel, {{Severity::kError,
|
|
std::move(code),
|
|
{domain.SourcePath(), 0U},
|
|
"*ELEMENT",
|
|
definition.SourceId().source_label_text,
|
|
std::move(message)}}));
|
|
}
|
|
|
|
/// @brief Returns the shared stable six-component node ordering.
|
|
std::vector<DofComponent> FullNodeComponents() {
|
|
return {DofComponent::kUx, DofComponent::kUy, DofComponent::kUz,
|
|
DofComponent::kUrx, DofComponent::kUry, DofComponent::kUrz};
|
|
}
|
|
|
|
/// @brief Finds one element's unified stable Domain collection position.
|
|
std::optional<EntityIndex> FindElementIndex(const ElementDefinition& definition,
|
|
const Domain& domain) {
|
|
for (std::size_t index = 0U; index < domain.Elements().Size(); ++index) {
|
|
if (&domain.Elements()[index] == &definition) {
|
|
return static_cast<EntityIndex>(index);
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
/// @brief Finds a Domain-owned B33 definition without performing a cast.
|
|
std::optional<EntityIndex> FindBeamIndex(const ElementDefinition& definition,
|
|
const Domain& domain) {
|
|
for (std::size_t index = 0U; index < domain.BeamElements().Size(); ++index) {
|
|
const ElementDefinition* candidate = &domain.BeamElements()[index];
|
|
if (candidate == &definition) {
|
|
return static_cast<EntityIndex>(index);
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
/// @brief Finds a Domain-owned MITC4 definition without performing a cast.
|
|
std::optional<EntityIndex> FindShellIndex(const ElementDefinition& definition,
|
|
const Domain& domain) {
|
|
for (std::size_t index = 0U; index < domain.ShellElements().Size(); ++index) {
|
|
const ElementDefinition* candidate = &domain.ShellElements()[index];
|
|
if (candidate == &definition) {
|
|
return static_cast<EntityIndex>(index);
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
/// @brief Verifies every topology entry before indexing Domain nodes.
|
|
bool HasValidNodes(const ElementDefinition& definition, const Domain& domain,
|
|
const std::size_t expected_count) {
|
|
if (definition.NodeIndices().size() != expected_count) {
|
|
return false;
|
|
}
|
|
for (const EntityIndex node : definition.NodeIndices()) {
|
|
if (node >= domain.Nodes().size()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Result<std::unique_ptr<Element>> ElementFactory::CreateBeam(
|
|
const ElementDefinition& definition, const Domain& domain) const {
|
|
const auto element_index = FindElementIndex(definition, domain);
|
|
const auto beam_index = FindBeamIndex(definition, domain);
|
|
if (!element_index.has_value() || !beam_index.has_value()) {
|
|
return FactoryFailure(
|
|
definition, domain, "invalid-element-definition",
|
|
"The B33 definition must be owned by the supplied Domain.");
|
|
}
|
|
// The concrete access is valid only after the kind and Domain-owned
|
|
// collection identity checks performed by Create and FindBeamIndex.
|
|
const auto& beam_definition =
|
|
static_cast<const EulerBeam3DDefinition&>(definition);
|
|
if (!HasValidNodes(definition, domain, 2U)) {
|
|
return FactoryFailure(definition, domain, "invalid-element-topology",
|
|
"B33 requires two valid Domain node indices.");
|
|
}
|
|
if (definition.PropertyIndex() >= domain.Properties().Size() ||
|
|
definition.MaterialIndex() >= domain.Materials().Size()) {
|
|
return FactoryFailure(
|
|
definition, domain, "invalid-element-assignment",
|
|
"B33 property and material indices must resolve in the Domain.");
|
|
}
|
|
const ElementProperty& property =
|
|
domain.Properties()[definition.PropertyIndex()];
|
|
const Material& material = domain.Materials()[definition.MaterialIndex()];
|
|
if (property.Kind() != ElementPropertyKind::kGeneralBeamSection ||
|
|
material.Kind() != MaterialKind::kIsotropicLinearElastic) {
|
|
return FactoryFailure(
|
|
definition, domain, "incompatible-element-assignment",
|
|
"B33 requires a general beam section and isotropic elastic material.");
|
|
}
|
|
// These casts are centralized after explicit discriminator validation.
|
|
const auto& section = static_cast<const GeneralBeamSection&>(property);
|
|
const auto& elastic =
|
|
static_cast<const IsotropicLinearElasticMaterial&>(material);
|
|
auto candidate = EulerBeam3D::Create(
|
|
domain.Nodes()[definition.NodeIndices()[0U]],
|
|
domain.Nodes()[definition.NodeIndices()[1U]], section, elastic);
|
|
if (!candidate.HasValue()) {
|
|
return Result<std::unique_ptr<Element>>::Failure(candidate.GetStatus());
|
|
}
|
|
|
|
ElementDofLayout layout{definition.SourceId(), definition.NodeIndices(),
|
|
FullNodeComponents()};
|
|
std::vector<SourceEntityId> node_source_ids;
|
|
node_source_ids.reserve(2U);
|
|
for (const EntityIndex node : definition.NodeIndices()) {
|
|
node_source_ids.push_back(domain.Nodes()[node].source_id);
|
|
}
|
|
auto runtime = std::make_unique<EulerBeam3D>(std::move(candidate.Value()));
|
|
runtime->BindRuntime(std::move(layout), *element_index,
|
|
std::move(node_source_ids), beam_definition.location);
|
|
std::unique_ptr<Element> result = std::move(runtime);
|
|
return Result<std::unique_ptr<Element>>::Success(std::move(result));
|
|
}
|
|
|
|
Result<std::unique_ptr<Element>> ElementFactory::CreateShell(
|
|
const ElementDefinition& definition, const Domain& domain) const {
|
|
const auto element_index = FindElementIndex(definition, domain);
|
|
const auto shell_index = FindShellIndex(definition, domain);
|
|
if (!element_index.has_value() || !shell_index.has_value()) {
|
|
return FactoryFailure(
|
|
definition, domain, "invalid-element-definition",
|
|
"The MITC4 definition must be owned by the supplied Domain.");
|
|
}
|
|
// The concrete access is valid only after the kind and Domain-owned
|
|
// collection identity checks performed by Create and FindShellIndex.
|
|
const auto& shell_definition =
|
|
static_cast<const Mitc4ShellDefinition&>(definition);
|
|
if (!HasValidNodes(definition, domain, 4U)) {
|
|
return FactoryFailure(definition, domain, "invalid-element-topology",
|
|
"MITC4 requires four valid Domain node indices.");
|
|
}
|
|
if (definition.PropertyIndex() >= domain.Properties().Size() ||
|
|
definition.MaterialIndex() >= domain.Materials().Size()) {
|
|
return FactoryFailure(
|
|
definition, domain, "invalid-element-assignment",
|
|
"MITC4 property and material indices must resolve in the Domain.");
|
|
}
|
|
const ElementProperty& property =
|
|
domain.Properties()[definition.PropertyIndex()];
|
|
const Material& material = domain.Materials()[definition.MaterialIndex()];
|
|
if (property.Kind() != ElementPropertyKind::kShellSection ||
|
|
material.Kind() != MaterialKind::kIsotropicLinearElastic) {
|
|
return FactoryFailure(
|
|
definition, domain, "incompatible-element-assignment",
|
|
"MITC4 requires a shell section and isotropic elastic material.");
|
|
}
|
|
const auto& section = static_cast<const ShellSection&>(property);
|
|
if (section.MaterialIndex() != definition.MaterialIndex()) {
|
|
return FactoryFailure(
|
|
definition, domain, "incompatible-element-assignment",
|
|
"MITC4 definition and shell section must reference one material.");
|
|
}
|
|
// These casts are centralized after explicit discriminator validation.
|
|
const auto& elastic =
|
|
static_cast<const IsotropicLinearElasticMaterial&>(material);
|
|
|
|
std::vector<const ShellNodeInitialFrame*> frame_by_node(domain.Nodes().size(),
|
|
nullptr);
|
|
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
|
if (frame.node_index >= frame_by_node.size() ||
|
|
frame_by_node[frame.node_index] != nullptr) {
|
|
return FactoryFailure(
|
|
definition, domain, "invalid-shell-frame",
|
|
"Shell initial frames must map uniquely to valid Domain nodes.");
|
|
}
|
|
frame_by_node[frame.node_index] = &frame;
|
|
}
|
|
|
|
std::array<const Node*, 4> nodes{};
|
|
std::array<std::array<double, 3>, 4> directors{};
|
|
for (std::size_t position = 0U; position < nodes.size(); ++position) {
|
|
const EntityIndex node = definition.NodeIndices()[position];
|
|
const ShellNodeInitialFrame* frame = frame_by_node[node];
|
|
if (frame == nullptr) {
|
|
return FactoryFailure(
|
|
definition, domain, "missing-shell-frame",
|
|
"MITC4 requires one initial frame for every element node.");
|
|
}
|
|
nodes[position] = &domain.Nodes()[node];
|
|
directors[position] = frame->director;
|
|
}
|
|
|
|
auto candidate = Mitc4Shell::Create(nodes, directors, section, elastic);
|
|
if (!candidate.HasValue()) {
|
|
return Result<std::unique_ptr<Element>>::Failure(candidate.GetStatus());
|
|
}
|
|
ElementDofLayout layout{definition.SourceId(), definition.NodeIndices(),
|
|
FullNodeComponents()};
|
|
auto runtime = std::make_unique<Mitc4Shell>(std::move(candidate.Value()));
|
|
runtime->BindRuntime(std::move(layout), *element_index,
|
|
shell_definition.location);
|
|
std::unique_ptr<Element> result = std::move(runtime);
|
|
return Result<std::unique_ptr<Element>>::Success(std::move(result));
|
|
}
|
|
|
|
Result<std::unique_ptr<Element>> ElementFactory::Create(
|
|
const ElementDefinition& definition, const Domain& domain) const {
|
|
switch (definition.Kind()) {
|
|
case ElementDefinitionKind::kEulerBeam3D:
|
|
return CreateBeam(definition, domain);
|
|
case ElementDefinitionKind::kMitc4Shell:
|
|
return CreateShell(definition, domain);
|
|
}
|
|
return FactoryFailure(definition, domain, "unsupported-element-definition",
|
|
"The element definition kind is not supported.");
|
|
}
|
|
|
|
} // namespace fesa
|