mirror of https://github.com/vitalif/openscad
Clifford Wolf:
Added CGAL Nef Polyhedron 2D/3D abstraction git-svn-id: http://svn.clifford.at/openscad/trunk@187 b57f626f-c46c-0410-a088-ec61d464b74cstl_dim
parent
612f04dfdf
commit
f7799a8e4e
24
csgops.cc
24
csgops.cc
|
@ -77,16 +77,26 @@ CGAL_Nef_polyhedron CsgNode::render_cgal_nef_polyhedron() const
|
||||||
if (first) {
|
if (first) {
|
||||||
N = v->render_cgal_nef_polyhedron();
|
N = v->render_cgal_nef_polyhedron();
|
||||||
first = false;
|
first = false;
|
||||||
} else if (type == CSG_TYPE_UNION) {
|
} else if (N.dim == 2) {
|
||||||
N += v->render_cgal_nef_polyhedron();
|
if (type == CSG_TYPE_UNION) {
|
||||||
} else if (type == CSG_TYPE_DIFFERENCE) {
|
N.p2 += v->render_cgal_nef_polyhedron().p2;
|
||||||
N -= v->render_cgal_nef_polyhedron();
|
} else if (type == CSG_TYPE_DIFFERENCE) {
|
||||||
} else if (type == CSG_TYPE_INTERSECTION) {
|
N.p2 -= v->render_cgal_nef_polyhedron().p2;
|
||||||
N *= v->render_cgal_nef_polyhedron();
|
} else if (type == CSG_TYPE_INTERSECTION) {
|
||||||
|
N.p2 *= v->render_cgal_nef_polyhedron().p2;
|
||||||
|
}
|
||||||
|
} else if (N.dim == 3) {
|
||||||
|
if (type == CSG_TYPE_UNION) {
|
||||||
|
N.p3 += v->render_cgal_nef_polyhedron().p3;
|
||||||
|
} else if (type == CSG_TYPE_DIFFERENCE) {
|
||||||
|
N.p3 -= v->render_cgal_nef_polyhedron().p3;
|
||||||
|
} else if (type == CSG_TYPE_INTERSECTION) {
|
||||||
|
N.p3 *= v->render_cgal_nef_polyhedron().p3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cgal_nef_cache.insert(cache_id, new CGAL_Nef_polyhedron(N), N.number_of_vertices());
|
cgal_nef_cache.insert(cache_id, new CGAL_Nef_polyhedron(N), N.weight());
|
||||||
progress_report();
|
progress_report();
|
||||||
return N;
|
return N;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
void export_stl(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog *pd)
|
void export_stl(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog *pd)
|
||||||
{
|
{
|
||||||
CGAL_Polyhedron P;
|
CGAL_Polyhedron P;
|
||||||
root_N->convert_to_Polyhedron(P);
|
root_N->p3.convert_to_Polyhedron(P);
|
||||||
|
|
||||||
typedef CGAL_Polyhedron::Vertex Vertex;
|
typedef CGAL_Polyhedron::Vertex Vertex;
|
||||||
typedef CGAL_Polyhedron::Vertex_const_iterator VCI;
|
typedef CGAL_Polyhedron::Vertex_const_iterator VCI;
|
||||||
|
|
52
mainwin.cc
52
mainwin.cc
|
@ -909,22 +909,28 @@ void MainWindow::actionRenderCGAL()
|
||||||
PRINTF("Number of objects currently in CGAL cache: %d", AbstractNode::cgal_nef_cache.size());
|
PRINTF("Number of objects currently in CGAL cache: %d", AbstractNode::cgal_nef_cache.size());
|
||||||
QApplication::processEvents();
|
QApplication::processEvents();
|
||||||
|
|
||||||
PRINTF(" Simple: %6s", root_N->is_simple() ? "yes" : "no");
|
if (root_N->dim == 2) {
|
||||||
QApplication::processEvents();
|
// FIXME
|
||||||
PRINTF(" Valid: %6s", root_N->is_valid() ? "yes" : "no");
|
}
|
||||||
QApplication::processEvents();
|
|
||||||
PRINTF(" Vertices: %6d", (int)root_N->number_of_vertices());
|
if (root_N->dim == 3) {
|
||||||
QApplication::processEvents();
|
PRINTF(" Simple: %6s", root_N->p3.is_simple() ? "yes" : "no");
|
||||||
PRINTF(" Halfedges: %6d", (int)root_N->number_of_halfedges());
|
QApplication::processEvents();
|
||||||
QApplication::processEvents();
|
PRINTF(" Valid: %6s", root_N->p3.is_valid() ? "yes" : "no");
|
||||||
PRINTF(" Edges: %6d", (int)root_N->number_of_edges());
|
QApplication::processEvents();
|
||||||
QApplication::processEvents();
|
PRINTF(" Vertices: %6d", (int)root_N->p3.number_of_vertices());
|
||||||
PRINTF(" Halffacets: %6d", (int)root_N->number_of_halffacets());
|
QApplication::processEvents();
|
||||||
QApplication::processEvents();
|
PRINTF(" Halfedges: %6d", (int)root_N->p3.number_of_halfedges());
|
||||||
PRINTF(" Facets: %6d", (int)root_N->number_of_facets());
|
QApplication::processEvents();
|
||||||
QApplication::processEvents();
|
PRINTF(" Edges: %6d", (int)root_N->p3.number_of_edges());
|
||||||
PRINTF(" Volumes: %6d", (int)root_N->number_of_volumes());
|
QApplication::processEvents();
|
||||||
QApplication::processEvents();
|
PRINTF(" Halffacets: %6d", (int)root_N->p3.number_of_halffacets());
|
||||||
|
QApplication::processEvents();
|
||||||
|
PRINTF(" Facets: %6d", (int)root_N->p3.number_of_facets());
|
||||||
|
QApplication::processEvents();
|
||||||
|
PRINTF(" Volumes: %6d", (int)root_N->p3.number_of_volumes());
|
||||||
|
QApplication::processEvents();
|
||||||
|
}
|
||||||
|
|
||||||
int s = t.elapsed() / 1000;
|
int s = t.elapsed() / 1000;
|
||||||
PRINTF("Total rendering time: %d hours, %d minutes, %d seconds", s / (60*60), (s / 60) % 60, s % 60);
|
PRINTF("Total rendering time: %d hours, %d minutes, %d seconds", s / (60*60), (s / 60) % 60, s % 60);
|
||||||
|
@ -1003,7 +1009,13 @@ void MainWindow::actionExportSTLorOFF(bool)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!root_N->is_simple()) {
|
if (root_N->dim != 3) {
|
||||||
|
PRINT("Current top level object is not a 3D object.");
|
||||||
|
current_win = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root_N->p3.is_simple()) {
|
||||||
PRINT("Object isn't a valid 2-manifold! Modify your design..");
|
PRINT("Object isn't a valid 2-manifold! Modify your design..");
|
||||||
current_win = NULL;
|
current_win = NULL;
|
||||||
return;
|
return;
|
||||||
|
@ -1020,7 +1032,7 @@ void MainWindow::actionExportSTLorOFF(bool)
|
||||||
|
|
||||||
QProgressDialog *pd = new QProgressDialog(
|
QProgressDialog *pd = new QProgressDialog(
|
||||||
stl_mode ? "Exporting object to STL file..." : "Exporting object to OFF file...",
|
stl_mode ? "Exporting object to STL file..." : "Exporting object to OFF file...",
|
||||||
QString(), 0, root_N->number_of_facets() + 1);
|
QString(), 0, root_N->p3.number_of_facets() + 1);
|
||||||
pd->setValue(0);
|
pd->setValue(0);
|
||||||
pd->setAutoClose(false);
|
pd->setAutoClose(false);
|
||||||
pd->show();
|
pd->show();
|
||||||
|
@ -1180,11 +1192,11 @@ static void renderGLviaCGAL(void *vp)
|
||||||
delete p;
|
delete p;
|
||||||
m->cgal_ogl_p = NULL;
|
m->cgal_ogl_p = NULL;
|
||||||
}
|
}
|
||||||
if (m->root_N) {
|
if (m->root_N && m->root_N->dim == 3) {
|
||||||
CGAL::OGL::Polyhedron *p = (CGAL::OGL::Polyhedron*)m->cgal_ogl_p;
|
CGAL::OGL::Polyhedron *p = (CGAL::OGL::Polyhedron*)m->cgal_ogl_p;
|
||||||
if (!p) {
|
if (!p) {
|
||||||
m->cgal_ogl_p = p = new CGAL::OGL::Polyhedron();
|
m->cgal_ogl_p = p = new CGAL::OGL::Polyhedron();
|
||||||
CGAL::OGL::Nef3_Converter<CGAL_Nef_polyhedron>::convert_to_OGLPolyhedron(*m->root_N, p);
|
CGAL::OGL::Nef3_Converter<CGAL_Nef_polyhedron3>::convert_to_OGLPolyhedron(m->root_N->p3, p);
|
||||||
p->init();
|
p->init();
|
||||||
}
|
}
|
||||||
if (m->viewActionCGALSurfaces->isChecked())
|
if (m->viewActionCGALSurfaces->isChecked())
|
||||||
|
|
17
module.cc
17
module.cc
|
@ -248,14 +248,21 @@ static CGAL_Nef_polyhedron render_cgal_nef_polyhedron_backend(const AbstractNode
|
||||||
continue;
|
continue;
|
||||||
if (is_first)
|
if (is_first)
|
||||||
N = v->render_cgal_nef_polyhedron();
|
N = v->render_cgal_nef_polyhedron();
|
||||||
else if (intersect)
|
else if (N.dim == 2) {
|
||||||
N *= v->render_cgal_nef_polyhedron();
|
if (intersect)
|
||||||
else
|
N.p2 *= v->render_cgal_nef_polyhedron().p2;
|
||||||
N += v->render_cgal_nef_polyhedron();
|
else
|
||||||
|
N.p2 += v->render_cgal_nef_polyhedron().p2;
|
||||||
|
} else {
|
||||||
|
if (intersect)
|
||||||
|
N.p3 *= v->render_cgal_nef_polyhedron().p3;
|
||||||
|
else
|
||||||
|
N.p3 += v->render_cgal_nef_polyhedron().p3;
|
||||||
|
}
|
||||||
is_first = false;
|
is_first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
that->cgal_nef_cache.insert(cache_id, new CGAL_Nef_polyhedron(N), N.number_of_vertices());
|
that->cgal_nef_cache.insert(cache_id, new CGAL_Nef_polyhedron(N), N.weight());
|
||||||
that->progress_report();
|
that->progress_report();
|
||||||
return N;
|
return N;
|
||||||
}
|
}
|
||||||
|
|
48
openscad.h
48
openscad.h
|
@ -459,20 +459,54 @@ public:
|
||||||
#ifdef ENABLE_CGAL
|
#ifdef ENABLE_CGAL
|
||||||
|
|
||||||
#include <CGAL/Gmpq.h>
|
#include <CGAL/Gmpq.h>
|
||||||
|
#include <CGAL/Extended_cartesian.h>
|
||||||
|
#include <CGAL/Nef_polyhedron_2.h>
|
||||||
#include <CGAL/Cartesian.h>
|
#include <CGAL/Cartesian.h>
|
||||||
#include <CGAL/Polyhedron_3.h>
|
#include <CGAL/Polyhedron_3.h>
|
||||||
#include <CGAL/Nef_polyhedron_3.h>
|
#include <CGAL/Nef_polyhedron_3.h>
|
||||||
#include <CGAL/IO/Polyhedron_iostream.h>
|
#include <CGAL/IO/Polyhedron_iostream.h>
|
||||||
|
|
||||||
typedef CGAL::Cartesian<CGAL::Gmpq> CGAL_Kernel;
|
typedef CGAL::Extended_cartesian<CGAL::Gmpq> CGAL_Kernel2;
|
||||||
typedef CGAL::Polyhedron_3<CGAL_Kernel> CGAL_Polyhedron;
|
typedef CGAL::Nef_polyhedron_2<CGAL_Kernel2> CGAL_Nef_polyhedron2;
|
||||||
|
|
||||||
|
typedef CGAL::Cartesian<CGAL::Gmpq> CGAL_Kernel3;
|
||||||
|
typedef CGAL::Polyhedron_3<CGAL_Kernel3> CGAL_Polyhedron;
|
||||||
typedef CGAL_Polyhedron::HalfedgeDS CGAL_HDS;
|
typedef CGAL_Polyhedron::HalfedgeDS CGAL_HDS;
|
||||||
typedef CGAL::Polyhedron_incremental_builder_3<CGAL_HDS> CGAL_Polybuilder;
|
typedef CGAL::Polyhedron_incremental_builder_3<CGAL_HDS> CGAL_Polybuilder;
|
||||||
typedef CGAL::Nef_polyhedron_3<CGAL_Kernel> CGAL_Nef_polyhedron;
|
typedef CGAL::Nef_polyhedron_3<CGAL_Kernel3> CGAL_Nef_polyhedron3;
|
||||||
typedef CGAL_Nef_polyhedron::Aff_transformation_3 CGAL_Aff_transformation;
|
typedef CGAL_Nef_polyhedron3::Aff_transformation_3 CGAL_Aff_transformation;
|
||||||
typedef CGAL_Nef_polyhedron::Vector_3 CGAL_Vector;
|
typedef CGAL_Nef_polyhedron3::Vector_3 CGAL_Vector;
|
||||||
typedef CGAL_Nef_polyhedron::Plane_3 CGAL_Plane;
|
typedef CGAL_Nef_polyhedron3::Plane_3 CGAL_Plane;
|
||||||
typedef CGAL_Nef_polyhedron::Point_3 CGAL_Point;
|
typedef CGAL_Nef_polyhedron3::Point_3 CGAL_Point;
|
||||||
|
|
||||||
|
struct CGAL_Nef_polyhedron
|
||||||
|
{
|
||||||
|
int dim;
|
||||||
|
CGAL_Nef_polyhedron2 p2;
|
||||||
|
CGAL_Nef_polyhedron3 p3;
|
||||||
|
|
||||||
|
CGAL_Nef_polyhedron() {
|
||||||
|
dim = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGAL_Nef_polyhedron(const CGAL_Nef_polyhedron2 &p) {
|
||||||
|
dim = 2;
|
||||||
|
p2 = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGAL_Nef_polyhedron(const CGAL_Nef_polyhedron3 &p) {
|
||||||
|
dim = 3;
|
||||||
|
p3 = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
int weight() {
|
||||||
|
if (dim == 2)
|
||||||
|
return 100;
|
||||||
|
if (dim == 3)
|
||||||
|
return p3.number_of_vertices();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* ENABLE_CGAL */
|
#endif /* ENABLE_CGAL */
|
||||||
|
|
||||||
|
|
|
@ -290,8 +290,8 @@ CGAL_Nef_polyhedron PolySet::render_cgal_nef_polyhedron() const
|
||||||
#if 0
|
#if 0
|
||||||
std::cout << P;
|
std::cout << P;
|
||||||
#endif
|
#endif
|
||||||
CGAL_Nef_polyhedron N(P);
|
CGAL_Nef_polyhedron3 N(P);
|
||||||
return N;
|
return CGAL_Nef_polyhedron(N);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* ENABLE_CGAL */
|
#endif /* ENABLE_CGAL */
|
||||||
|
@ -314,7 +314,7 @@ CGAL_Nef_polyhedron AbstractPolyNode::render_cgal_nef_polyhedron() const
|
||||||
PolySet *ps = render_polyset(RENDER_CGAL);
|
PolySet *ps = render_polyset(RENDER_CGAL);
|
||||||
CGAL_Nef_polyhedron N = ps->render_cgal_nef_polyhedron();
|
CGAL_Nef_polyhedron N = ps->render_cgal_nef_polyhedron();
|
||||||
|
|
||||||
cgal_nef_cache.insert(cache_id, new CGAL_Nef_polyhedron(N), N.number_of_vertices());
|
cgal_nef_cache.insert(cache_id, new CGAL_Nef_polyhedron(N), N.weight());
|
||||||
progress_report();
|
progress_report();
|
||||||
ps->unlink();
|
ps->unlink();
|
||||||
return N;
|
return N;
|
||||||
|
|
83
render.cc
83
render.cc
|
@ -93,12 +93,14 @@ CGAL_Nef_polyhedron RenderNode::render_cgal_nef_polyhedron() const
|
||||||
if (first) {
|
if (first) {
|
||||||
N = v->render_cgal_nef_polyhedron();
|
N = v->render_cgal_nef_polyhedron();
|
||||||
first = false;
|
first = false;
|
||||||
} else {
|
} else if (N.dim == 2) {
|
||||||
N += v->render_cgal_nef_polyhedron();
|
N.p2 += v->render_cgal_nef_polyhedron().p2;
|
||||||
|
} else if (N.dim == 3) {
|
||||||
|
N.p3 += v->render_cgal_nef_polyhedron().p3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cgal_nef_cache.insert(cache_id, new CGAL_Nef_polyhedron(N), N.number_of_vertices());
|
cgal_nef_cache.insert(cache_id, new CGAL_Nef_polyhedron(N), N.weight());
|
||||||
progress_report();
|
progress_report();
|
||||||
return N;
|
return N;
|
||||||
}
|
}
|
||||||
|
@ -153,45 +155,56 @@ CSGTerm *RenderNode::render_csg_term(double m[16], QVector<CSGTerm*> *highlights
|
||||||
delete pd;
|
delete pd;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!N.is_simple()) {
|
if (N.dim == 2)
|
||||||
PRINTF("WARNING: Result of render() isn't valid 2-manifold! Modify your design..");
|
{
|
||||||
|
// FIXME
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
PolySet *ps = new PolySet();
|
if (N.dim == 3)
|
||||||
ps->convexity = convexity;
|
{
|
||||||
|
if (!N.p3.is_simple()) {
|
||||||
CGAL_Polyhedron P;
|
PRINTF("WARNING: Result of render() isn't valid 2-manifold! Modify your design..");
|
||||||
N.convert_to_Polyhedron(P);
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
typedef CGAL_Polyhedron::Vertex Vertex;
|
PolySet *ps = new PolySet();
|
||||||
typedef CGAL_Polyhedron::Vertex_const_iterator VCI;
|
ps->convexity = convexity;
|
||||||
typedef CGAL_Polyhedron::Facet_const_iterator FCI;
|
|
||||||
typedef CGAL_Polyhedron::Halfedge_around_facet_const_circulator HFCC;
|
CGAL_Polyhedron P;
|
||||||
|
N.p3.convert_to_Polyhedron(P);
|
||||||
|
|
||||||
for (FCI fi = P.facets_begin(); fi != P.facets_end(); ++fi) {
|
typedef CGAL_Polyhedron::Vertex Vertex;
|
||||||
HFCC hc = fi->facet_begin();
|
typedef CGAL_Polyhedron::Vertex_const_iterator VCI;
|
||||||
HFCC hc_end = hc;
|
typedef CGAL_Polyhedron::Facet_const_iterator FCI;
|
||||||
ps->append_poly();
|
typedef CGAL_Polyhedron::Halfedge_around_facet_const_circulator HFCC;
|
||||||
do {
|
|
||||||
Vertex v = *VCI((hc++)->vertex());
|
for (FCI fi = P.facets_begin(); fi != P.facets_end(); ++fi) {
|
||||||
double x = CGAL::to_double(v.point().x());
|
HFCC hc = fi->facet_begin();
|
||||||
double y = CGAL::to_double(v.point().y());
|
HFCC hc_end = hc;
|
||||||
double z = CGAL::to_double(v.point().z());
|
ps->append_poly();
|
||||||
ps->append_vertex(x, y, z);
|
do {
|
||||||
} while (hc != hc_end);
|
Vertex v = *VCI((hc++)->vertex());
|
||||||
|
double x = CGAL::to_double(v.point().x());
|
||||||
|
double y = CGAL::to_double(v.point().y());
|
||||||
|
double z = CGAL::to_double(v.point().z());
|
||||||
|
ps->append_vertex(x, y, z);
|
||||||
|
} while (hc != hc_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
PolySet::ps_cache.insert(key, new PolySetPtr(ps->link()));
|
||||||
|
|
||||||
|
CSGTerm *term = new CSGTerm(ps, m, QString("n%1").arg(idx));
|
||||||
|
if (modinst->tag_highlight && highlights)
|
||||||
|
highlights->append(term->link());
|
||||||
|
if (modinst->tag_background && background) {
|
||||||
|
background->append(term);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return term;
|
||||||
}
|
}
|
||||||
|
|
||||||
PolySet::ps_cache.insert(key, new PolySetPtr(ps->link()));
|
return NULL;
|
||||||
|
|
||||||
CSGTerm *term = new CSGTerm(ps, m, QString("n%1").arg(idx));
|
|
||||||
if (modinst->tag_highlight && highlights)
|
|
||||||
highlights->append(term->link());
|
|
||||||
if (modinst->tag_background && background) {
|
|
||||||
background->append(term);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return term;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
28
transform.cc
28
transform.cc
|
@ -190,20 +190,34 @@ CGAL_Nef_polyhedron TransformNode::render_cgal_nef_polyhedron() const
|
||||||
return *cgal_nef_cache[cache_id];
|
return *cgal_nef_cache[cache_id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
CGAL_Nef_polyhedron N;
|
CGAL_Nef_polyhedron N;
|
||||||
|
|
||||||
foreach (AbstractNode *v, children) {
|
foreach (AbstractNode *v, children) {
|
||||||
if (v->modinst->tag_background)
|
if (v->modinst->tag_background)
|
||||||
continue;
|
continue;
|
||||||
N += v->render_cgal_nef_polyhedron();
|
if (first) {
|
||||||
|
N = v->render_cgal_nef_polyhedron();
|
||||||
|
first = false;
|
||||||
|
} else if (N.dim == 2) {
|
||||||
|
N.p2 += v->render_cgal_nef_polyhedron().p2;
|
||||||
|
} else if (N.dim == 3) {
|
||||||
|
N.p3 += v->render_cgal_nef_polyhedron().p3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CGAL_Aff_transformation t(
|
if (N.dim == 2) {
|
||||||
m[0], m[4], m[ 8], m[12],
|
// FIXME
|
||||||
m[1], m[5], m[ 9], m[13],
|
}
|
||||||
m[2], m[6], m[10], m[14], m[15]);
|
if (N.dim == 3) {
|
||||||
N.transform(t);
|
CGAL_Aff_transformation t(
|
||||||
|
m[0], m[4], m[ 8], m[12],
|
||||||
|
m[1], m[5], m[ 9], m[13],
|
||||||
|
m[2], m[6], m[10], m[14], m[15]);
|
||||||
|
N.p3.transform(t);
|
||||||
|
}
|
||||||
|
|
||||||
cgal_nef_cache.insert(cache_id, new CGAL_Nef_polyhedron(N), N.number_of_vertices());
|
cgal_nef_cache.insert(cache_id, new CGAL_Nef_polyhedron(N), N.weight());
|
||||||
progress_report();
|
progress_report();
|
||||||
return N;
|
return N;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue