Commit 795268ed authored by Kirill Terekhov's avatar Kirill Terekhov

Functions to remove and restore upper/lower adjacency connections; use...

Functions to remove and restore upper/lower adjacency connections; use RestoreCellNodes for vtk/vtu output only; change RestoreCellNodes algorithm to not require upper adjacencies for nodes
parent aa464182
...@@ -881,9 +881,9 @@ namespace INMOST ...@@ -881,9 +881,9 @@ namespace INMOST
__INLINE bool Tag::isDefinedMask(ElementType mask) const __INLINE bool Tag::isDefinedMask(ElementType mask) const
{ {
assert(mem!=NULL); assert(mem!=NULL);
bool ret = false; bool ret = true;
for(ElementType etype = NODE; etype <= MESH; etype = NextElementType(etype)) for(ElementType etype = NODE; etype <= MESH; etype = NextElementType(etype))
if( etype & mask ) ret |= isDefined(etype&mask); if( etype & mask ) ret &= isDefined(etype&mask);
return ret; return ret;
} }
__INLINE bool Tag::isSparse(ElementType type) const __INLINE bool Tag::isSparse(ElementType type) const
......
...@@ -2068,6 +2068,18 @@ namespace INMOST ...@@ -2068,6 +2068,18 @@ namespace INMOST
/// @see Face::FixNormalOrientation /// @see Face::FixNormalOrientation
/// @see Mesh::LowConn /// @see Mesh::LowConn
Element::adj_type & HighConn (HandleType h) {return *static_cast<inner_reference_array*>(MGetDenseLink(h,HighConnTag()));} Element::adj_type & HighConn (HandleType h) {return *static_cast<inner_reference_array*>(MGetDenseLink(h,HighConnTag()));}
/// Check that upper adjacencies are stored
ElementType HaveUpperAdjacencies() const;
/// Delete all upper adjacencies, access to HighConn should fire assertion and retrival of upper adjacencies is no longer valid
void RemoveUpperAdjacencies(ElementType mask = (NODE|EDGE|FACE));
/// Restore all upper adjacencies
void RestoreUpperAdjacencies(ElementType mask = (NODE|EDGE|FACE));
/// Check that upper adjacencies are stored
ElementType HaveLowerAdjacencies() const;
/// Delete all upper adjacencies, access to HighConn should fire assertion and retrival of upper adjacencies is no longer valid
void RemoveLowerAdjacencies(ElementType mask = (EDGE|FACE|CELL));
/// Restore all upper adjacencies
void RestoreLowerAdjacencies(ElementType mask = (EDGE|FACE|CELL));
/// Access directly higher order adjacencies of current element without right of modification. /// Access directly higher order adjacencies of current element without right of modification.
Element::adj_type const& HighConn (HandleType h) const {return *static_cast<const inner_reference_array*>(MGetDenseLink(h,HighConnTag()));} Element::adj_type const& HighConn (HandleType h) const {return *static_cast<const inner_reference_array*>(MGetDenseLink(h,HighConnTag()));}
/// Access directly lower order adjacencies of current element with right of modification. /// Access directly lower order adjacencies of current element with right of modification.
......
...@@ -227,7 +227,8 @@ namespace INMOST ...@@ -227,7 +227,8 @@ namespace INMOST
case Element::Pyramid: case Element::Pyramid:
case Element::Prism: case Element::Prism:
{ {
ElementArray<Node> nodes = it->getNodes(); ElementArray<Node> nodes(this);// = it->getNodes();
RestoreCellNodes(*it,nodes);
if( nodes.size() != VtkElementNodes(it->GetGeometricType()) ) goto safe_output; if( nodes.size() != VtkElementNodes(it->GetGeometricType()) ) goto safe_output;
values.push_back(static_cast<integer>(nodes.size())); values.push_back(static_cast<integer>(nodes.size()));
for(ElementArray<Node>::iterator jt = nodes.begin(); jt != nodes.end(); jt++) for(ElementArray<Node>::iterator jt = nodes.begin(); jt != nodes.end(); jt++)
...@@ -243,50 +244,6 @@ namespace INMOST ...@@ -243,50 +244,6 @@ namespace INMOST
values.push_back(jt->IntegerDF(set_id)); values.push_back(jt->IntegerDF(set_id));
break; break;
} }
/*
case Element::Prism:
{
ElementArray<Node> nodes = it->getNodes();
if( nodes.size() != 6 ) goto safe_output;
values.push_back(static_cast<integer>(nodes.size()));
values.push_back(nodes[0].IntegerDF(set_id));
values.push_back(nodes[2].IntegerDF(set_id));
values.push_back(nodes[1].IntegerDF(set_id));
values.push_back(nodes[3].IntegerDF(set_id));
values.push_back(nodes[5].IntegerDF(set_id));
values.push_back(nodes[4].IntegerDF(set_id));
break;
}
*/
/*
case Element::Hex:
{
ElementArray<Node> nodes = it->getNodes();
if( nodes.size() != 8 ) goto safe_output;
values.push_back(static_cast<integer>(nodes.size()));
values.push_back(nodes[0].IntegerDF(set_id));
values.push_back(nodes[3].IntegerDF(set_id));
values.push_back(nodes[2].IntegerDF(set_id));
values.push_back(nodes[1].IntegerDF(set_id));
values.push_back(nodes[4].IntegerDF(set_id));
values.push_back(nodes[7].IntegerDF(set_id));
values.push_back(nodes[6].IntegerDF(set_id));
values.push_back(nodes[5].IntegerDF(set_id));
break;
}
case Element::Pyramid:
{
ElementArray<Node> nodes = it->getNodes();
if( nodes.size() != 5 ) goto safe_output;
values.push_back(static_cast<integer>(nodes.size()));
values.push_back(nodes[0].IntegerDF(set_id));
values.push_back(nodes[3].IntegerDF(set_id));
values.push_back(nodes[2].IntegerDF(set_id));
values.push_back(nodes[1].IntegerDF(set_id));
values.push_back(nodes[4].IntegerDF(set_id));
break;
}
*/
case Element::Polyhedron: case Element::Polyhedron:
case Element::MultiPolygon: case Element::MultiPolygon:
{ {
......
...@@ -344,7 +344,9 @@ namespace INMOST ...@@ -344,7 +344,9 @@ namespace INMOST
f << "\t\t\t\t<DataArray type=\"Int64\" Name=\"connectivity\" format=\"ascii\">" << std::endl; f << "\t\t\t\t<DataArray type=\"Int64\" Name=\"connectivity\" format=\"ascii\">" << std::endl;
for(Mesh::iteratorCell jt = BeginCell(); jt != EndCell(); ++jt) for(Mesh::iteratorCell jt = BeginCell(); jt != EndCell(); ++jt)
{ {
ElementArray<Node> nodes = jt->getNodes(); ElementArray<Node> nodes(this); //= jt->getNodes();
RestoreCellNodes(*jt,nodes);
assert(nodes.size() == jt->nbAdjElements(NODE));
for(ElementArray<Node>::iterator kt = nodes.begin(); kt != nodes.end(); ++kt) for(ElementArray<Node>::iterator kt = nodes.begin(); kt != nodes.end(); ++kt)
f << nid[*kt] << " "; f << nid[*kt] << " ";
f << std::endl; f << std::endl;
...@@ -355,8 +357,9 @@ namespace INMOST ...@@ -355,8 +357,9 @@ namespace INMOST
size_t offset = 0; size_t offset = 0;
for(Mesh::iteratorCell jt = BeginCell(); jt != EndCell(); ++jt) for(Mesh::iteratorCell jt = BeginCell(); jt != EndCell(); ++jt)
{ {
ElementArray<Node> nodes = jt->getNodes(); //ElementArray<Node> nodes = jt->getNodes();
offset += nodes.size(); //offset += nodes.size();
offset += jt->nbAdjElements(NODE);
f << offset << std::endl; f << offset << std::endl;
} }
} }
...@@ -383,6 +386,7 @@ namespace INMOST ...@@ -383,6 +386,7 @@ namespace INMOST
for(ElementArray<Face>::iterator kt = faces.begin(); kt != faces.end(); ++kt) for(ElementArray<Face>::iterator kt = faces.begin(); kt != faces.end(); ++kt)
{ {
ElementArray<Node> nodes = kt->getNodes(); ElementArray<Node> nodes = kt->getNodes();
assert(nodes.size() == kt->nbAdjElements(NODE));
f << nodes.size() << " "; f << nodes.size() << " ";
for(ElementArray<Node>::iterator qt = nodes.begin(); qt != nodes.end(); ++qt) for(ElementArray<Node>::iterator qt = nodes.begin(); qt != nodes.end(); ++qt)
f << nid[*qt] << " "; f << nid[*qt] << " ";
...@@ -405,7 +409,8 @@ namespace INMOST ...@@ -405,7 +409,8 @@ namespace INMOST
for(ElementArray<Face>::iterator kt = faces.begin(); kt != faces.end(); ++kt) for(ElementArray<Face>::iterator kt = faces.begin(); kt != faces.end(); ++kt)
{ {
offset++; //number of nodes in face offset++; //number of nodes in face
offset+= kt->getNodes().size(); //node ids //offset+= kt->getNodes().size(); //node ids
offset += kt->nbAdjElements(NODE);
} }
f << offset << std::endl; f << offset << std::endl;
} }
......
...@@ -289,7 +289,7 @@ namespace INMOST ...@@ -289,7 +289,7 @@ namespace INMOST
return false; return false;
} }
} }
return false; return true;
} }
ElementArray<Node> Cell::getNodes() const ElementArray<Node> Cell::getNodes() const
...@@ -297,7 +297,51 @@ namespace INMOST ...@@ -297,7 +297,51 @@ namespace INMOST
assert(GetHandleElementType(GetHandle())==CELL); assert(GetHandleElementType(GetHandle())==CELL);
Mesh * m = GetMeshLink(); Mesh * m = GetMeshLink();
ElementArray<Node> ret(m); ElementArray<Node> ret(m);
m->RestoreCellNodes(GetHandle(),ret); MarkerType mrk = m->CreatePrivateMarker();
HandleType hc = GetHandle();
if( !m->HideMarker() )
{
Element::adj_type & lc = m->LowConn(hc);
for(Element::adj_type::size_type i = 0; i < lc.size(); i++) //iterate over faces
{
Element::adj_type & ilc = m->LowConn(lc[i]);
for(Element::adj_type::size_type j = 0; j < ilc.size(); j++) //iterate over face edges
{
Element::adj_type & jlc = m->LowConn(ilc[j]);
for(Element::adj_type::size_type k = 0; k < jlc.size(); k++) //iterator over edge nodes
{
if( !m->GetPrivateMarker(jlc[k],mrk) )
{
m->SetPrivateMarker(jlc[k],mrk);
ret.push_back(jlc[k]);
}
}
}
}
}
else
{
MarkerType hm = m->HideMarker();
Element::adj_type & lc = m->LowConn(hc);
for(Element::adj_type::size_type i = 0; i < lc.size(); i++) if( !m->GetMarker(lc[i],hm) ) //iterate over faces
{
Element::adj_type & ilc = m->LowConn(lc[i]);
for(Element::adj_type::size_type j = 0; j < ilc.size(); j++) if( !m->GetMarker(ilc[j],hm) ) //iterate over face edges
{
Element::adj_type & jlc = m->LowConn(ilc[j]);
for(Element::adj_type::size_type k = 0; k < jlc.size(); k++) if( !m->GetMarker(jlc[k],hm) ) //iterator over edge nodes
{
if( !m->GetPrivateMarker(jlc[k], mrk) )
{
m->SetPrivateMarker(jlc[k],mrk);
ret.push_back(jlc[k]);
}
}
}
}
}
ret.RemPrivateMarker(mrk);
m->ReleasePrivateMarker(mrk);
return ret; return ret;
} }
...@@ -306,21 +350,100 @@ namespace INMOST ...@@ -306,21 +350,100 @@ namespace INMOST
{ {
assert(GetHandleElementType(GetHandle())==CELL); assert(GetHandleElementType(GetHandle())==CELL);
Mesh * m = GetMeshLink(); Mesh * m = GetMeshLink();
ElementArray<Node> aret(m), ret(m); ElementArray<Node> ret(m);
m->RestoreCellNodes(GetHandle(),ret); MarkerType mrk = m->CreatePrivateMarker();
HandleType hc = GetHandle();
if( isPrivate(mask) ) if( isPrivate(mask) )
{ {
for(ElementArray<Node>::iterator it = ret.begin(); it != ret.end(); ++it) if( !m->HideMarker() )
if( invert ^ m->GetPrivateMarker(*it,mask) ) {
aret.push_back(*it); Element::adj_type & lc = m->LowConn(hc);
for(Element::adj_type::size_type i = 0; i < lc.size(); i++) //iterate over faces
{
Element::adj_type & ilc = m->LowConn(lc[i]);
for(Element::adj_type::size_type j = 0; j < ilc.size(); j++) //iterate over face edges
{
Element::adj_type & jlc = m->LowConn(ilc[j]);
for(Element::adj_type::size_type k = 0; k < jlc.size(); k++) //iterator over edge nodes
{
if( (invert ^ m->GetPrivateMarker(jlc[k],mask)) && !m->GetPrivateMarker(jlc[k],mrk) )
{
m->SetPrivateMarker(jlc[k],mrk);
ret.push_back(jlc[k]);
}
}
}
}
}
else
{
MarkerType hm = m->HideMarker();
Element::adj_type & lc = m->LowConn(hc);
for(Element::adj_type::size_type i = 0; i < lc.size(); i++) if( !m->GetMarker(lc[i],hm) ) //iterate over faces
{
Element::adj_type & ilc = m->LowConn(lc[i]);
for(Element::adj_type::size_type j = 0; j < ilc.size(); j++) if( !m->GetMarker(ilc[j],hm) ) //iterate over face edges
{
Element::adj_type & jlc = m->LowConn(ilc[j]);
for(Element::adj_type::size_type k = 0; k < jlc.size(); k++) if( !m->GetMarker(jlc[k],hm) ) //iterator over edge nodes
{
if( (invert ^ m->GetPrivateMarker(jlc[k],mask)) && !m->GetPrivateMarker(jlc[k], mrk) )
{
m->SetPrivateMarker(jlc[k],mrk);
ret.push_back(jlc[k]);
}
}
}
}
}
} }
else else
{ {
for(ElementArray<Node>::iterator it = ret.begin(); it != ret.end(); ++it) if( !m->HideMarker() )
if( invert ^ m->GetMarker(*it,mask) ) {
aret.push_back(*it); Element::adj_type & lc = m->LowConn(hc);
for(Element::adj_type::size_type i = 0; i < lc.size(); i++) //iterate over faces
{
Element::adj_type & ilc = m->LowConn(lc[i]);
for(Element::adj_type::size_type j = 0; j < ilc.size(); j++) //iterate over face edges
{
Element::adj_type & jlc = m->LowConn(ilc[j]);
for(Element::adj_type::size_type k = 0; k < jlc.size(); k++) //iterator over edge nodes
{
if( (invert ^ m->GetMarker(jlc[k],mask)) && !m->GetPrivateMarker(jlc[k],mrk) )
{
m->SetPrivateMarker(jlc[k],mrk);
ret.push_back(jlc[k]);
}
}
}
}
}
else
{
MarkerType hm = m->HideMarker();
Element::adj_type & lc = m->LowConn(hc);
for(Element::adj_type::size_type i = 0; i < lc.size(); i++) if( !m->GetMarker(lc[i],hm) ) //iterate over faces
{
Element::adj_type & ilc = m->LowConn(lc[i]);
for(Element::adj_type::size_type j = 0; j < ilc.size(); j++) if( !m->GetMarker(ilc[j],hm) ) //iterate over face edges
{
Element::adj_type & jlc = m->LowConn(ilc[j]);
for(Element::adj_type::size_type k = 0; k < jlc.size(); k++) if( !m->GetMarker(jlc[k],hm) ) //iterator over edge nodes
{
if( (invert ^ m->GetMarker(jlc[k],mask)) && !m->GetPrivateMarker(jlc[k], mrk) )
{
m->SetPrivateMarker(jlc[k],mrk);
ret.push_back(jlc[k]);
}
}
}
}
}
} }
return aret; ret.RemPrivateMarker(mrk);
m->ReleasePrivateMarker(mrk);
return ret;
} }
ElementArray<Edge> Cell::getEdges() const ElementArray<Edge> Cell::getEdges() const
......
This diff is collapsed.
...@@ -902,10 +902,12 @@ namespace INMOST ...@@ -902,10 +902,12 @@ namespace INMOST
{ {
ENTER_FUNC(); ENTER_FUNC();
#if defined(USE_MPI) #if defined(USE_MPI)
static std::vector<Storage::real> temp; std::vector<Storage::real> temp(size);
temp.resize(size); if( !temp.empty() )
memcpy(temp.data(),input,sizeof(Storage::real)*size); {
REPORT_MPI(MPI_Allreduce(temp.data(),input,size,INMOST_MPI_DATA_REAL_TYPE,MPI_SUM,comm)); memcpy(&temp[0],input,sizeof(Storage::real)*size);
REPORT_MPI(MPI_Allreduce(&temp[0],input,size,INMOST_MPI_DATA_REAL_TYPE,MPI_SUM,comm));
}
#else//USE_MPI #else//USE_MPI
(void) input; (void) input;
(void) size; (void) size;
...@@ -917,10 +919,12 @@ namespace INMOST ...@@ -917,10 +919,12 @@ namespace INMOST
{ {
ENTER_FUNC(); ENTER_FUNC();
#if defined(USE_MPI) #if defined(USE_MPI)
static std::vector<Storage::integer> temp; std::vector<Storage::integer> temp(size);
temp.resize(size); if( !temp.empty() )
memcpy(temp.data(),input,sizeof(Storage::integer)*size); {
REPORT_MPI(MPI_Allreduce(temp.data(),input,size,INMOST_MPI_DATA_INTEGER_TYPE,MPI_SUM,comm)); memcpy(&temp[0],input,sizeof(Storage::integer)*size);
REPORT_MPI(MPI_Allreduce(&temp[0],input,size,INMOST_MPI_DATA_INTEGER_TYPE,MPI_SUM,comm));
}
#else//USE_MPI #else//USE_MPI
(void) input; (void) input;
(void) size; (void) size;
...@@ -932,10 +936,12 @@ namespace INMOST ...@@ -932,10 +936,12 @@ namespace INMOST
{ {
ENTER_FUNC(); ENTER_FUNC();
#if defined(USE_MPI) #if defined(USE_MPI)
static std::vector<Storage::enumerator> temp; std::vector<Storage::enumerator> temp(size);
temp.resize(size); if( !temp.empty() )
memcpy(temp.data(),input,sizeof(Storage::enumerator)*size); {
REPORT_MPI(MPI_Allreduce(temp.data(),input,size,INMOST_MPI_DATA_ENUM_TYPE,MPI_SUM,comm)); memcpy(&temp[0],input,sizeof(Storage::enumerator)*size);
REPORT_MPI(MPI_Allreduce(&temp[0],input,size,INMOST_MPI_DATA_ENUM_TYPE,MPI_SUM,comm));
}
#else//USE_MPI #else//USE_MPI
(void) input; (void) input;
(void) size; (void) size;
...@@ -1032,10 +1038,12 @@ namespace INMOST ...@@ -1032,10 +1038,12 @@ namespace INMOST
{ {
ENTER_FUNC(); ENTER_FUNC();
#if defined(USE_MPI) #if defined(USE_MPI)
static std::vector<Storage::real> temp; std::vector<Storage::real> temp(size);
temp.resize(size); if( !temp.empty() )
memcpy(temp.data(),input,sizeof(Storage::real)*size); {
REPORT_MPI(MPI_Allreduce(temp.data(),input,size,INMOST_MPI_DATA_REAL_TYPE,MPI_MAX,comm)); memcpy(&temp[0],input,sizeof(Storage::real)*size);
REPORT_MPI(MPI_Allreduce(&temp[0],input,size,INMOST_MPI_DATA_REAL_TYPE,MPI_MAX,comm));
}
#else//USE_MPI #else//USE_MPI
(void) input; (void) input;
(void) size; (void) size;
...@@ -1047,10 +1055,12 @@ namespace INMOST ...@@ -1047,10 +1055,12 @@ namespace INMOST
{ {
ENTER_FUNC(); ENTER_FUNC();
#if defined(USE_MPI) #if defined(USE_MPI)
static std::vector<Storage::integer> temp; std::vector<Storage::integer> temp(size);
temp.resize(size); if( !temp.empty() )
memcpy(temp.data(),input,sizeof(Storage::integer)*size); {
REPORT_MPI(MPI_Allreduce(temp.data(),input,size,INMOST_MPI_DATA_INTEGER_TYPE,MPI_MAX,comm)); memcpy(&temp[0],input,sizeof(Storage::integer)*size);
REPORT_MPI(MPI_Allreduce(&temp[0],input,size,INMOST_MPI_DATA_INTEGER_TYPE,MPI_MAX,comm));
}
#else//USE_MPI #else//USE_MPI
(void) input; (void) input;
(void) size; (void) size;
...@@ -1062,10 +1072,12 @@ namespace INMOST ...@@ -1062,10 +1072,12 @@ namespace INMOST
{ {
ENTER_FUNC(); ENTER_FUNC();
#if defined(USE_MPI) #if defined(USE_MPI)
static std::vector<Storage::enumerator> temp; std::vector<Storage::enumerator> temp(size);
temp.resize(size); if( !temp.empty() )
memcpy(temp.data(),input,sizeof(Storage::enumerator)*size); {
REPORT_MPI(MPI_Allreduce(temp.data(),input,size,INMOST_MPI_DATA_ENUM_TYPE,MPI_MAX,comm)); memcpy(&temp[0],input,sizeof(Storage::enumerator)*size);
REPORT_MPI(MPI_Allreduce(&temp[0],input,size,INMOST_MPI_DATA_ENUM_TYPE,MPI_MAX,comm));
}
#else//USE_MPI #else//USE_MPI
(void) input; (void) input;
(void) size; (void) size;
...@@ -1116,10 +1128,12 @@ namespace INMOST ...@@ -1116,10 +1128,12 @@ namespace INMOST
{ {
ENTER_FUNC(); ENTER_FUNC();
#if defined(USE_MPI) #if defined(USE_MPI)
static std::vector<Storage::real> temp; std::vector<Storage::real> temp(size);
temp.resize(size); if( !temp.empty() )
memcpy(temp.data(),input,sizeof(Storage::real)*size); {
REPORT_MPI(MPI_Allreduce(temp.data(),input,size,INMOST_MPI_DATA_REAL_TYPE,MPI_MIN,comm)); memcpy(&temp[0],input,sizeof(Storage::real)*size);
REPORT_MPI(MPI_Allreduce(&temp[0],input,size,INMOST_MPI_DATA_REAL_TYPE,MPI_MIN,comm));
}
#else//USE_MPI #else//USE_MPI
(void) input; (void) input;
(void) size; (void) size;
...@@ -1131,10 +1145,12 @@ namespace INMOST ...@@ -1131,10 +1145,12 @@ namespace INMOST
{ {
ENTER_FUNC(); ENTER_FUNC();
#if defined(USE_MPI) #if defined(USE_MPI)
static std::vector<Storage::enumerator> temp; std::vector<Storage::enumerator> temp(size);
temp.resize(size); if( !temp.empty() )
memcpy(temp.data(),input,sizeof(Storage::enumerator)*size); {
REPORT_MPI(MPI_Allreduce(temp.data(),input,size,INMOST_MPI_DATA_ENUM_TYPE,MPI_MIN,comm)); memcpy(&temp[0],input,sizeof(Storage::enumerator)*size);
REPORT_MPI(MPI_Allreduce(&temp[0],input,size,INMOST_MPI_DATA_ENUM_TYPE,MPI_MIN,comm));
}
#else//USE_MPI #else//USE_MPI
(void) input; (void) input;
(void) size; (void) size;
...@@ -1146,10 +1162,12 @@ namespace INMOST ...@@ -1146,10 +1162,12 @@ namespace INMOST
{ {
ENTER_FUNC(); ENTER_FUNC();
#if defined(USE_MPI) #if defined(USE_MPI)
static std::vector<Storage::integer> temp; std::vector<Storage::integer> temp(size);
temp.resize(size); if( !temp.empty() )
memcpy(temp.data(),input,sizeof(Storage::integer)*size); {
REPORT_MPI(MPI_Allreduce(temp.data(),input,size,INMOST_MPI_DATA_INTEGER_TYPE,MPI_MIN,comm)); memcpy(&temp[0],input,sizeof(Storage::integer)*size);
REPORT_MPI(MPI_Allreduce(&temp[0],input,size,INMOST_MPI_DATA_INTEGER_TYPE,MPI_MIN,comm));
}
#else//USE_MPI #else//USE_MPI
(void) input; (void) input;
(void) size; (void) size;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment