mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-01 00:11:57 +08:00
Fixing float nullable config vals and sla override apply func
This commit is contained in:
parent
7143190779
commit
91e67e2e16
@ -61,9 +61,9 @@ namespace Slic3r {
|
|||||||
template<class Archive> void serialize(Archive& ar) { ar(this->value); ar(this->percent); }
|
template<class Archive> void serialize(Archive& ar) { ar(this->value); ar(this->percent); }
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool operator==(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value == r.value && l.percent == r.percent; }
|
inline bool operator==(const FloatOrPercent& l, const FloatOrPercent& r) noexcept { return l.value == r.value && l.percent == r.percent; }
|
||||||
inline bool operator!=(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return !(l == r); }
|
inline bool operator!=(const FloatOrPercent& l, const FloatOrPercent& r) noexcept { return !(l == r); }
|
||||||
inline bool operator< (const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value < r.value || (l.value == r.value && int(l.percent) < int(r.percent)); }
|
inline bool operator< (const FloatOrPercent& l, const FloatOrPercent& r) noexcept { return l.value < r.value || (l.value == r.value && int(l.percent) < int(r.percent)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
@ -320,6 +320,54 @@ public:
|
|||||||
typedef ConfigOption* ConfigOptionPtr;
|
typedef ConfigOption* ConfigOptionPtr;
|
||||||
typedef const ConfigOption* ConfigOptionConstPtr;
|
typedef const ConfigOption* ConfigOptionConstPtr;
|
||||||
|
|
||||||
|
// Nill value will be defined in specializations
|
||||||
|
template<class T, class En = void> struct NilValueTempl
|
||||||
|
{
|
||||||
|
using NilType = T;
|
||||||
|
static_assert(always_false<T>::value, "Type has no well defined nil value");
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T> struct NilValueTempl<T, std::enable_if_t<std::is_integral_v<T>, void>> {
|
||||||
|
using NilType = T;
|
||||||
|
static constexpr auto value = std::numeric_limits<T>::max();
|
||||||
|
};
|
||||||
|
|
||||||
|
template<> struct NilValueTempl<bool> : public NilValueTempl<int>{};
|
||||||
|
|
||||||
|
// For enums the nil is the max value of the underlying type.
|
||||||
|
template<class T>
|
||||||
|
struct NilValueTempl<T, std::enable_if_t<std::is_enum_v<T>, void>>
|
||||||
|
{
|
||||||
|
using NilType = T;
|
||||||
|
static constexpr auto value = static_cast<T>(std::numeric_limits<std::underlying_type_t<T>>::max());
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T> struct NilValueTempl<T, std::enable_if_t<std::is_floating_point_v<T>, void>> {
|
||||||
|
using NilType = T;
|
||||||
|
static constexpr auto value = std::numeric_limits<T>::quiet_NaN();
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct NilValueTempl<FloatOrPercent> : public NilValueTempl<double> {};
|
||||||
|
|
||||||
|
template<> struct NilValueTempl<std::string> {
|
||||||
|
using NilType = const char *;
|
||||||
|
|
||||||
|
static constexpr const char* value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
template<int N, class T> struct NilValueTempl<Vec<N, T>> {
|
||||||
|
using NilType = Vec<N, T>;
|
||||||
|
// No constexpr for Vec<N, T>
|
||||||
|
static inline const Vec<N, T> value = Vec<N, T>::Ones() * NilValueTempl<remove_cvref_t<T>>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T> using NilType = typename NilValueTempl<remove_cvref_t<T>>::NilType;
|
||||||
|
|
||||||
|
// Define shortcut as a function instead of a static const var so that it can be constexpr
|
||||||
|
// even if the NilValueTempl::value is not constexpr.
|
||||||
|
template<class T> static constexpr NilType<T> NilValue() noexcept { return NilValueTempl<remove_cvref_t<T>>::value; }
|
||||||
|
|
||||||
// Value of a single valued option (bool, int, float, string, point, enum)
|
// Value of a single valued option (bool, int, float, string, point, enum)
|
||||||
template <class T, bool NULLABLE = false>
|
template <class T, bool NULLABLE = false>
|
||||||
class ConfigOptionSingle : public ConfigOption {
|
class ConfigOptionSingle : public ConfigOption {
|
||||||
@ -357,12 +405,12 @@ public:
|
|||||||
throw ConfigurationError("Cannot override a nullable ConfigOption.");
|
throw ConfigurationError("Cannot override a nullable ConfigOption.");
|
||||||
if (rhs->type() != this->type())
|
if (rhs->type() != this->type())
|
||||||
throw ConfigurationError("ConfigOptionVector.overriden_by() applied to different types.");
|
throw ConfigurationError("ConfigOptionVector.overriden_by() applied to different types.");
|
||||||
auto rhs_vec = static_cast<const ConfigOptionSingle*>(rhs);
|
auto rhs_co = static_cast<const ConfigOptionSingle*>(rhs);
|
||||||
if (! rhs->nullable())
|
if (! rhs->nullable())
|
||||||
// Overridding a non-nullable object with another non-nullable object.
|
// Overridding a non-nullable object with another non-nullable object.
|
||||||
return this->value != rhs_vec->value;
|
return this->value != rhs_co->value;
|
||||||
|
|
||||||
return false;
|
return !rhs_co->is_nil() && rhs_co->value != this->value;
|
||||||
}
|
}
|
||||||
// Apply an override option, possibly a nullable one.
|
// Apply an override option, possibly a nullable one.
|
||||||
bool apply_override(const ConfigOption *rhs) override {
|
bool apply_override(const ConfigOption *rhs) override {
|
||||||
@ -370,27 +418,37 @@ public:
|
|||||||
throw ConfigurationError("Cannot override a nullable ConfigOption.");
|
throw ConfigurationError("Cannot override a nullable ConfigOption.");
|
||||||
if (rhs->type() != this->type())
|
if (rhs->type() != this->type())
|
||||||
throw ConfigurationError("ConfigOptionVector.apply_override() applied to different types.");
|
throw ConfigurationError("ConfigOptionVector.apply_override() applied to different types.");
|
||||||
auto rhs_vec = static_cast<const ConfigOptionSingle*>(rhs);
|
auto rhs_co = static_cast<const ConfigOptionSingle*>(rhs);
|
||||||
if (! rhs->nullable()) {
|
if (! rhs->nullable()) {
|
||||||
// Overridding a non-nullable object with another non-nullable object.
|
// Overridding a non-nullable object with another non-nullable object.
|
||||||
if (this->value != rhs_vec->value) {
|
if (this->value != rhs_co->value) {
|
||||||
this->value = rhs_vec->value;
|
this->value = rhs_co->value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!rhs_co->is_nil() && rhs_co->value != this->value) {
|
||||||
|
this->value = rhs_co->value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool nullable() const override { return NULLABLE; }
|
bool nullable() const override { return NULLABLE; }
|
||||||
|
|
||||||
static T nil_value() { return std::numeric_limits<T>::min(); }
|
static constexpr NilType<T> nil_value() { return NilValue<T>(); }
|
||||||
|
|
||||||
// A scalar is nil, or all values of a vector are nil.
|
// A scalar is nil, or all values of a vector are nil.
|
||||||
bool is_nil() const override
|
bool is_nil() const override
|
||||||
{
|
{
|
||||||
return this->value == nil_value();
|
bool ret = false;
|
||||||
|
|
||||||
|
if constexpr (NULLABLE)
|
||||||
|
ret = this->value == nil_value();
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -636,7 +694,18 @@ public:
|
|||||||
std::string serialize() const override
|
std::string serialize() const override
|
||||||
{
|
{
|
||||||
std::ostringstream ss;
|
std::ostringstream ss;
|
||||||
ss << this->value;
|
double v = this->value;
|
||||||
|
|
||||||
|
if (std::isfinite(v))
|
||||||
|
ss << v;
|
||||||
|
else if (std::isnan(v)) {
|
||||||
|
if (NULLABLE)
|
||||||
|
ss << "nil";
|
||||||
|
else
|
||||||
|
throw ConfigurationError("Serializing NaN");
|
||||||
|
} else
|
||||||
|
throw ConfigurationError("Serializing invalid number");
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -644,7 +713,16 @@ public:
|
|||||||
{
|
{
|
||||||
UNUSED(append);
|
UNUSED(append);
|
||||||
std::istringstream iss(str);
|
std::istringstream iss(str);
|
||||||
iss >> this->value;
|
|
||||||
|
if (str == "nil") {
|
||||||
|
if (NULLABLE)
|
||||||
|
this->value = this->nil_value();
|
||||||
|
else
|
||||||
|
throw ConfigurationError("Deserializing nil into a non-nullable object");
|
||||||
|
} else {
|
||||||
|
iss >> this->value;
|
||||||
|
}
|
||||||
|
|
||||||
return !iss.fail();
|
return !iss.fail();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,6 +732,11 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_nil() const override
|
||||||
|
{
|
||||||
|
return std::isnan(this->value);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class cereal::access;
|
friend class cereal::access;
|
||||||
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<double, NULLABLE>>(this)); }
|
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<double, NULLABLE>>(this)); }
|
||||||
@ -805,7 +888,14 @@ public:
|
|||||||
std::string serialize() const override
|
std::string serialize() const override
|
||||||
{
|
{
|
||||||
std::ostringstream ss;
|
std::ostringstream ss;
|
||||||
ss << this->value;
|
if (this->value == this->nil_value()) {
|
||||||
|
if (NULLABLE)
|
||||||
|
ss << "nil";
|
||||||
|
else
|
||||||
|
throw ConfigurationError("Serializing NaN");
|
||||||
|
} else
|
||||||
|
ss << this->value;
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -813,7 +903,16 @@ public:
|
|||||||
{
|
{
|
||||||
UNUSED(append);
|
UNUSED(append);
|
||||||
std::istringstream iss(str);
|
std::istringstream iss(str);
|
||||||
iss >> this->value;
|
|
||||||
|
if (str == "nil") {
|
||||||
|
if (NULLABLE)
|
||||||
|
this->value = this->nil_value();
|
||||||
|
else
|
||||||
|
throw ConfigurationError("Deserializing nil into a non-nullable object");
|
||||||
|
} else {
|
||||||
|
iss >> this->value;
|
||||||
|
}
|
||||||
|
|
||||||
return !iss.fail();
|
return !iss.fail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +45,9 @@ namespace {
|
|||||||
std::string to_ini(const ConfMap &m)
|
std::string to_ini(const ConfMap &m)
|
||||||
{
|
{
|
||||||
std::string ret;
|
std::string ret;
|
||||||
for (auto ¶m : m) ret += param.first + " = " + param.second + "\n";
|
for (auto ¶m : m)
|
||||||
|
ret += param.first + " = " + param.second + "\n";
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1117,6 +1117,14 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||||||
((ConfigOptionFloat, material_correction_y))
|
((ConfigOptionFloat, material_correction_y))
|
||||||
((ConfigOptionFloat, material_correction_z))
|
((ConfigOptionFloat, material_correction_z))
|
||||||
((ConfigOptionEnum<SLAMaterialSpeed>, material_print_speed))
|
((ConfigOptionEnum<SLAMaterialSpeed>, material_print_speed))
|
||||||
|
((ConfigOptionFloatNullable, material_ow_support_pillar_diameter))
|
||||||
|
((ConfigOptionFloatNullable, material_ow_branchingsupport_pillar_diameter))
|
||||||
|
((ConfigOptionFloatNullable, material_ow_support_head_front_diameter))
|
||||||
|
((ConfigOptionFloatNullable, material_ow_branchingsupport_head_front_diameter))
|
||||||
|
((ConfigOptionFloatNullable, material_ow_support_head_penetration))
|
||||||
|
((ConfigOptionFloatNullable, material_ow_branchingsupport_head_penetration))
|
||||||
|
((ConfigOptionFloatNullable, material_ow_support_head_width))
|
||||||
|
((ConfigOptionFloatNullable, material_ow_branchingsupport_head_width))
|
||||||
)
|
)
|
||||||
|
|
||||||
PRINT_CONFIG_CLASS_DEFINE(
|
PRINT_CONFIG_CLASS_DEFINE(
|
||||||
|
@ -203,14 +203,24 @@ std::vector<ObjectID> SLAPrint::print_object_ids() const
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
static t_config_option_keys print_config_diffs(const SLAPrintObjectConfig ¤t_config,
|
static t_config_option_keys print_config_diffs(const StaticPrintConfig ¤t_config,
|
||||||
const DynamicPrintConfig &new_full_config,
|
const DynamicPrintConfig &new_full_config,
|
||||||
DynamicPrintConfig &material_overrides)
|
DynamicPrintConfig &material_overrides)
|
||||||
{
|
{
|
||||||
using namespace std::string_view_literals;
|
using namespace std::string_view_literals;
|
||||||
|
|
||||||
static const constexpr StaticSet overriden_keys = {
|
static const constexpr StaticSet overriden_keys = {
|
||||||
"support_pillar_diameter"sv
|
"support_head_front_diameter"sv,
|
||||||
|
"support_head_penetration"sv,
|
||||||
|
"support_head_width"sv,
|
||||||
|
"support_pillar_diameter"sv,
|
||||||
|
"branchingsupport_head_front_diameter"sv,
|
||||||
|
"branchingsupport_head_penetration"sv,
|
||||||
|
"branchingsupport_head_width"sv,
|
||||||
|
"branchingsupport_pillar_diameter"sv,
|
||||||
|
"support_points_density_relative"sv,
|
||||||
|
"relative_correction"sv,
|
||||||
|
"elefant_foot_compensation"sv,
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr auto material_ow_prefix = "material_ow_";
|
static constexpr auto material_ow_prefix = "material_ow_";
|
||||||
@ -260,12 +270,15 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con
|
|||||||
config.option("printer_settings_id", true);
|
config.option("printer_settings_id", true);
|
||||||
config.option("physical_printer_settings_id", true);
|
config.option("physical_printer_settings_id", true);
|
||||||
// Collect changes to print config.
|
// Collect changes to print config.
|
||||||
|
DynamicPrintConfig mat_overrides;
|
||||||
t_config_option_keys print_diff = m_print_config.diff(config);
|
t_config_option_keys print_diff = m_print_config.diff(config);
|
||||||
t_config_option_keys printer_diff = m_printer_config.diff(config);
|
t_config_option_keys printer_diff = print_config_diffs(m_printer_config, config, mat_overrides);
|
||||||
t_config_option_keys material_diff = m_material_config.diff(config);
|
t_config_option_keys material_diff = m_material_config.diff(config);
|
||||||
t_config_option_keys object_diff = m_default_object_config.diff(config);
|
t_config_option_keys object_diff = print_config_diffs(m_default_object_config, config, mat_overrides);
|
||||||
t_config_option_keys placeholder_parser_diff = m_placeholder_parser.config_diff(config);
|
t_config_option_keys placeholder_parser_diff = m_placeholder_parser.config_diff(config);
|
||||||
|
|
||||||
|
config.apply(mat_overrides, true);
|
||||||
|
|
||||||
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
|
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
|
||||||
unsigned int apply_status = APPLY_STATUS_UNCHANGED;
|
unsigned int apply_status = APPLY_STATUS_UNCHANGED;
|
||||||
auto update_apply_status = [&apply_status](bool invalidated)
|
auto update_apply_status = [&apply_status](bool invalidated)
|
||||||
@ -838,17 +851,17 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
|
|||||||
"bottle_volume"sv,
|
"bottle_volume"sv,
|
||||||
"bottle_weight"sv,
|
"bottle_weight"sv,
|
||||||
"material_density"sv,
|
"material_density"sv,
|
||||||
// "material_ow_support_pillar_diameter"sv,
|
"material_ow_support_pillar_diameter"sv,
|
||||||
// "material_ow_support_head_front_diameter"sv,
|
"material_ow_support_head_front_diameter"sv,
|
||||||
// "material_ow_support_head_penetration"sv,
|
"material_ow_support_head_penetration"sv,
|
||||||
// "material_ow_support_head_width"sv,
|
"material_ow_support_head_width"sv,
|
||||||
// "material_ow_branchingsupport_pillar_diameter"sv,
|
"material_ow_branchingsupport_pillar_diameter"sv,
|
||||||
// "material_ow_branchingsupport_head_front_diameter"sv,
|
"material_ow_branchingsupport_head_front_diameter"sv,
|
||||||
// "material_ow_branchingsupport_head_penetration"sv,
|
"material_ow_branchingsupport_head_penetration"sv,
|
||||||
// "material_ow_branchingsupport_head_width"sv,
|
"material_ow_branchingsupport_head_width"sv,
|
||||||
// "material_ow_elefant_foot_compensation"sv,
|
"material_ow_elefant_foot_compensation"sv,
|
||||||
// "material_ow_support_points_density_relative"sv,
|
"material_ow_support_points_density_relative"sv,
|
||||||
// "material_ow_relative_correction"sv
|
"material_ow_relative_correction"sv
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<SLAPrintStep> steps;
|
std::vector<SLAPrintStep> steps;
|
||||||
|
@ -868,7 +868,11 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
|
|||||||
switch (opt->type)
|
switch (opt->type)
|
||||||
{
|
{
|
||||||
case coFloat:
|
case coFloat:
|
||||||
ret = double_to_string(config.option<ConfigOptionFloatNullable>(opt_key)->value);
|
if (config.option(opt_key)->is_nil())
|
||||||
|
ret = _L("N/A");
|
||||||
|
else
|
||||||
|
ret = double_to_string(config.option<ConfigOptionFloatNullable>(opt_key)->value);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case coInt:
|
case coInt:
|
||||||
ret = config.option<ConfigOptionIntNullable>(opt_key)->value;
|
ret = config.option<ConfigOptionIntNullable>(opt_key)->value;
|
||||||
|
@ -5581,24 +5581,24 @@ void TabSLAMaterial::update_line_with_near_label_widget(ConfigOptionsGroupShp op
|
|||||||
if (optgroup->title == "Support head" || optgroup->title == "Support pillar") {
|
if (optgroup->title == "Support head" || optgroup->title == "Support pillar") {
|
||||||
for (auto& prefix : { "", "branching" }) {
|
for (auto& prefix : { "", "branching" }) {
|
||||||
std::string opt_key = preprefix + prefix + key;
|
std::string opt_key = preprefix + prefix + key;
|
||||||
is_checked &= !m_config->option(opt_key)->is_nil();
|
is_checked = !m_config->option(opt_key)->is_nil();
|
||||||
opt_keys.push_back(opt_key);
|
opt_keys.push_back(opt_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (key == "relative_correction") {
|
else if (key == "relative_correction") {
|
||||||
for (auto& axis : { "x", "y", "z" }) {
|
for (auto& axis : { "x", "y", "z" }) {
|
||||||
std::string opt_key = preprefix + key + "_" + char(axis[0]);
|
std::string opt_key = preprefix + key + "_" + char(axis[0]);
|
||||||
is_checked &= !m_config->option(opt_key)->is_nil();
|
is_checked = !m_config->option(opt_key)->is_nil();
|
||||||
opt_keys.push_back(opt_key);
|
opt_keys.push_back(opt_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
std::string opt_key = preprefix + key;
|
std::string opt_key = preprefix + key;
|
||||||
is_checked &= !m_config->option(opt_key)->is_nil();
|
is_checked = !m_config->option(opt_key)->is_nil();
|
||||||
opt_keys.push_back(opt_key);
|
opt_keys.push_back(opt_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// m_overrides_options[key]->Enable(is_checked);
|
// m_overrides_options[key]->Enable(is_checked);
|
||||||
|
|
||||||
CheckBox::SetValue(m_overrides_options[key], is_checked);
|
CheckBox::SetValue(m_overrides_options[key], is_checked);
|
||||||
|
|
||||||
@ -5622,22 +5622,25 @@ void TabSLAMaterial::update_material_overrides_page()
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const std::string& key : keys) {
|
for (const std::string& key : keys) {
|
||||||
update_line_with_near_label_widget(*optgroup, key, false);
|
update_line_with_near_label_widget(*optgroup, key);
|
||||||
continue;
|
// // update_line_with_near_label_widget(*optgroup, key, false);
|
||||||
|
|
||||||
bool is_checked{ true };
|
// const static std::string preprefix = "material_ow_";
|
||||||
|
|
||||||
const static std::string preprefix = "material_ow_";
|
// if (title == "Support head" || title == "Support pillar") {
|
||||||
if (title == "Support head" || title == "Support pillar") {
|
|
||||||
for (auto& prefix : { "", "branching" })
|
// for (auto& prefix : { "", "branching" }) {
|
||||||
update_line_with_near_label_widget(*optgroup, preprefix + prefix + key, is_checked);
|
// update_line_with_near_label_widget(*optgroup, preprefix + prefix + key);
|
||||||
}
|
// }
|
||||||
else if (key == "relative_correction") {
|
// }
|
||||||
for (auto& axis : { "x", "y", "z" })
|
// else if (key == "relative_correction") {
|
||||||
update_line_with_near_label_widget(*optgroup, preprefix + key + "_" + char(axis[0]), is_checked);
|
// for (auto& axis : { "x", "y", "z" }) {
|
||||||
}
|
// update_line_with_near_label_widget(*optgroup, preprefix + key + "_" + char(axis[0]));
|
||||||
else
|
// }
|
||||||
update_line_with_near_label_widget(*optgroup, preprefix + key, is_checked);
|
// }
|
||||||
|
// else {
|
||||||
|
// update_line_with_near_label_widget(*optgroup, preprefix + key);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user