Allow the user to switch between visualizing original or processed volumes in 3D scene after slicing using SLA printers
This commit is contained in:
parent
47aacbdc5e
commit
797dd1197e
@ -613,10 +613,28 @@ void GLVolumeCollection::load_object_auxiliary(
|
|||||||
if (convex_hull.has_value())
|
if (convex_hull.has_value())
|
||||||
v.set_convex_hull(*convex_hull);
|
v.set_convex_hull(*convex_hull);
|
||||||
v.is_modifier = false;
|
v.is_modifier = false;
|
||||||
v.shader_outside_printer_detection_enabled = (step == slaposSupportTree);
|
v.shader_outside_printer_detection_enabled = (step == slaposSupportTree || step == slaposDrillHoles);
|
||||||
v.set_instance_transformation(model_instance.get_transformation());
|
v.set_instance_transformation(model_instance.get_transformation());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (milestone == SLAPrintObjectStep::slaposDrillHoles) {
|
||||||
|
if (print_object->get_parts_to_slice().size() > 1) {
|
||||||
|
// Get the mesh.
|
||||||
|
TriangleMesh backend_mesh;
|
||||||
|
std::shared_ptr<const indexed_triangle_set> preview_mesh_ptr = print_object->get_mesh_to_print();
|
||||||
|
if (preview_mesh_ptr != nullptr)
|
||||||
|
backend_mesh = TriangleMesh(*preview_mesh_ptr);
|
||||||
|
if (!backend_mesh.empty()) {
|
||||||
|
backend_mesh.transform(mesh_trafo_inv);
|
||||||
|
TriangleMesh convex_hull = backend_mesh.convex_hull_3d();
|
||||||
|
for (const std::pair<size_t, size_t>& instance_idx : instances) {
|
||||||
|
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
|
||||||
|
add_volume(obj_idx, (int)instance_idx.first, model_instance, slaposDrillHoles, backend_mesh, GLVolume::MODEL_COLOR[0], convex_hull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the support mesh.
|
// Get the support mesh.
|
||||||
if (milestone == SLAPrintObjectStep::slaposSupportTree) {
|
if (milestone == SLAPrintObjectStep::slaposSupportTree) {
|
||||||
TriangleMesh supports_mesh = print_object->support_mesh();
|
TriangleMesh supports_mesh = print_object->support_mesh();
|
||||||
@ -924,7 +942,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (GLVolume* volume : volumes) {
|
for (GLVolume* volume : volumes) {
|
||||||
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0)
|
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->is_sla_pad() || volume->is_sla_support())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int extruder_id = volume->extruder_id - 1;
|
int extruder_id = volume->extruder_id - 1;
|
||||||
|
@ -1072,6 +1072,107 @@ void GLCanvas3D::load_arrange_settings()
|
|||||||
m_arrange_settings_fff_seq_print.alignment = arr_alignment ;
|
m_arrange_settings_fff_seq_print.alignment = arr_alignment ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int processed_object_idx(const Model& model, const SLAPrint& sla_print, const GLVolumePtrs& volumes)
|
||||||
|
{
|
||||||
|
for (const GLVolume* v : volumes) {
|
||||||
|
if (v->volume_idx() == -(int)slaposDrillHoles) {
|
||||||
|
const int mo_idx = v->object_idx();
|
||||||
|
const ModelObject* model_object = (mo_idx < (int)model.objects.size()) ? model.objects[mo_idx] : nullptr;
|
||||||
|
if (model_object != nullptr && model_object->instances[v->instance_idx()]->is_printable()) {
|
||||||
|
const SLAPrintObject* print_object = sla_print.get_print_object_by_model_object_id(model_object->id());
|
||||||
|
if (print_object != nullptr && print_object->get_parts_to_slice().size() > 1)
|
||||||
|
return mo_idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
GLCanvas3D::ESLAViewType GLCanvas3D::SLAView::detect_type(const GLVolumePtrs& volumes)
|
||||||
|
{
|
||||||
|
m_type = ESLAViewType::Original;
|
||||||
|
if (m_allow_type_detection) {
|
||||||
|
for (const GLVolume* v : volumes) {
|
||||||
|
if (v->volume_idx() == -(int)slaposDrillHoles) {
|
||||||
|
m_type = ESLAViewType::Processed;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_parent.set_sla_view_type(m_type);
|
||||||
|
m_allow_type_detection = false;
|
||||||
|
return m_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLCanvas3D::SLAView::set_type(ESLAViewType type) {
|
||||||
|
if (m_type != type) {
|
||||||
|
m_type = type;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCanvas3D::SLAView::update_volumes(GLVolumePtrs& volumes)
|
||||||
|
{
|
||||||
|
const SLAPrint* sla_print = m_parent.sla_print();
|
||||||
|
const int mo_idx = (sla_print != nullptr) ? processed_object_idx(*m_parent.get_model(), *sla_print, volumes) : -1;
|
||||||
|
const bool show_processed = m_type == ESLAViewType::Processed && mo_idx != -1;
|
||||||
|
|
||||||
|
auto show = [show_processed](const GLVolume& v) {
|
||||||
|
return show_processed ? v.volume_idx() < 0 : v.volume_idx() != -(int)slaposDrillHoles;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<SceneRaycasterItem>>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume);
|
||||||
|
|
||||||
|
for (GLVolume* v : volumes) {
|
||||||
|
v->is_active = v->object_idx() != mo_idx || show(*v);
|
||||||
|
auto it = std::find_if(raycasters->begin(), raycasters->end(), [v](std::shared_ptr<SceneRaycasterItem> item) { return item->get_raycaster() == v->mesh_raycaster.get(); });
|
||||||
|
if (it != raycasters->end())
|
||||||
|
(*it)->set_active(v->is_active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCanvas3D::SLAView::render_switch_button()
|
||||||
|
{
|
||||||
|
const SLAPrint* sla_print = m_parent.sla_print();
|
||||||
|
const int mo_idx = (sla_print != nullptr) ? processed_object_idx(*m_parent.get_model(), *sla_print, m_parent.get_volumes().volumes) : -1;
|
||||||
|
if (mo_idx == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const BoundingBoxf ss_box = m_parent.get_selection().get_screen_space_bounding_box();
|
||||||
|
if (ss_box.defined) {
|
||||||
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||||
|
ImGui::SetNextWindowPos(ImVec2((float)ss_box.max.x(), (float)ss_box.center().y()), ImGuiCond_Always, ImVec2(0.0, 0.5));
|
||||||
|
imgui.begin(std::string("SLAViewSwitch"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration);
|
||||||
|
const wxString btn_text = (m_type == ESLAViewType::Original) ? _L("Processed") : _L("Original");
|
||||||
|
if (imgui.button(btn_text)) {
|
||||||
|
switch (m_type)
|
||||||
|
{
|
||||||
|
case ESLAViewType::Original: { m_parent.set_sla_view_type(ESLAViewType::Processed); break; }
|
||||||
|
case ESLAViewType::Processed: { m_parent.set_sla_view_type(ESLAViewType::Original); break; }
|
||||||
|
default: { assert(false); break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imgui.end();
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//void GLCanvas3D::SLAView::render_debug_window()
|
||||||
|
//{
|
||||||
|
// ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||||
|
// imgui.begin(std::string("SLAView"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize);
|
||||||
|
// imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Type:");
|
||||||
|
// ImGui::SameLine();
|
||||||
|
// const std::string text = (m_type == ESLAViewType::Original) ? "Original" : "Processed";
|
||||||
|
// imgui.text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), text);
|
||||||
|
// imgui.end();
|
||||||
|
//}
|
||||||
|
|
||||||
PrinterTechnology GLCanvas3D::current_printer_technology() const
|
PrinterTechnology GLCanvas3D::current_printer_technology() const
|
||||||
{
|
{
|
||||||
return m_process->current_printer_technology();
|
return m_process->current_printer_technology();
|
||||||
@ -1113,6 +1214,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
|
|||||||
, m_render_sla_auxiliaries(true)
|
, m_render_sla_auxiliaries(true)
|
||||||
, m_labels(*this)
|
, m_labels(*this)
|
||||||
, m_slope(m_volumes)
|
, m_slope(m_volumes)
|
||||||
|
, m_sla_view(*this)
|
||||||
{
|
{
|
||||||
if (m_canvas != nullptr) {
|
if (m_canvas != nullptr) {
|
||||||
m_timer.SetOwner(m_canvas);
|
m_timer.SetOwner(m_canvas);
|
||||||
@ -1642,6 +1744,13 @@ void GLCanvas3D::render()
|
|||||||
GLModel::render_statistics();
|
GLModel::render_statistics();
|
||||||
#endif // ENABLE_GLMODEL_STATISTICS
|
#endif // ENABLE_GLMODEL_STATISTICS
|
||||||
|
|
||||||
|
if (wxGetApp().plater()->is_view3D_shown() && current_printer_technology() == ptSLA) {
|
||||||
|
const GLGizmosManager::EType type = m_gizmos.get_current_type();
|
||||||
|
if (type == GLGizmosManager::EType::Undefined)
|
||||||
|
m_sla_view.render_switch_button();
|
||||||
|
// m_sla_view.render_debug_window();
|
||||||
|
}
|
||||||
|
|
||||||
std::string tooltip;
|
std::string tooltip;
|
||||||
|
|
||||||
// Negative coordinate means out of the window, likely because the window was deactivated.
|
// Negative coordinate means out of the window, likely because the window was deactivated.
|
||||||
@ -2114,6 +2223,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
|||||||
if (!instances[istep].empty())
|
if (!instances[istep].empty())
|
||||||
m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp);
|
m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_sla_view.detect_type(m_volumes.volumes) == ESLAViewType::Processed)
|
||||||
|
update_object_list = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shift-up all volumes of the object so that it has the right elevation with respect to the print bed
|
// Shift-up all volumes of the object so that it has the right elevation with respect to the print bed
|
||||||
@ -2169,6 +2281,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
|||||||
else
|
else
|
||||||
m_selection.volumes_changed(map_glvolume_old_to_new);
|
m_selection.volumes_changed(map_glvolume_old_to_new);
|
||||||
|
|
||||||
|
if (printer_technology == ptSLA)
|
||||||
|
m_sla_view.update_volumes(m_volumes.volumes);
|
||||||
|
|
||||||
m_gizmos.update_data();
|
m_gizmos.update_data();
|
||||||
m_gizmos.refresh_on_off_state();
|
m_gizmos.refresh_on_off_state();
|
||||||
|
|
||||||
|
@ -468,6 +468,12 @@ public:
|
|||||||
int alignment = 0;
|
int alignment = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ESLAViewType
|
||||||
|
{
|
||||||
|
Original,
|
||||||
|
Processed
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
wxGLCanvas* m_canvas;
|
wxGLCanvas* m_canvas;
|
||||||
wxGLContext* m_context;
|
wxGLContext* m_context;
|
||||||
@ -547,6 +553,26 @@ private:
|
|||||||
bool m_tooltip_enabled{ true };
|
bool m_tooltip_enabled{ true };
|
||||||
Slope m_slope;
|
Slope m_slope;
|
||||||
|
|
||||||
|
class SLAView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit SLAView(GLCanvas3D& parent) : m_parent(parent) {}
|
||||||
|
void allow_type_detection(bool allow) { m_allow_type_detection = allow; }
|
||||||
|
ESLAViewType detect_type(const GLVolumePtrs& volumes);
|
||||||
|
ESLAViewType get_type() const { return m_type; }
|
||||||
|
bool set_type(ESLAViewType type);
|
||||||
|
void update_volumes(GLVolumePtrs& volumes);
|
||||||
|
void render_switch_button();
|
||||||
|
// void render_debug_window();
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLCanvas3D& m_parent;
|
||||||
|
ESLAViewType m_type{ ESLAViewType::Original };
|
||||||
|
bool m_allow_type_detection{ false };
|
||||||
|
};
|
||||||
|
|
||||||
|
SLAView m_sla_view;
|
||||||
|
|
||||||
ArrangeSettings m_arrange_settings_fff, m_arrange_settings_sla,
|
ArrangeSettings m_arrange_settings_fff, m_arrange_settings_sla,
|
||||||
m_arrange_settings_fff_seq_print;
|
m_arrange_settings_fff_seq_print;
|
||||||
|
|
||||||
@ -656,7 +682,7 @@ private:
|
|||||||
GLModel m_background;
|
GLModel m_background;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed);
|
GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed);
|
||||||
~GLCanvas3D();
|
~GLCanvas3D();
|
||||||
|
|
||||||
bool is_initialized() const { return m_initialized; }
|
bool is_initialized() const { return m_initialized; }
|
||||||
@ -962,6 +988,22 @@ public:
|
|||||||
|
|
||||||
std::pair<SlicingParameters, const std::vector<double>> get_layers_height_data(int object_id);
|
std::pair<SlicingParameters, const std::vector<double>> get_layers_height_data(int object_id);
|
||||||
|
|
||||||
|
void set_sla_view_type(ESLAViewType type) {
|
||||||
|
if (type == ESLAViewType::Processed) {
|
||||||
|
assert(!m_selection.is_empty());
|
||||||
|
const GLVolume* v = m_selection.get_first_volume();
|
||||||
|
m_selection.add_instance(v->object_idx(), v->instance_idx());
|
||||||
|
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_dirty = type != m_sla_view.get_type();
|
||||||
|
|
||||||
|
if (m_sla_view.set_type(type))
|
||||||
|
m_sla_view.update_volumes(m_volumes.volumes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void allow_sla_view_type_detection(bool allow) { m_sla_view.allow_type_detection(allow); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _is_shown_on_screen() const;
|
bool _is_shown_on_screen() const;
|
||||||
|
|
||||||
|
@ -747,6 +747,10 @@ void ObjectList::selection_changed()
|
|||||||
wxGetApp().obj_layers()->update_scene_from_editor_selection();
|
wxGetApp().obj_layers()->update_scene_from_editor_selection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (type & itVolume) {
|
||||||
|
if (printer_technology() == ptSLA)
|
||||||
|
wxGetApp().plater()->canvas3D()->set_sla_view_type(GLCanvas3D::ESLAViewType::Original);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
part_selection_changed();
|
part_selection_changed();
|
||||||
|
@ -64,7 +64,6 @@ void GLGizmoSlaSupports::data_changed()
|
|||||||
|
|
||||||
// If we triggered autogeneration before, check backend and fetch results if they are there
|
// If we triggered autogeneration before, check backend and fetch results if they are there
|
||||||
if (mo) {
|
if (mo) {
|
||||||
m_c->instances_hider()->set_hide_full_scene(true);
|
|
||||||
const SLAPrintObject* po = m_c->selection_info()->print_object();
|
const SLAPrintObject* po = m_c->selection_info()->print_object();
|
||||||
const int required_step = get_min_sla_print_object_step();
|
const int required_step = get_min_sla_print_object_step();
|
||||||
auto last_comp_step = static_cast<int>(po->last_completed_step());
|
auto last_comp_step = static_cast<int>(po->last_completed_step());
|
||||||
@ -83,6 +82,8 @@ void GLGizmoSlaSupports::data_changed()
|
|||||||
register_point_raycasters_for_picking();
|
register_point_raycasters_for_picking();
|
||||||
else
|
else
|
||||||
update_point_raycasters_for_picking_transform();
|
update_point_raycasters_for_picking_transform();
|
||||||
|
|
||||||
|
m_c->instances_hider()->set_hide_full_scene(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// m_parent.toggle_model_objects_visibility(false);
|
// m_parent.toggle_model_objects_visibility(false);
|
||||||
|
@ -969,6 +969,8 @@ bool GLGizmosManager::activate_gizmo(EType type)
|
|||||||
return false; // gizmo refused to be turned on.
|
return false; // gizmo refused to be turned on.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_parent.set_sla_view_type(GLCanvas3D::ESLAViewType::Original);
|
||||||
|
|
||||||
new_gizmo.register_raycasters_for_picking();
|
new_gizmo.register_raycasters_for_picking();
|
||||||
|
|
||||||
// sucessful activation of gizmo
|
// sucessful activation of gizmo
|
||||||
|
@ -3448,8 +3448,10 @@ unsigned int Plater::priv::update_restart_background_process(bool force_update_s
|
|||||||
{
|
{
|
||||||
// bitmask of UpdateBackgroundProcessReturnState
|
// bitmask of UpdateBackgroundProcessReturnState
|
||||||
unsigned int state = this->update_background_process(false);
|
unsigned int state = this->update_background_process(false);
|
||||||
if (force_update_scene || (state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0)
|
if (force_update_scene || (state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0) {
|
||||||
|
view3D->get_canvas3d()->allow_sla_view_type_detection(true);
|
||||||
view3D->reload_scene(false);
|
view3D->reload_scene(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (force_update_preview)
|
if (force_update_preview)
|
||||||
this->preview->reload_print();
|
this->preview->reload_print();
|
||||||
|
@ -890,6 +890,43 @@ std::pair<BoundingBoxf3, Transform3d> Selection::get_bounding_box_in_reference_s
|
|||||||
}
|
}
|
||||||
#endif // ENABLE_WORLD_COORDINATE
|
#endif // ENABLE_WORLD_COORDINATE
|
||||||
|
|
||||||
|
BoundingBoxf Selection::get_screen_space_bounding_box()
|
||||||
|
{
|
||||||
|
BoundingBoxf ss_box;
|
||||||
|
if (!is_empty()) {
|
||||||
|
const auto& [box, box_trafo] = get_bounding_box_in_current_reference_system();
|
||||||
|
|
||||||
|
// vertices
|
||||||
|
std::vector<Vec3d> vertices = {
|
||||||
|
{ box.min.x(), box.min.y(), box.min.z() },
|
||||||
|
{ box.max.x(), box.min.y(), box.min.z() },
|
||||||
|
{ box.max.x(), box.max.y(), box.min.z() },
|
||||||
|
{ box.min.x(), box.max.y(), box.min.z() },
|
||||||
|
{ box.min.x(), box.min.y(), box.max.z() },
|
||||||
|
{ box.max.x(), box.min.y(), box.max.z() },
|
||||||
|
{ box.max.x(), box.max.y(), box.max.z() },
|
||||||
|
{ box.min.x(), box.max.y(), box.max.z() }
|
||||||
|
};
|
||||||
|
|
||||||
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||||
|
const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix();
|
||||||
|
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||||
|
|
||||||
|
const double half_w = 0.5 * double(viewport[2]);
|
||||||
|
const double h = double(viewport[3]);
|
||||||
|
const double half_h = 0.5 * h;
|
||||||
|
for (const Vec3d& v : vertices) {
|
||||||
|
const Vec3d world = box_trafo * v;
|
||||||
|
const Vec4d clip = projection_view_matrix * Vec4d(world.x(), world.y(), world.z(), 1.0);
|
||||||
|
const Vec3d ndc = Vec3d(clip.x(), clip.y(), clip.z()) / clip.w();
|
||||||
|
const Vec2d ss = Vec2d(half_w * ndc.x() + double(viewport[0]) + half_w, h - (half_h * ndc.y() + double(viewport[1]) + half_h));
|
||||||
|
ss_box.merge(ss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss_box;
|
||||||
|
}
|
||||||
|
|
||||||
void Selection::setup_cache()
|
void Selection::setup_cache()
|
||||||
{
|
{
|
||||||
if (!m_valid)
|
if (!m_valid)
|
||||||
|
@ -400,6 +400,9 @@ public:
|
|||||||
std::pair<BoundingBoxf3, Transform3d> get_bounding_box_in_reference_system(ECoordinatesType type) const;
|
std::pair<BoundingBoxf3, Transform3d> get_bounding_box_in_reference_system(ECoordinatesType type) const;
|
||||||
#endif // ENABLE_WORLD_COORDINATE
|
#endif // ENABLE_WORLD_COORDINATE
|
||||||
|
|
||||||
|
// Returns the screen space bounding box
|
||||||
|
BoundingBoxf get_screen_space_bounding_box();
|
||||||
|
|
||||||
void setup_cache();
|
void setup_cache();
|
||||||
|
|
||||||
#if ENABLE_WORLD_COORDINATE
|
#if ENABLE_WORLD_COORDINATE
|
||||||
|
Loading…
Reference in New Issue
Block a user