mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-11 17:08:58 +08:00
Merge branch 'master' into dk_connect
This commit is contained in:
commit
75f13002ff
@ -115,6 +115,19 @@ struct OverhangAttributes {
|
||||
float proximity_to_curled_lines; //value between 0 and 1
|
||||
};
|
||||
|
||||
inline bool operator==(const OverhangAttributes &lhs, const OverhangAttributes &rhs) {
|
||||
if (std::abs(lhs.start_distance_from_prev_layer - rhs.start_distance_from_prev_layer) > std::numeric_limits<float>::epsilon()) {
|
||||
return false;
|
||||
}
|
||||
if (std::abs(lhs.end_distance_from_prev_layer - rhs.end_distance_from_prev_layer) > std::numeric_limits<float>::epsilon()) {
|
||||
return false;
|
||||
}
|
||||
if (std::abs(lhs.proximity_to_curled_lines - rhs.proximity_to_curled_lines) > std::numeric_limits<float>::epsilon()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct ExtrusionAttributes : ExtrusionFlow
|
||||
{
|
||||
ExtrusionAttributes() = default;
|
||||
@ -132,7 +145,7 @@ struct ExtrusionAttributes : ExtrusionFlow
|
||||
inline bool operator==(const ExtrusionAttributes &lhs, const ExtrusionAttributes &rhs)
|
||||
{
|
||||
return static_cast<const ExtrusionFlow&>(lhs) == static_cast<const ExtrusionFlow&>(rhs) &&
|
||||
lhs.role == rhs.role;
|
||||
lhs.role == rhs.role && lhs.overhang_attributes == rhs.overhang_attributes;
|
||||
}
|
||||
|
||||
class ExtrusionPath : public ExtrusionEntity
|
||||
|
@ -236,26 +236,14 @@ double VisibilityCalculator::get_angle_visibility_modifier(
|
||||
return -angle_smooth_weight;
|
||||
}
|
||||
|
||||
std::vector<Vec2d> extract_points(
|
||||
const Perimeters::Perimeter &perimeter, const Perimeters::PointType point_type
|
||||
) {
|
||||
std::vector<Vec2d> result;
|
||||
for (std::size_t i{0}; i < perimeter.positions.size(); ++i) {
|
||||
if (perimeter.point_types[i] == point_type) {
|
||||
result.push_back(perimeter.positions[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Vec2d> get_starting_positions(const Shells::Shell<> &shell) {
|
||||
const Perimeters::Perimeter &perimeter{shell.front().boundary};
|
||||
|
||||
std::vector<Vec2d> enforcers{extract_points(perimeter, Perimeters::PointType::enforcer)};
|
||||
std::vector<Vec2d> enforcers{Perimeters::extract_points(perimeter, Perimeters::PointType::enforcer)};
|
||||
if (!enforcers.empty()) {
|
||||
return enforcers;
|
||||
}
|
||||
std::vector<Vec2d> common{extract_points(perimeter, Perimeters::PointType::common)};
|
||||
std::vector<Vec2d> common{Perimeters::extract_points(perimeter, Perimeters::PointType::common)};
|
||||
if (!common.empty()) {
|
||||
return common;
|
||||
}
|
||||
|
@ -18,6 +18,9 @@ std::optional<SeamChoice> maybe_choose_seam_point(
|
||||
return seam_choice;
|
||||
}
|
||||
}
|
||||
if (!Perimeters::extract_points(perimeter, point_type).empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
|
@ -185,6 +185,19 @@ inline std::size_t get_layer_count(
|
||||
}
|
||||
return layer_count;
|
||||
}
|
||||
|
||||
inline std::vector<Vec2d> extract_points(
|
||||
const Perimeters::Perimeter &perimeter, const Perimeters::PointType point_type
|
||||
) {
|
||||
std::vector<Vec2d> result;
|
||||
for (std::size_t i{0}; i < perimeter.positions.size(); ++i) {
|
||||
if (perimeter.point_types[i] == point_type) {
|
||||
result.push_back(perimeter.positions[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Seams::Perimeters
|
||||
|
||||
#endif // libslic3r_SeamPerimeters_hpp_
|
||||
|
@ -1173,7 +1173,7 @@ void Print::_make_skirt()
|
||||
|
||||
// Initial offset of the brim inner edge from the object (possible with a support & raft).
|
||||
// The skirt will touch the brim if the brim is extruded.
|
||||
auto distance = float(scale_(m_config.skirt_distance.value) - spacing/2.);
|
||||
auto distance = float(scale_(m_config.skirt_distance.value - spacing/2.));
|
||||
// Draw outlines from outside to inside.
|
||||
// Loop while we have less skirts than required or any extruder hasn't reached the min length if any.
|
||||
std::vector<coordf_t> extruded_length(extruders.size(), 0.);
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "Utils.hpp"
|
||||
|
||||
|
@ -1560,7 +1560,7 @@ TriangleSelector::TriangleSplittingData TriangleSelector::serialize() const {
|
||||
} else {
|
||||
// In case this is leaf, we better save information about its state.
|
||||
int n = int(tr.get_state());
|
||||
if (n < static_cast<size_t>(TriangleStateType::Count))
|
||||
if (n < static_cast<int>(TriangleStateType::Count))
|
||||
data.used_states[n] = true;
|
||||
|
||||
if (n >= 3) {
|
||||
|
@ -203,6 +203,10 @@ void DSForLayers::draw_ticks(const ImRect& slideable_region)
|
||||
|
||||
if (ImGui::IsMouseHoveringRect(tick_hover_box.Min, tick_hover_box.Max)) {
|
||||
ImGui::RenderFrame(tick_hover_box.Min, tick_hover_box.Max, tick_hovered_clr, false);
|
||||
if (tick_it->type == ColorChange || tick_it->type == ToolChange) {
|
||||
m_focus = fiTick;
|
||||
ImGuiPureWrap::tooltip(get_tooltip(tick_it->tick), ImGui::GetFontSize() * 20.f);
|
||||
}
|
||||
break;
|
||||
}
|
||||
++tick_it;
|
||||
@ -228,22 +232,22 @@ void DSForLayers::draw_ticks(const ImRect& slideable_region)
|
||||
bool activate_this_tick = false;
|
||||
if (tick_it == active_tick_it && m_allow_editing) {
|
||||
// delete tick
|
||||
if (render_button(ImGui::RemoveTick, ImGui::RemoveTickHovered, btn_label, icon_pos, m_ctrl.IsActiveHigherThumb() ? fiHigherThumb : fiLowerThumb, tick_it->tick)) {
|
||||
if (render_button(ImGui::RemoveTick, ImGui::RemoveTickHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick)) {
|
||||
m_ticks.ticks.erase(tick_it);
|
||||
process_ticks_changed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (m_draw_mode != dmRegular)// if we have non-regular draw mode, all ticks should be marked with error icon
|
||||
activate_this_tick = render_button(ImGui::ErrorTick, ImGui::ErrorTickHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick);
|
||||
activate_this_tick = render_button(ImGui::ErrorTick, ImGui::ErrorTickHovered, btn_label, icon_pos, fiTick, tick_it->tick);
|
||||
else if (tick_it->type == ColorChange || tick_it->type == ToolChange) {
|
||||
if (m_ticks.is_conflict_tick(*tick_it, m_mode, m_values[tick_it->tick]))
|
||||
activate_this_tick = render_button(ImGui::ErrorTick, ImGui::ErrorTickHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick);
|
||||
activate_this_tick = render_button(ImGui::ErrorTick, ImGui::ErrorTickHovered, btn_label, icon_pos, fiTick, tick_it->tick);
|
||||
}
|
||||
else if (tick_it->type == CustomGCode::PausePrint)
|
||||
activate_this_tick = render_button(ImGui::PausePrint, ImGui::PausePrintHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick);
|
||||
activate_this_tick = render_button(ImGui::PausePrint, ImGui::PausePrintHovered, btn_label, icon_pos, fiTick, tick_it->tick);
|
||||
else
|
||||
activate_this_tick = render_button(ImGui::EditGCode, ImGui::EditGCodeHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick);
|
||||
activate_this_tick = render_button(ImGui::EditGCode, ImGui::EditGCodeHovered, btn_label, icon_pos, fiTick, tick_it->tick);
|
||||
|
||||
if (activate_this_tick) {
|
||||
m_ctrl.IsActiveHigherThumb() ? SetHigherPos(tick_it->tick) : SetLowerPos(tick_it->tick);
|
||||
@ -297,10 +301,14 @@ void DSForLayers::draw_colored_band(const ImRect& groove, const ImRect& slideabl
|
||||
ImRect main_band = ImRect(blank_rect);
|
||||
main_band.Expand(blank_padding);
|
||||
|
||||
auto draw_band = [](const ImU32& clr, const ImRect& band_rc) {
|
||||
auto draw_band = [this](const ImU32& clr, const ImRect& band_rc) {
|
||||
ImGui::RenderFrame(band_rc.Min, band_rc.Max, clr, false, band_rc.GetWidth() * 0.5);
|
||||
//cover round corner
|
||||
ImGui::RenderFrame(ImVec2(band_rc.Min.x, band_rc.Max.y - band_rc.GetWidth() * 0.5), band_rc.Max, clr, false);
|
||||
|
||||
// add tooltip
|
||||
if (ImGui::IsMouseHoveringRect(band_rc.Min, band_rc.Max))
|
||||
m_focus = fiColorBand;
|
||||
};
|
||||
|
||||
auto draw_main_band = [&main_band](const ImU32& clr) {
|
||||
@ -315,6 +323,8 @@ void DSForLayers::draw_colored_band(const ImRect& groove, const ImRect& slideabl
|
||||
|
||||
static float tick_pos;
|
||||
std::set<TickCode>::const_iterator tick_it = m_ticks.ticks.begin();
|
||||
|
||||
int rclicked_tick = -1;
|
||||
while (tick_it != m_ticks.ticks.end())
|
||||
{
|
||||
//get position from tick
|
||||
@ -337,13 +347,27 @@ void DSForLayers::draw_colored_band(const ImRect& groove, const ImRect& slideabl
|
||||
ImU32 band_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f);
|
||||
if (tick_it->tick == 0)
|
||||
draw_main_band(band_clr);
|
||||
else
|
||||
else {
|
||||
draw_band(band_clr, band_rect);
|
||||
|
||||
ImGuiContext& g = *GImGui;
|
||||
if (ImGui::IsMouseHoveringRect(band_rect.Min, band_rect.Max) &&
|
||||
g.IO.MouseClicked[1] && !m_ctrl.IsRClickOnThumb()) {
|
||||
rclicked_tick = tick_it->tick;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tick_it++;
|
||||
}
|
||||
|
||||
if (m_focus == fiColorBand) {
|
||||
if (rclicked_tick > 0)
|
||||
edit_tick(rclicked_tick);
|
||||
else if (auto tip = get_tooltip(); !tip.empty())
|
||||
ImGuiPureWrap::tooltip(tip, ImGui::GetFontSize() * 20.f);
|
||||
}
|
||||
}
|
||||
|
||||
void DSForLayers::render_menu()
|
||||
@ -361,10 +385,13 @@ void DSForLayers::render_menu()
|
||||
ImGui::OpenPopup("slider_add_tick_menu_popup");
|
||||
else if (m_show_cog_menu)
|
||||
ImGui::OpenPopup("cog_menu_popup");
|
||||
else if (m_show_edit_menu)
|
||||
ImGui::OpenPopup("edit_menu_popup");
|
||||
|
||||
if (can_edit())
|
||||
render_add_tick_menu();
|
||||
render_cog_menu();
|
||||
render_edit_menu();
|
||||
|
||||
ImGui::PopStyleColor(1);
|
||||
ImGui::PopStyleVar(4);
|
||||
@ -373,6 +400,7 @@ void DSForLayers::render_menu()
|
||||
if (context.IO.MouseReleased[0]) {
|
||||
m_show_just_color_change_menu = false;
|
||||
m_show_cog_menu = false;
|
||||
m_show_edit_menu = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,8 +444,10 @@ void DSForLayers::render_add_tick_menu()
|
||||
}
|
||||
}
|
||||
|
||||
void DSForLayers::render_multi_extruders_menu()
|
||||
bool DSForLayers::render_multi_extruders_menu(bool switch_current_code/* = false*/)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
std::vector<std::string> colors;
|
||||
if (m_cb_get_extruder_colors)
|
||||
colors = m_cb_get_extruder_colors();
|
||||
@ -428,7 +458,7 @@ void DSForLayers::render_multi_extruders_menu()
|
||||
const int tick = m_ctrl.GetActivePos();
|
||||
|
||||
if (m_mode == MultiAsSingle) {
|
||||
const std::string menu_name = _u8L("Change extruder");
|
||||
const std::string menu_name = switch_current_code ? _u8L("Switch code to Change extruder") : _u8L("Change extruder");
|
||||
if (ImGuiPureWrap::begin_menu(menu_name.c_str())) {
|
||||
std::array<int, 2> active_extruders = m_ticks.get_active_extruders_for_tick(tick, m_mode);
|
||||
for (int i = 1; i <= extruders_cnt; i++) {
|
||||
@ -439,14 +469,18 @@ void DSForLayers::render_multi_extruders_menu()
|
||||
|
||||
std::array<float, 4> rgba = decode_color_to_float_array(colors[i - 1]);
|
||||
ImU32 icon_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f);
|
||||
if (ImGuiPureWrap::menu_item_with_icon(item_name.c_str(), "", ImVec2(14, 14) * m_scale, icon_clr, false, !is_active_extruder))
|
||||
if (ImGuiPureWrap::menu_item_with_icon(item_name.c_str(), "", ImVec2(14, 14) * m_scale, icon_clr, false, !is_active_extruder)) {
|
||||
add_code_as_tick(ToolChange, i);
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
ImGuiPureWrap::end_menu();
|
||||
}
|
||||
}
|
||||
|
||||
const std::string menu_name = format(_u8L("Add color change (%1%) for:"), gcode(ColorChange));
|
||||
const std::string menu_name = switch_current_code ?
|
||||
format(_u8L("Switch code to Color change (%1%) for:"), gcode(ColorChange)) :
|
||||
format(_u8L("Add color change (%1%) for:"), gcode(ColorChange));
|
||||
if (ImGuiPureWrap::begin_menu(menu_name.c_str())) {
|
||||
std::set<int> used_extruders_for_tick = m_ticks.get_used_extruders_for_tick(tick, m_values[tick]);
|
||||
|
||||
@ -457,12 +491,15 @@ void DSForLayers::render_multi_extruders_menu()
|
||||
if (is_used_extruder)
|
||||
item_name += " (" + _u8L("used") + ")";
|
||||
|
||||
if (ImGuiPureWrap::menu_item_with_icon(item_name.c_str(), ""))
|
||||
if (ImGuiPureWrap::menu_item_with_icon(item_name.c_str(), "")) {
|
||||
add_code_as_tick(ColorChange, i);
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
ImGuiPureWrap::end_menu();
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DSForLayers::render_color_picker()
|
||||
@ -519,6 +556,50 @@ void DSForLayers::render_cog_menu()
|
||||
}
|
||||
}
|
||||
|
||||
void DSForLayers::render_edit_menu()
|
||||
{
|
||||
if (!m_show_edit_menu)
|
||||
return;
|
||||
|
||||
const ImVec2 icon_sz = ImVec2(14, 14);
|
||||
if (m_ticks.has_tick(m_ctrl.GetActivePos()) && ImGui::BeginPopup("edit_menu_popup")) {
|
||||
std::set<TickCode>::iterator it = m_ticks.ticks.find(TickCode{ m_ctrl.GetActivePos()});
|
||||
|
||||
if (it->type == ToolChange) {
|
||||
if (render_multi_extruders_menu(true)) {
|
||||
ImGui::EndPopup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::string edit_item_name = it->type == CustomGCode::ColorChange ? _u8L("Edit color") :
|
||||
it->type == CustomGCode::PausePrint ? _u8L("Edit pause print message") :
|
||||
_u8L("Edit custom G-code");
|
||||
if (ImGuiPureWrap::menu_item_with_icon(edit_item_name.c_str(), "")) {
|
||||
edit_tick();
|
||||
ImGui::EndPopup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (it->type == ColorChange && m_mode == MultiAsSingle) {
|
||||
if (render_multi_extruders_menu(true)) {
|
||||
ImGui::EndPopup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string delete_item_name = it->type == CustomGCode::ColorChange ? _u8L("Delete color change") :
|
||||
it->type == CustomGCode::ToolChange ? _u8L("Delete tool change") :
|
||||
it->type == CustomGCode::PausePrint ? _u8L("Delete pause print") :
|
||||
_u8L("Delete custom G-code");
|
||||
if (ImGuiPureWrap::menu_item_with_icon(delete_item_name.c_str(), ""))
|
||||
delete_current_tick();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
bool DSForLayers::render_button(const wchar_t btn_icon, const wchar_t btn_icon_hovered, const std::string& label_id, const ImVec2& pos, FocusedItem focus, int tick /*= -1*/)
|
||||
{
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_WindowBorderSize, 0);
|
||||
@ -547,6 +628,9 @@ bool DSForLayers::render_button(const wchar_t btn_icon, const wchar_t btn_icon_h
|
||||
ImGui::SetCursorPos(ImVec2(0, 0));
|
||||
const bool ret = m_imgui->image_button(g.HoveredWindow == g.CurrentWindow ? btn_icon_hovered : btn_icon, tooltip, false);
|
||||
|
||||
if (tick > 0 && tick == m_ctrl.GetActivePos() && g.HoveredWindow == g.CurrentWindow && g.IO.MouseClicked[1])
|
||||
m_show_edit_menu = true;
|
||||
|
||||
ImGuiPureWrap::end();
|
||||
|
||||
ImGui::PopStyleColor(2);
|
||||
@ -814,7 +898,7 @@ std::string DSForLayers::get_tooltip(int tick/*=-1*/)
|
||||
"or Set ruler mode")) % "(Shift + G)").str();
|
||||
}
|
||||
if (m_focus == fiColorBand)
|
||||
return m_mode != SingleExtruder ? "" :
|
||||
return m_mode != SingleExtruder || !can_edit() ? "" :
|
||||
_u8L("Edit current color - Right click the colored slider segment");
|
||||
if (m_focus == fiSmartWipeTower)
|
||||
return _u8L("This is wipe tower layer");
|
||||
@ -884,7 +968,7 @@ std::string DSForLayers::get_tooltip(int tick/*=-1*/)
|
||||
};
|
||||
tooltip +=
|
||||
tick_code_it->type == ColorChange ?
|
||||
(m_mode == SingleExtruder ?
|
||||
(m_mode == SingleExtruder && tick_code_it->extruder==1 ?
|
||||
format(_u8L("Color change (\"%1%\")"), gcode(ColorChange)) :
|
||||
format(_u8L("Color change (\"%1%\") for Extruder %2%"), gcode(ColorChange), tick_code_it->extruder)) :
|
||||
tick_code_it->type == CustomGCode::PausePrint ?
|
||||
|
@ -31,8 +31,6 @@ enum FocusedItem {
|
||||
fiCogIcon,
|
||||
fiColorBand,
|
||||
fiActionIcon,
|
||||
fiLowerThumb,
|
||||
fiHigherThumb,
|
||||
fiSmartWipeTower,
|
||||
fiTick
|
||||
};
|
||||
@ -138,6 +136,7 @@ private:
|
||||
bool m_allow_editing { true };
|
||||
bool m_show_estimated_times { false };
|
||||
bool m_show_cog_menu { false };
|
||||
bool m_show_edit_menu { false };
|
||||
|
||||
DrawMode m_draw_mode { dmRegular };
|
||||
Mode m_mode { SingleExtruder };
|
||||
@ -163,6 +162,7 @@ private:
|
||||
void draw_ticks(const ImRect& slideable_region);
|
||||
void render_menu();
|
||||
void render_cog_menu();
|
||||
void render_edit_menu();
|
||||
bool render_button(const wchar_t btn_icon, const wchar_t btn_icon_hovered, const std::string& label_id, const ImVec2& pos, FocusedItem focus, int tick = -1);
|
||||
|
||||
void add_code_as_tick(Type type, int selected_extruder = -1);
|
||||
@ -188,7 +188,7 @@ private:
|
||||
std::string m_selectable_color;
|
||||
|
||||
void render_add_tick_menu();
|
||||
void render_multi_extruders_menu();
|
||||
bool render_multi_extruders_menu(bool switch_current_code = false);
|
||||
bool render_jump_to_window(const ImVec2& pos, double* active_value, double min_z, double max_z);
|
||||
void render_color_picker();
|
||||
|
||||
|
@ -1048,7 +1048,7 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
|
||||
this->SetFont(this->normal_font());
|
||||
|
||||
#ifdef _WIN32
|
||||
if (m_tmp_top_bar->IsShown())
|
||||
if (m_tmp_top_bar && m_tmp_top_bar->IsShown())
|
||||
m_tmp_top_bar->Rescale();
|
||||
m_tabpanel->Rescale();
|
||||
#endif
|
||||
|
@ -35,7 +35,6 @@
|
||||
#include <pwd.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/filesystem/convenience.hpp>
|
||||
#include <boost/process.hpp>
|
||||
#endif
|
||||
|
||||
|
@ -832,8 +832,14 @@ std::pair<BoundingBoxf3, Transform3d> Selection::get_bounding_box_in_reference_s
|
||||
const GLVolume& vol = *get_volume(id);
|
||||
const Transform3d vol_world_rafo = vol.world_matrix();
|
||||
const TriangleMesh* mesh = vol.convex_hull();
|
||||
if (mesh == nullptr)
|
||||
if (mesh == nullptr) {
|
||||
// workaround to avoid a crash, see spe-2295 -> Crash when re-cutting with dowel connectors
|
||||
const int obj_id = vol.object_idx();
|
||||
const int vol_id = vol.volume_idx();
|
||||
if (m_model->objects[obj_id]->volumes.size() <= vol_id)
|
||||
continue;
|
||||
mesh = &m_model->objects[vol.object_idx()]->volumes[vol.volume_idx()]->mesh();
|
||||
}
|
||||
assert(mesh != nullptr);
|
||||
for (const stl_vertex& v : mesh->its.vertices) {
|
||||
const Vec3d world_v = vol_world_rafo * v.cast<double>();
|
||||
|
Loading…
x
Reference in New Issue
Block a user