Integrate Refactor solver sources and dual MSVC CMake builds
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
#include "fesa/analysis/prepared_analysis.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "analysis_procedures.h"
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/analysis/hht_dynamic_procedure.h"
|
||||
#include "fesa/analysis/linear_frequency_procedure.h"
|
||||
@@ -9,15 +12,13 @@
|
||||
#include "fesa/analysis/newton_iteration_log.h"
|
||||
#include "fesa/analysis/nonlinear_static_procedure.h"
|
||||
#include "fesa/analysis/solid_frequency_procedure.h"
|
||||
#include "fesa/analysis/solid_hht_dynamic_procedure.h"
|
||||
#include "fesa/analysis/solid_static_procedure.h"
|
||||
#include "fesa/analysis/transient_heat_procedure.h"
|
||||
#include "fesa/app/output_paths.h"
|
||||
#include "fesa/io/abaqus/domain_mapper.h"
|
||||
#include "fesa/io/abaqus/input_reader.h"
|
||||
#include "fesa/results/dynamic_results_writer.h"
|
||||
#include "fesa/results/eigen_results_writer.h"
|
||||
#include "fesa/results/nonlinear_results_writer.h"
|
||||
#include "fesa/results/results_writer.h"
|
||||
#include "fesa/results/solid_results_writer.h"
|
||||
#include "fesa/results/thermal_results_writer.h"
|
||||
#include "fesa/results/prepared_results_writer.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
@@ -39,6 +40,185 @@ Result<PreparedAnalysisInput> PrepareAnalysisInput(
|
||||
return Result<PreparedAnalysisInput>::Success(std::move(prepared));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
enum class ProcedureKind {
|
||||
kLinearStatic,
|
||||
kNonlinearStatic,
|
||||
kLinearFrequency,
|
||||
kHhtDynamic,
|
||||
kSolidStatic,
|
||||
kSolidFrequency,
|
||||
kSolidHhtDynamic,
|
||||
kTransientHeat
|
||||
};
|
||||
|
||||
// Keep procedure precedence and step-count diagnostics common to both APIs.
|
||||
Result<ProcedureKind> SelectProcedure(const Domain& domain) {
|
||||
if (domain.ThermalStep() != nullptr)
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kTransientHeat);
|
||||
if (domain.Steps().Size() != 1U) {
|
||||
return Result<ProcedureKind>::Failure(Status::Failure(
|
||||
FailureCategory::kInput,
|
||||
{{Severity::kError,
|
||||
"invalid-analysis-step-count",
|
||||
{},
|
||||
"*STEP",
|
||||
{},
|
||||
"Prepared analysis requires exactly one analysis step."}}));
|
||||
}
|
||||
if (domain.IsSolid()) {
|
||||
if (domain.Dynamic() != nullptr)
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kSolidHhtDynamic);
|
||||
if (domain.Steps()[0U].IsFrequency())
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kSolidFrequency);
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kSolidStatic);
|
||||
}
|
||||
if (domain.Dynamic() != nullptr)
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kHhtDynamic);
|
||||
if (domain.Steps()[0U].IsFrequency())
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kLinearFrequency);
|
||||
if (domain.Steps()[0U].IsNonlinearStatic())
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kNonlinearStatic);
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kLinearStatic);
|
||||
}
|
||||
|
||||
template <typename Payload>
|
||||
Result<PreparedAnalysisOutcome> Completed(Result<Payload> result) {
|
||||
if (!result.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(result.GetStatus());
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(result.Value())});
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
MissingExecutor(const char* code, const char* keyword, const char* message) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(
|
||||
Status::Failure(FailureCategory::kSolver,
|
||||
{{Severity::kError, code, {}, keyword, {}, message}}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
LinearStaticProcedure::Execute(const Domain& domain) {
|
||||
return Completed(executor_.Execute(domain));
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
NonlinearStaticProcedure::Execute(const Domain& domain) {
|
||||
return Completed(executor_.Execute(domain, log_));
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
LinearFrequencyProcedure::Execute(const Domain& domain) {
|
||||
return Completed(executor_.Execute(domain));
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
HhtDynamicProcedure::Execute(const Domain& domain) {
|
||||
return Completed(executor_.Execute(domain, log_));
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
SolidStaticProcedure::Execute(const Domain& domain) {
|
||||
SolidStaticState state;
|
||||
const Status execution = executor_.ExecuteInto(domain, state);
|
||||
for (const auto& record : state.IterationRecords()) {
|
||||
const Status written = log_.Write(record, domain.Steps()[0U].Name());
|
||||
if (!written.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(written);
|
||||
}
|
||||
if (!execution.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(execution);
|
||||
return Result<PreparedAnalysisOutcome>::Success(PreparedAnalysisOutcome{
|
||||
SolidStaticExecutionResult{std::move(state), domain.Warnings()}});
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
SolidFrequencyProcedure::Execute(const Domain& domain) {
|
||||
return Completed(executor_.Execute(domain));
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
SolidHhtDynamicProcedure::Execute(const Domain& domain) {
|
||||
auto model = AnalysisModel::Create(domain);
|
||||
if (!model.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(model.GetStatus());
|
||||
auto dofs = DofManager::Create(model.Value());
|
||||
if (!dofs.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(dofs.GetStatus());
|
||||
auto state = SolidDynamicAnalysisState::Create(domain, dofs.Value());
|
||||
if (!state.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(state.GetStatus());
|
||||
const Status execution = executor_.ExecuteInto(domain, state.Value());
|
||||
for (const auto& record : state.Value().IterationRecords()) {
|
||||
const Status written = log_.Write(record);
|
||||
if (!written.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(written);
|
||||
}
|
||||
if (!execution.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(execution);
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{SolidDynamicExecutionResult{
|
||||
std::move(dofs.Value()), std::move(state.Value())}});
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
TransientHeatProcedure::Execute(const Domain& domain) {
|
||||
return Completed(executor_.Execute(domain));
|
||||
}
|
||||
|
||||
Result<std::unique_ptr<AnalysisProcedure>> CreateAnalysisProcedure(
|
||||
const Domain& domain, const ParallelFor& parallel_for,
|
||||
LinearSolver& linear_solver, TangentSolver& tangent_solver,
|
||||
const EigenSolver& eigen_solver, NewtonIterationLogWriter& log) {
|
||||
const auto kind = SelectProcedure(domain);
|
||||
if (!kind.HasValue())
|
||||
return Result<std::unique_ptr<AnalysisProcedure>>::Failure(
|
||||
kind.GetStatus());
|
||||
std::unique_ptr<AnalysisProcedure> procedure;
|
||||
switch (kind.Value()) {
|
||||
case ProcedureKind::kLinearStatic:
|
||||
procedure =
|
||||
std::make_unique<LinearStaticProcedure>(parallel_for, linear_solver);
|
||||
break;
|
||||
case ProcedureKind::kNonlinearStatic:
|
||||
procedure = std::make_unique<NonlinearStaticProcedure>(parallel_for,
|
||||
tangent_solver, log);
|
||||
break;
|
||||
case ProcedureKind::kLinearFrequency:
|
||||
procedure = std::make_unique<LinearFrequencyProcedure>(eigen_solver);
|
||||
break;
|
||||
case ProcedureKind::kHhtDynamic:
|
||||
procedure = std::make_unique<HhtDynamicProcedure>(
|
||||
parallel_for, linear_solver, tangent_solver, log);
|
||||
break;
|
||||
case ProcedureKind::kSolidStatic:
|
||||
procedure = std::make_unique<SolidStaticProcedure>(
|
||||
parallel_for, linear_solver, tangent_solver, log);
|
||||
break;
|
||||
case ProcedureKind::kSolidFrequency:
|
||||
procedure = std::make_unique<SolidFrequencyProcedure>(eigen_solver);
|
||||
break;
|
||||
case ProcedureKind::kSolidHhtDynamic:
|
||||
procedure = std::make_unique<SolidHhtDynamicProcedure>(
|
||||
parallel_for, linear_solver, tangent_solver, log);
|
||||
break;
|
||||
case ProcedureKind::kTransientHeat:
|
||||
procedure =
|
||||
std::make_unique<TransientHeatProcedure>(parallel_for, linear_solver);
|
||||
break;
|
||||
}
|
||||
return Result<std::unique_ptr<AnalysisProcedure>>::Success(
|
||||
std::move(procedure));
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
ExecutePreparedAnalysis(const Domain& domain, AnalysisProcedure& procedure) {
|
||||
return procedure.Execute(domain);
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
|
||||
const Domain& domain, LinearStaticProcedureExecutor& linear_executor,
|
||||
NonlinearStaticProcedureExecutor& nonlinear_executor,
|
||||
@@ -49,133 +229,59 @@ Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
|
||||
const SolidStaticProcedureExecutor* solid_static_executor,
|
||||
const SolidFrequencyProcedureExecutor* solid_frequency_executor,
|
||||
const SolidHhtDynamicProcedureExecutor* solid_dynamic_executor) {
|
||||
if (domain.ThermalStep() != nullptr) {
|
||||
if (thermal_executor == nullptr) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(
|
||||
Status::Failure(FailureCategory::kSolver,
|
||||
{{Severity::kError,
|
||||
"missing-thermal-executor",
|
||||
{},
|
||||
"HEAT TRANSFER",
|
||||
{},
|
||||
"Thermal execution adapter is required."}}));
|
||||
}
|
||||
auto thermal = thermal_executor->Execute(domain);
|
||||
if (!thermal.HasValue()) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(thermal.GetStatus());
|
||||
}
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(thermal.Value())});
|
||||
}
|
||||
if (domain.Steps().Size() != 1U) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(Status::Failure(
|
||||
FailureCategory::kInput,
|
||||
{{Severity::kError,
|
||||
"invalid-analysis-step-count",
|
||||
{},
|
||||
"*STEP",
|
||||
{},
|
||||
"Prepared analysis requires exactly one analysis step."}}));
|
||||
}
|
||||
|
||||
if (domain.IsSolid()) {
|
||||
const auto missing = []() {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(Status::Failure(
|
||||
FailureCategory::kSolver,
|
||||
{{Severity::kError,
|
||||
"missing-solid-executor",
|
||||
{},
|
||||
"",
|
||||
{},
|
||||
"The selected Solid execution adapter is required."}}));
|
||||
};
|
||||
if (domain.Dynamic() != nullptr) {
|
||||
if (solid_dynamic_executor == nullptr) return missing();
|
||||
auto model = AnalysisModel::Create(domain);
|
||||
if (!model.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(model.GetStatus());
|
||||
auto dofs = DofManager::Create(model.Value());
|
||||
if (!dofs.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(dofs.GetStatus());
|
||||
auto state = SolidDynamicAnalysisState::Create(domain, dofs.Value());
|
||||
if (!state.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(state.GetStatus());
|
||||
const Status execution =
|
||||
solid_dynamic_executor->ExecuteInto(domain, state.Value());
|
||||
for (const auto& record : state.Value().IterationRecords()) {
|
||||
const Status written = log.Write(record);
|
||||
if (!written.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(written);
|
||||
}
|
||||
if (!execution.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(execution);
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{SolidDynamicExecutionResult{
|
||||
std::move(dofs.Value()), std::move(state.Value())}});
|
||||
}
|
||||
if (domain.Steps()[0U].IsFrequency()) {
|
||||
if (solid_frequency_executor == nullptr) return missing();
|
||||
auto result = solid_frequency_executor->Execute(domain);
|
||||
if (!result.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(result.GetStatus());
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(result.Value())});
|
||||
}
|
||||
if (solid_static_executor == nullptr) return missing();
|
||||
SolidStaticState state;
|
||||
const Status execution = solid_static_executor->ExecuteInto(domain, state);
|
||||
for (const auto& record : state.IterationRecords()) {
|
||||
const Status written = log.Write(record, domain.Steps()[0U].Name());
|
||||
if (!written.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(written);
|
||||
}
|
||||
if (!execution.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(execution);
|
||||
return Result<PreparedAnalysisOutcome>::Success(PreparedAnalysisOutcome{
|
||||
SolidStaticExecutionResult{std::move(state), domain.Warnings()}});
|
||||
}
|
||||
|
||||
if (domain.Dynamic() != nullptr) {
|
||||
const auto kind = SelectProcedure(domain);
|
||||
if (!kind.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(kind.GetStatus());
|
||||
std::unique_ptr<AnalysisProcedure> procedure;
|
||||
switch (kind.Value()) {
|
||||
case ProcedureKind::kLinearStatic:
|
||||
procedure = std::make_unique<LinearStaticProcedure>(linear_executor);
|
||||
break;
|
||||
case ProcedureKind::kNonlinearStatic:
|
||||
procedure =
|
||||
std::make_unique<NonlinearStaticProcedure>(nonlinear_executor, log);
|
||||
break;
|
||||
case ProcedureKind::kLinearFrequency:
|
||||
procedure = std::make_unique<LinearFrequencyProcedure>(frequency_executor);
|
||||
break;
|
||||
case ProcedureKind::kHhtDynamic:
|
||||
if (dynamic_executor == nullptr)
|
||||
return Result<PreparedAnalysisOutcome>::Failure(
|
||||
Status::Failure(FailureCategory::kSolver,
|
||||
{{Severity::kError,
|
||||
"missing-dynamic-executor",
|
||||
{},
|
||||
"DYNAMIC",
|
||||
{},
|
||||
"Dynamic execution adapter is required."}}));
|
||||
auto dynamic = dynamic_executor->Execute(domain, log);
|
||||
if (!dynamic.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(dynamic.GetStatus());
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(dynamic.Value())});
|
||||
return MissingExecutor("missing-dynamic-executor", "DYNAMIC",
|
||||
"Dynamic execution adapter is required.");
|
||||
procedure = std::make_unique<HhtDynamicProcedure>(*dynamic_executor, log);
|
||||
break;
|
||||
case ProcedureKind::kSolidStatic:
|
||||
if (solid_static_executor == nullptr)
|
||||
return MissingExecutor(
|
||||
"missing-solid-executor", "",
|
||||
"The selected Solid execution adapter is required.");
|
||||
procedure =
|
||||
std::make_unique<SolidStaticProcedure>(*solid_static_executor, log);
|
||||
break;
|
||||
case ProcedureKind::kSolidFrequency:
|
||||
if (solid_frequency_executor == nullptr)
|
||||
return MissingExecutor(
|
||||
"missing-solid-executor", "",
|
||||
"The selected Solid execution adapter is required.");
|
||||
procedure =
|
||||
std::make_unique<SolidFrequencyProcedure>(*solid_frequency_executor);
|
||||
break;
|
||||
case ProcedureKind::kSolidHhtDynamic:
|
||||
if (solid_dynamic_executor == nullptr)
|
||||
return MissingExecutor(
|
||||
"missing-solid-executor", "",
|
||||
"The selected Solid execution adapter is required.");
|
||||
procedure = std::make_unique<SolidHhtDynamicProcedure>(
|
||||
*solid_dynamic_executor, log);
|
||||
break;
|
||||
case ProcedureKind::kTransientHeat:
|
||||
if (thermal_executor == nullptr)
|
||||
return MissingExecutor("missing-thermal-executor", "HEAT TRANSFER",
|
||||
"Thermal execution adapter is required.");
|
||||
procedure = std::make_unique<TransientHeatProcedure>(*thermal_executor);
|
||||
break;
|
||||
}
|
||||
if (domain.Steps()[0U].IsFrequency()) {
|
||||
auto eigen = frequency_executor.Execute(domain);
|
||||
if (!eigen.HasValue()) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(eigen.GetStatus());
|
||||
}
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(eigen.Value())});
|
||||
}
|
||||
|
||||
if (domain.Steps()[0U].IsNonlinearStatic()) {
|
||||
Result<NonlinearStaticExecutionResult> nonlinear =
|
||||
nonlinear_executor.Execute(domain, log);
|
||||
if (!nonlinear.HasValue()) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(nonlinear.GetStatus());
|
||||
}
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(nonlinear.Value())});
|
||||
}
|
||||
|
||||
Result<LinearStaticExecutionResult> linear = linear_executor.Execute(domain);
|
||||
if (!linear.HasValue()) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(linear.GetStatus());
|
||||
}
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(linear.Value())});
|
||||
return ExecutePreparedAnalysis(domain, *procedure);
|
||||
}
|
||||
|
||||
Status FinalizePreparedAnalysisResult(
|
||||
@@ -185,68 +291,10 @@ Status FinalizePreparedAnalysisResult(
|
||||
NonlinearResultsWriter& nonlinear_writer, EigenResultsWriter& eigen_writer,
|
||||
DynamicResultsWriter* dynamic_writer, ThermalResultsWriter* thermal_writer,
|
||||
SolidResultsWriter* solid_writer) {
|
||||
if (std::holds_alternative<SolidStaticExecutionResult>(outcome) ||
|
||||
std::holds_alternative<SolidFrequencyState>(outcome) ||
|
||||
std::holds_alternative<SolidDynamicExecutionResult>(outcome)) {
|
||||
if (solid_writer == nullptr)
|
||||
return Status::Failure(FailureCategory::kOutput,
|
||||
{{Severity::kError,
|
||||
"missing-solid-writer",
|
||||
{},
|
||||
"",
|
||||
{},
|
||||
"Solid result writer is required."}});
|
||||
if (const auto* result = std::get_if<SolidStaticExecutionResult>(&outcome))
|
||||
return solid_writer->Write(paths.results_path, domain, result->state,
|
||||
diagnostics);
|
||||
if (const auto* result = std::get_if<SolidFrequencyState>(&outcome))
|
||||
return solid_writer->Write(paths.results_path, domain, *result,
|
||||
diagnostics);
|
||||
return solid_writer->Write(
|
||||
paths.results_path, domain,
|
||||
std::get<SolidDynamicExecutionResult>(outcome).state, diagnostics);
|
||||
}
|
||||
if (std::holds_alternative<ThermalExecutionResult>(outcome)) {
|
||||
if (thermal_writer == nullptr) {
|
||||
return Status::Failure(FailureCategory::kOutput,
|
||||
{{Severity::kError,
|
||||
"missing-thermal-writer",
|
||||
{},
|
||||
"HEAT TRANSFER",
|
||||
{},
|
||||
"Thermal result writer is required."}});
|
||||
}
|
||||
return thermal_writer->Write(
|
||||
paths.results_path, domain,
|
||||
std::get<ThermalExecutionResult>(outcome).state, diagnostics);
|
||||
}
|
||||
if (std::holds_alternative<DynamicExecutionResult>(outcome)) {
|
||||
if (dynamic_writer == nullptr)
|
||||
return Status::Failure(FailureCategory::kOutput,
|
||||
{{Severity::kError,
|
||||
"missing-dynamic-writer",
|
||||
{},
|
||||
"DYNAMIC",
|
||||
{},
|
||||
"Dynamic result writer is required."}});
|
||||
return dynamic_writer->Write(
|
||||
paths.results_path, domain,
|
||||
std::get<DynamicExecutionResult>(outcome).state, diagnostics);
|
||||
}
|
||||
if (std::holds_alternative<EigenAnalysisState>(outcome)) {
|
||||
return eigen_writer.Write(paths.results_path, domain,
|
||||
std::get<EigenAnalysisState>(outcome),
|
||||
diagnostics);
|
||||
}
|
||||
if (std::holds_alternative<LinearStaticExecutionResult>(outcome)) {
|
||||
return linear_writer.Write(
|
||||
paths.results_path, domain,
|
||||
std::get<LinearStaticExecutionResult>(outcome).state, diagnostics);
|
||||
}
|
||||
|
||||
return nonlinear_writer.Write(
|
||||
paths.results_path, domain,
|
||||
std::get<NonlinearStaticExecutionResult>(outcome).state, diagnostics);
|
||||
return WritePreparedAnalysisResult(
|
||||
paths.results_path, domain, outcome, diagnostics, linear_writer,
|
||||
nonlinear_writer, eigen_writer, dynamic_writer, thermal_writer,
|
||||
solid_writer);
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
Reference in New Issue
Block a user