Vojtech's improvements in the SLA preview cutting dialog.
This commit is contained in:
parent
678c513cb9
commit
4a210aeecf
@ -43,11 +43,21 @@ typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> stl_normal;
|
|||||||
static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect");
|
static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect");
|
||||||
static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect");
|
static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect");
|
||||||
|
|
||||||
typedef struct {
|
struct stl_facet {
|
||||||
stl_normal normal;
|
stl_normal normal;
|
||||||
stl_vertex vertex[3];
|
stl_vertex vertex[3];
|
||||||
char extra[2];
|
char extra[2];
|
||||||
} stl_facet;
|
|
||||||
|
stl_facet rotated(const Eigen::Quaternion<float, Eigen::DontAlign> &rot) {
|
||||||
|
stl_facet out;
|
||||||
|
out.normal = rot * this->normal;
|
||||||
|
out.vertex[0] = rot * this->vertex[0];
|
||||||
|
out.vertex[1] = rot * this->vertex[1];
|
||||||
|
out.vertex[2] = rot * this->vertex[2];
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#define SIZEOF_STL_FACET 50
|
#define SIZEOF_STL_FACET 50
|
||||||
|
|
||||||
static_assert(offsetof(stl_facet, normal) == 0, "stl_facet.normal has correct offset");
|
static_assert(offsetof(stl_facet, normal) == 0, "stl_facet.normal has correct offset");
|
||||||
|
@ -41,10 +41,12 @@ stl_open(stl_file *stl, const char *file) {
|
|||||||
stl_count_facets(stl, file);
|
stl_count_facets(stl, file);
|
||||||
stl_allocate(stl);
|
stl_allocate(stl);
|
||||||
stl_read(stl, 0, true);
|
stl_read(stl, 0, true);
|
||||||
if (!stl->error) fclose(stl->fp);
|
if (stl->fp != nullptr) {
|
||||||
|
fclose(stl->fp);
|
||||||
|
stl->fp = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
stl_initialize(stl_file *stl) {
|
stl_initialize(stl_file *stl) {
|
||||||
memset(stl, 0, sizeof(stl_file));
|
memset(stl, 0, sizeof(stl_file));
|
||||||
@ -118,7 +120,7 @@ stl_count_facets(stl_file *stl, const char *file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Read the int following the header. This should contain # of facets */
|
/* Read the int following the header. This should contain # of facets */
|
||||||
bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp);
|
bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp) != 0;
|
||||||
#ifndef BOOST_LITTLE_ENDIAN
|
#ifndef BOOST_LITTLE_ENDIAN
|
||||||
// Convert from little endian to big endian.
|
// Convert from little endian to big endian.
|
||||||
stl_internal_reverse_quads((char*)&header_num_facets, 4);
|
stl_internal_reverse_quads((char*)&header_num_facets, 4);
|
||||||
@ -257,7 +259,6 @@ stl_reallocate(stl_file *stl) {
|
|||||||
time running this for the stl and therefore we should reset our max and min stats. */
|
time running this for the stl and therefore we should reset our max and min stats. */
|
||||||
void stl_read(stl_file *stl, int first_facet, bool first) {
|
void stl_read(stl_file *stl, int first_facet, bool first) {
|
||||||
stl_facet facet;
|
stl_facet facet;
|
||||||
int i;
|
|
||||||
|
|
||||||
if (stl->error) return;
|
if (stl->error) return;
|
||||||
|
|
||||||
@ -268,7 +269,7 @@ void stl_read(stl_file *stl, int first_facet, bool first) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char normal_buf[3][32];
|
char normal_buf[3][32];
|
||||||
for(i = first_facet; i < stl->stats.number_of_facets; i++) {
|
for(uint32_t i = first_facet; i < stl->stats.number_of_facets; i++) {
|
||||||
if(stl->stats.type == binary)
|
if(stl->stats.type == binary)
|
||||||
/* Read a single facet from a binary .STL file */
|
/* Read a single facet from a binary .STL file */
|
||||||
{
|
{
|
||||||
@ -366,17 +367,19 @@ void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void stl_close(stl_file *stl)
|
||||||
stl_close(stl_file *stl) {
|
{
|
||||||
if (stl->error) return;
|
assert(stl->fp == nullptr);
|
||||||
|
assert(stl->heads == nullptr);
|
||||||
|
assert(stl->tail == nullptr);
|
||||||
|
|
||||||
if(stl->neighbors_start != NULL)
|
if (stl->facet_start != NULL)
|
||||||
free(stl->neighbors_start);
|
|
||||||
if(stl->facet_start != NULL)
|
|
||||||
free(stl->facet_start);
|
free(stl->facet_start);
|
||||||
if(stl->v_indices != NULL)
|
if (stl->neighbors_start != NULL)
|
||||||
|
free(stl->neighbors_start);
|
||||||
|
if (stl->v_indices != NULL)
|
||||||
free(stl->v_indices);
|
free(stl->v_indices);
|
||||||
if(stl->v_shared != NULL)
|
if (stl->v_shared != NULL)
|
||||||
free(stl->v_shared);
|
free(stl->v_shared);
|
||||||
|
memset(stl, 0, sizeof(stl_file));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -897,13 +897,13 @@ BoundingBoxf3 ModelObject::raw_bounding_box() const
|
|||||||
throw std::invalid_argument("Can't call raw_bounding_box() with no instances");
|
throw std::invalid_argument("Can't call raw_bounding_box() with no instances");
|
||||||
#endif // !ENABLE_GENERIC_SUBPARTS_PLACEMENT
|
#endif // !ENABLE_GENERIC_SUBPARTS_PLACEMENT
|
||||||
|
|
||||||
TriangleMesh vol_mesh(v->mesh);
|
|
||||||
#if ENABLE_GENERIC_SUBPARTS_PLACEMENT
|
#if ENABLE_GENERIC_SUBPARTS_PLACEMENT
|
||||||
vol_mesh.transform(inst_matrix * v->get_matrix());
|
bb.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix()));
|
||||||
bb.merge(vol_mesh.bounding_box());
|
|
||||||
#else
|
#else
|
||||||
vol_mesh.transform(v->get_matrix());
|
// unmaintaned
|
||||||
bb.merge(this->instances.front()->transform_mesh_bounding_box(vol_mesh, true));
|
assert(false);
|
||||||
|
// vol_mesh.transform(v->get_matrix());
|
||||||
|
// bb.merge(this->instances.front()->transform_mesh_bounding_box(vol_mesh, true));
|
||||||
#endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT
|
#endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT
|
||||||
}
|
}
|
||||||
return bb;
|
return bb;
|
||||||
@ -920,13 +920,13 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_
|
|||||||
{
|
{
|
||||||
if (v->is_model_part())
|
if (v->is_model_part())
|
||||||
{
|
{
|
||||||
TriangleMesh mesh(v->mesh);
|
|
||||||
#if ENABLE_GENERIC_SUBPARTS_PLACEMENT
|
#if ENABLE_GENERIC_SUBPARTS_PLACEMENT
|
||||||
mesh.transform(inst_matrix * v->get_matrix());
|
bb.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix()));
|
||||||
bb.merge(mesh.bounding_box());
|
|
||||||
#else
|
#else
|
||||||
mesh.transform(v->get_matrix());
|
// not maintained
|
||||||
bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(mesh, dont_translate));
|
assert(false);
|
||||||
|
//mesh.transform(v->get_matrix());
|
||||||
|
//bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(mesh, dont_translate));
|
||||||
#endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT
|
#endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -805,14 +805,7 @@ void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons
|
|||||||
void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLines>* lines, boost::mutex* lines_mutex,
|
void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLines>* lines, boost::mutex* lines_mutex,
|
||||||
const std::vector<float> &z) const
|
const std::vector<float> &z) const
|
||||||
{
|
{
|
||||||
const stl_facet &facet_orig = this->mesh->stl.facet_start[facet_idx];
|
const stl_facet &facet = m_use_quaternion ? this->mesh->stl.facet_start[facet_idx].rotated(m_quaternion) : this->mesh->stl.facet_start[facet_idx];
|
||||||
stl_facet facet = facet_orig;
|
|
||||||
|
|
||||||
if (m_use_quaternion) {
|
|
||||||
facet.vertex[0] = m_quaternion * facet.vertex[0];
|
|
||||||
facet.vertex[1] = m_quaternion * facet.vertex[1];
|
|
||||||
facet.vertex[2] = m_quaternion * facet.vertex[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
// find facet extents
|
// find facet extents
|
||||||
const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2)));
|
const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2)));
|
||||||
@ -884,7 +877,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet(
|
|||||||
const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
|
const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
|
||||||
int i = (facet.vertex[1].z() == min_z) ? 1 : ((facet.vertex[2].z() == min_z) ? 2 : 0);
|
int i = (facet.vertex[1].z() == min_z) ? 1 : ((facet.vertex[2].z() == min_z) ? 2 : 0);
|
||||||
|
|
||||||
// These are used only if the cut plane is inclined:
|
// These are used only if the cut plane is tilted:
|
||||||
stl_vertex rotated_a;
|
stl_vertex rotated_a;
|
||||||
stl_vertex rotated_b;
|
stl_vertex rotated_b;
|
||||||
|
|
||||||
@ -909,10 +902,11 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet(
|
|||||||
// Is edge or face aligned with the cutting plane?
|
// Is edge or face aligned with the cutting plane?
|
||||||
if (a->z() == slice_z && b->z() == slice_z) {
|
if (a->z() == slice_z && b->z() == slice_z) {
|
||||||
// Edge is horizontal and belongs to the current layer.
|
// Edge is horizontal and belongs to the current layer.
|
||||||
|
// The following rotation of the three vertices may not be efficient, but this branch happens rarely.
|
||||||
const stl_vertex &v0 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[0]]) : this->v_scaled_shared[vertices[0]];
|
const stl_vertex &v0 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[0]]) : this->v_scaled_shared[vertices[0]];
|
||||||
const stl_vertex &v1 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[1]]) : this->v_scaled_shared[vertices[1]];
|
const stl_vertex &v1 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[1]]) : this->v_scaled_shared[vertices[1]];
|
||||||
const stl_vertex &v2 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[2]]) : this->v_scaled_shared[vertices[2]];
|
const stl_vertex &v2 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[2]]) : this->v_scaled_shared[vertices[2]];
|
||||||
const stl_normal &normal = m_use_quaternion ? stl_vertex(m_quaternion * this->mesh->stl.facet_start[facet_idx].normal) : this->mesh->stl.facet_start[facet_idx].normal;
|
const stl_normal &normal = facet.normal;
|
||||||
// We may ignore this edge for slicing purposes, but we may still use it for object cutting.
|
// We may ignore this edge for slicing purposes, but we may still use it for object cutting.
|
||||||
FacetSliceType result = Slicing;
|
FacetSliceType result = Slicing;
|
||||||
if (min_z == max_z) {
|
if (min_z == max_z) {
|
||||||
@ -1029,7 +1023,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet(
|
|||||||
i = vertices[2];
|
i = vertices[2];
|
||||||
assert(i != line_out->a_id && i != line_out->b_id);
|
assert(i != line_out->a_id && i != line_out->b_id);
|
||||||
line_out->edge_type = ((m_use_quaternion ?
|
line_out->edge_type = ((m_use_quaternion ?
|
||||||
m_quaternion * this->v_scaled_shared[i].z()
|
(m_quaternion * this->v_scaled_shared[i]).z()
|
||||||
: this->v_scaled_shared[i].z()) < slice_z) ? feTop : feBottom;
|
: this->v_scaled_shared[i].z()) < slice_z) ? feTop : feBottom;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -25,9 +25,10 @@ public:
|
|||||||
TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets);
|
TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets);
|
||||||
TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; }
|
TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; }
|
||||||
TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); }
|
TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); }
|
||||||
~TriangleMesh() { stl_close(&this->stl); }
|
~TriangleMesh() { clear(); }
|
||||||
TriangleMesh& operator=(const TriangleMesh &other);
|
TriangleMesh& operator=(const TriangleMesh &other);
|
||||||
TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; }
|
TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; }
|
||||||
|
void clear() { stl_close(&this->stl); this->repaired = false; }
|
||||||
void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); }
|
void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); }
|
||||||
void ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); }
|
void ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); }
|
||||||
void write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); }
|
void write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); }
|
||||||
|
@ -3494,9 +3494,12 @@ void GLCanvas3D::_picking_pass() const
|
|||||||
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
||||||
|
|
||||||
m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane();
|
m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane();
|
||||||
|
if (! m_use_VBOs) {
|
||||||
::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data());
|
::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data());
|
||||||
::glEnable(GL_CLIP_PLANE0);
|
::glEnable(GL_CLIP_PLANE0);
|
||||||
|
}
|
||||||
_render_volumes(true);
|
_render_volumes(true);
|
||||||
|
if (! m_use_VBOs)
|
||||||
::glDisable(GL_CLIP_PLANE0);
|
::glDisable(GL_CLIP_PLANE0);
|
||||||
|
|
||||||
m_gizmos.render_current_gizmo_for_picking_pass(m_selection);
|
m_gizmos.render_current_gizmo_for_picking_pass(m_selection);
|
||||||
@ -3661,7 +3664,7 @@ void GLCanvas3D::_render_objects() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_camera_clipping_plane = ClippingPlane::ClipsNothing();
|
m_camera_clipping_plane = ClippingPlane::ClipsNothing();
|
||||||
::glDisable(GL_LIGHTING);
|
glsafe(::glDisable(GL_LIGHTING));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLCanvas3D::_render_selection() const
|
void GLCanvas3D::_render_selection() const
|
||||||
|
@ -58,6 +58,7 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S
|
|||||||
{
|
{
|
||||||
// Cache the bb - it's needed for dealing with the clipping plane quite often
|
// Cache the bb - it's needed for dealing with the clipping plane quite often
|
||||||
// It could be done inside update_mesh but one has to account for scaling of the instance.
|
// It could be done inside update_mesh but one has to account for scaling of the instance.
|
||||||
|
//FIXME calling ModelObject::instance_bounding_box() is expensive!
|
||||||
m_active_instance_bb_radius = m_model_object->instance_bounding_box(m_active_instance).radius();
|
m_active_instance_bb_radius = m_model_object->instance_bounding_box(m_active_instance).radius();
|
||||||
|
|
||||||
if (is_mesh_update_necessary()) {
|
if (is_mesh_update_necessary()) {
|
||||||
@ -123,6 +124,11 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection, const
|
|||||||
|| m_old_direction_to_camera != direction_to_camera) {
|
|| m_old_direction_to_camera != direction_to_camera) {
|
||||||
|
|
||||||
std::vector<ExPolygons> list_of_expolys;
|
std::vector<ExPolygons> list_of_expolys;
|
||||||
|
if (! m_tms) {
|
||||||
|
m_tms.reset(new TriangleMeshSlicer);
|
||||||
|
m_tms->init(const_cast<TriangleMesh*>(&m_mesh), [](){});
|
||||||
|
}
|
||||||
|
|
||||||
m_tms->set_up_direction(up);
|
m_tms->set_up_direction(up);
|
||||||
m_tms->slice(std::vector<float>{height_mesh}, 0.f, &list_of_expolys, [](){});
|
m_tms->slice(std::vector<float>{height_mesh}, 0.f, &list_of_expolys, [](){});
|
||||||
m_triangles = triangulate_expolygons_2f(list_of_expolys[0]);
|
m_triangles = triangulate_expolygons_2f(list_of_expolys[0]);
|
||||||
@ -131,6 +137,7 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection, const
|
|||||||
m_old_clipping_plane_distance = m_clipping_plane_distance;
|
m_old_clipping_plane_distance = m_clipping_plane_distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! m_triangles.empty()) {
|
||||||
::glPushMatrix();
|
::glPushMatrix();
|
||||||
::glTranslated(0.0, 0.0, m_z_shift);
|
::glTranslated(0.0, 0.0, m_z_shift);
|
||||||
::glMultMatrixf(instance_matrix.data());
|
::glMultMatrixf(instance_matrix.data());
|
||||||
@ -139,14 +146,14 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection, const
|
|||||||
Eigen::AngleAxisf aa(q);
|
Eigen::AngleAxisf aa(q);
|
||||||
::glRotatef(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2));
|
::glRotatef(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2));
|
||||||
::glTranslatef(0.f, 0.f, -0.001f); // to make sure the cut is safely beyond the near clipping plane
|
::glTranslatef(0.f, 0.f, -0.001f); // to make sure the cut is safely beyond the near clipping plane
|
||||||
|
::glColor3f(1.0f, 0.37f, 0.0f);
|
||||||
::glBegin(GL_TRIANGLES);
|
::glBegin(GL_TRIANGLES);
|
||||||
::glColor3f(1.0f, 0.37f, 0.0f);
|
::glColor3f(1.0f, 0.37f, 0.0f);
|
||||||
for (const Vec2f& point : m_triangles)
|
for (const Vec2f& point : m_triangles)
|
||||||
::glVertex3f(point(0), point(1), height_mesh);
|
::glVertex3f(point(0), point(1), height_mesh);
|
||||||
::glEnd();
|
::glEnd();
|
||||||
|
|
||||||
::glPopMatrix();
|
::glPopMatrix();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -344,9 +351,6 @@ void GLGizmoSlaSupports::update_mesh()
|
|||||||
|
|
||||||
m_AABB = igl::AABB<Eigen::MatrixXf,3>();
|
m_AABB = igl::AABB<Eigen::MatrixXf,3>();
|
||||||
m_AABB.init(m_V, m_F);
|
m_AABB.init(m_V, m_F);
|
||||||
|
|
||||||
m_tms.reset(new TriangleMeshSlicer);
|
|
||||||
m_tms->init(&m_mesh, [](){});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unprojects the mouse position on the mesh and return the hit point and normal of the facet.
|
// Unprojects the mouse position on the mesh and return the hit point and normal of the facet.
|
||||||
@ -969,6 +973,12 @@ void GLGizmoSlaSupports::on_set_state()
|
|||||||
m_editing_mode = false; // so it is not active next time the gizmo opens
|
m_editing_mode = false; // so it is not active next time the gizmo opens
|
||||||
m_editing_mode_cache.clear();
|
m_editing_mode_cache.clear();
|
||||||
m_clipping_plane_distance = 0.f;
|
m_clipping_plane_distance = 0.f;
|
||||||
|
// Release copy of the mesh, triangle slicer and the AABB spatial search structure.
|
||||||
|
m_mesh.clear();
|
||||||
|
m_AABB.deinit();
|
||||||
|
m_V = Eigen::MatrixXf();
|
||||||
|
m_F = Eigen::MatrixXi();
|
||||||
|
m_tms.reset(nullptr);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
m_old_state = m_state;
|
m_old_state = m_state;
|
||||||
|
Loading…
Reference in New Issue
Block a user