mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-28 11:18:18 +00:00
Use fuzzy boolean subtraction if available
Remove collinear points from polygonal halfspace boundary Lookup styled items on boolean operation operands Fixes https://github.com/IfcOpenShell/IfcOpenShell/issues/6 Fixes https://sourceforge.net/p/ifcopenshell/discussion/1782717/thread/a7f99e41/?limit=25#5c36
This commit is contained in:
@@ -109,6 +109,7 @@ public:
|
|||||||
bool convert_openings(const IfcSchema::IfcProduct* entity, const IfcSchema::IfcRelVoidsElement::list::ptr& openings, const IfcRepresentationShapeItems& entity_shapes, const gp_Trsf& entity_trsf, IfcRepresentationShapeItems& cut_shapes);
|
bool convert_openings(const IfcSchema::IfcProduct* entity, const IfcSchema::IfcRelVoidsElement::list::ptr& openings, const IfcRepresentationShapeItems& entity_shapes, const gp_Trsf& entity_trsf, IfcRepresentationShapeItems& cut_shapes);
|
||||||
bool convert_openings_fast(const IfcSchema::IfcProduct* entity, const IfcSchema::IfcRelVoidsElement::list::ptr& openings, const IfcRepresentationShapeItems& entity_shapes, const gp_Trsf& entity_trsf, IfcRepresentationShapeItems& cut_shapes);
|
bool convert_openings_fast(const IfcSchema::IfcProduct* entity, const IfcSchema::IfcRelVoidsElement::list::ptr& openings, const IfcRepresentationShapeItems& entity_shapes, const gp_Trsf& entity_trsf, IfcRepresentationShapeItems& cut_shapes);
|
||||||
IfcSchema::IfcSurfaceStyleShading* get_surface_style(IfcSchema::IfcRepresentationItem* item);
|
IfcSchema::IfcSurfaceStyleShading* get_surface_style(IfcSchema::IfcRepresentationItem* item);
|
||||||
|
const IfcSchema::IfcRepresentationItem* find_item_carrying_style(const IfcSchema::IfcRepresentationItem* item);
|
||||||
bool create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& solid);
|
bool create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& solid);
|
||||||
bool is_compound(const TopoDS_Shape& shape);
|
bool is_compound(const TopoDS_Shape& shape);
|
||||||
bool is_convex(const TopoDS_Wire& wire);
|
bool is_convex(const TopoDS_Wire& wire);
|
||||||
@@ -123,7 +124,10 @@ public:
|
|||||||
void setValue(GeomValue var, double value);
|
void setValue(GeomValue var, double value);
|
||||||
double getValue(GeomValue var) const;
|
double getValue(GeomValue var) const;
|
||||||
bool fill_nonmanifold_wires_with_planar_faces(TopoDS_Shape& shape);
|
bool fill_nonmanifold_wires_with_planar_faces(TopoDS_Shape& shape);
|
||||||
void remove_redundant_points_from_loop(TColgp_SequenceOfPnt& polygon, bool closed, double tol=-1.);
|
void remove_duplicate_points_from_loop(TColgp_SequenceOfPnt& polygon, bool closed, double tol=-1.);
|
||||||
|
void remove_collinear_points_from_loop(TColgp_SequenceOfPnt& polygon, bool closed, double tol=-1.);
|
||||||
|
bool wire_to_sequence_of_point(const TopoDS_Wire&, TColgp_SequenceOfPnt&);
|
||||||
|
void sequence_of_point_to_wire(const TColgp_SequenceOfPnt&, TopoDS_Wire&, bool closed);
|
||||||
|
|
||||||
std::pair<std::string, double> initializeUnits(IfcSchema::IfcUnitAssignment*);
|
std::pair<std::string, double> initializeUnits(IfcSchema::IfcUnitAssignment*);
|
||||||
|
|
||||||
@@ -135,6 +139,10 @@ public:
|
|||||||
const SurfaceStyle* get_style(const IfcSchema::IfcRepresentationItem* representation_item);
|
const SurfaceStyle* get_style(const IfcSchema::IfcRepresentationItem* representation_item);
|
||||||
|
|
||||||
template <typename T> std::pair<IfcSchema::IfcSurfaceStyle*, T*> get_surface_style(const IfcSchema::IfcRepresentationItem* representation_item) {
|
template <typename T> std::pair<IfcSchema::IfcSurfaceStyle*, T*> get_surface_style(const IfcSchema::IfcRepresentationItem* representation_item) {
|
||||||
|
// For certain representation items, most notably boolean operands,
|
||||||
|
// a style definition might reside on one of its operands.
|
||||||
|
representation_item = find_item_carrying_style(representation_item);
|
||||||
|
|
||||||
IfcSchema::IfcStyledItem::list::ptr styled_items = representation_item->StyledByItem();
|
IfcSchema::IfcStyledItem::list::ptr styled_items = representation_item->StyledByItem();
|
||||||
for (IfcSchema::IfcStyledItem::list::it jt = styled_items->begin(); jt != styled_items->end(); ++jt) {
|
for (IfcSchema::IfcStyledItem::list::it jt = styled_items->begin(); jt != styled_items->end(); ++jt) {
|
||||||
#ifdef USE_IFC4
|
#ifdef USE_IFC4
|
||||||
|
|||||||
@@ -100,6 +100,8 @@
|
|||||||
#include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
|
#include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
|
||||||
#include <TopTools_ListIteratorOfListOfShape.hxx>
|
#include <TopTools_ListIteratorOfListOfShape.hxx>
|
||||||
|
|
||||||
|
#include <Standard_Version.hxx>
|
||||||
|
|
||||||
#include "../ifcparse/IfcSIPrefix.h"
|
#include "../ifcparse/IfcSIPrefix.h"
|
||||||
#include "../ifcgeom/IfcGeom.h"
|
#include "../ifcgeom/IfcGeom.h"
|
||||||
|
|
||||||
@@ -232,7 +234,21 @@ bool IfcGeom::Kernel::convert_openings(const IfcSchema::IfcProduct* entity, cons
|
|||||||
TopExp_Explorer exp(entity_shape, TopAbs_SOLID);
|
TopExp_Explorer exp(entity_shape, TopAbs_SOLID);
|
||||||
|
|
||||||
for (; exp.More(); exp.Next()) {
|
for (; exp.More(); exp.Next()) {
|
||||||
|
|
||||||
|
#if OCC_VERSION_HEX < 0x60900
|
||||||
BRepAlgoAPI_Cut brep_cut(exp.Current(), opening_shape);
|
BRepAlgoAPI_Cut brep_cut(exp.Current(), opening_shape);
|
||||||
|
#else
|
||||||
|
BRepAlgoAPI_Cut brep_cut;
|
||||||
|
TopTools_ListOfShape s1s;
|
||||||
|
s1s.Append(exp.Current());
|
||||||
|
TopTools_ListOfShape s2s;
|
||||||
|
s2s.Append(opening_shape);
|
||||||
|
brep_cut.SetFuzzyValue(getValue(GV_PRECISION));
|
||||||
|
brep_cut.SetArguments(s1s);
|
||||||
|
brep_cut.SetTools(s2s);
|
||||||
|
brep_cut.Build();
|
||||||
|
#endif
|
||||||
|
|
||||||
bool added = false;
|
bool added = false;
|
||||||
if ( brep_cut.IsDone() ) {
|
if ( brep_cut.IsDone() ) {
|
||||||
TopoDS_Shape brep_cut_result = brep_cut;
|
TopoDS_Shape brep_cut_result = brep_cut;
|
||||||
@@ -257,7 +273,19 @@ bool IfcGeom::Kernel::convert_openings(const IfcSchema::IfcProduct* entity, cons
|
|||||||
entity_shape = compound;
|
entity_shape = compound;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
#if OCC_VERSION_HEX < 0x60900
|
||||||
BRepAlgoAPI_Cut brep_cut(entity_shape,opening_shape);
|
BRepAlgoAPI_Cut brep_cut(entity_shape,opening_shape);
|
||||||
|
#else
|
||||||
|
BRepAlgoAPI_Cut brep_cut;
|
||||||
|
TopTools_ListOfShape s1s;
|
||||||
|
s1s.Append(entity_shape);
|
||||||
|
TopTools_ListOfShape s2s;
|
||||||
|
s2s.Append(opening_shape);
|
||||||
|
brep_cut.SetFuzzyValue(getValue(GV_PRECISION));
|
||||||
|
brep_cut.SetArguments(s1s);
|
||||||
|
brep_cut.SetTools(s2s);
|
||||||
|
brep_cut.Build();
|
||||||
|
#endif
|
||||||
|
|
||||||
if ( brep_cut.IsDone() ) {
|
if ( brep_cut.IsDone() ) {
|
||||||
TopoDS_Shape brep_cut_result = brep_cut;
|
TopoDS_Shape brep_cut_result = brep_cut;
|
||||||
@@ -354,7 +382,20 @@ bool IfcGeom::Kernel::convert_openings_fast(const IfcSchema::IfcProduct* entity,
|
|||||||
entity_shape = entity_shape_unlocated.Moved(entity_shape_gtrsf.Trsf());
|
entity_shape = entity_shape_unlocated.Moved(entity_shape_gtrsf.Trsf());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if OCC_VERSION_HEX < 0x60900
|
||||||
BRepAlgoAPI_Cut brep_cut(entity_shape,opening_compound);
|
BRepAlgoAPI_Cut brep_cut(entity_shape,opening_compound);
|
||||||
|
#else
|
||||||
|
BRepAlgoAPI_Cut brep_cut;
|
||||||
|
TopTools_ListOfShape s1s;
|
||||||
|
s1s.Append(entity_shape);
|
||||||
|
TopTools_ListOfShape s2s;
|
||||||
|
s2s.Append(opening_compound);
|
||||||
|
brep_cut.SetFuzzyValue(getValue(GV_PRECISION));
|
||||||
|
brep_cut.SetArguments(s1s);
|
||||||
|
brep_cut.SetTools(s2s);
|
||||||
|
brep_cut.Build();
|
||||||
|
#endif
|
||||||
|
|
||||||
bool is_valid = false;
|
bool is_valid = false;
|
||||||
if ( brep_cut.IsDone() ) {
|
if ( brep_cut.IsDone() ) {
|
||||||
TopoDS_Shape brep_cut_result = brep_cut;
|
TopoDS_Shape brep_cut_result = brep_cut;
|
||||||
@@ -836,7 +877,7 @@ bool IfcGeom::Kernel::flatten_shape_list(const IfcGeom::IfcRepresentationShapeIt
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IfcGeom::Kernel::remove_redundant_points_from_loop(TColgp_SequenceOfPnt& polygon, bool closed, double tol) {
|
void IfcGeom::Kernel::remove_duplicate_points_from_loop(TColgp_SequenceOfPnt& polygon, bool closed, double tol) {
|
||||||
if (tol <= 0.) tol = getValue(GV_POINT_EQUALITY_TOLERANCE);
|
if (tol <= 0.) tol = getValue(GV_POINT_EQUALITY_TOLERANCE);
|
||||||
tol *= tol;
|
tol *= tol;
|
||||||
|
|
||||||
@@ -860,6 +901,69 @@ void IfcGeom::Kernel::remove_redundant_points_from_loop(TColgp_SequenceOfPnt& po
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IfcGeom::Kernel::remove_collinear_points_from_loop(TColgp_SequenceOfPnt& polygon, bool closed, double tol) {
|
||||||
|
if (tol <= 0.) tol = getValue(GV_POINT_EQUALITY_TOLERANCE);
|
||||||
|
const int start = closed ? 1 : 2;
|
||||||
|
const int end = polygon.Length() - (closed ? 0 : 1);
|
||||||
|
std::vector<bool> to_remove(polygon.Length(), false);
|
||||||
|
for (int i = start; i <= end; ++i) {
|
||||||
|
const gp_Pnt& a = polygon.Value(((i - 2 + polygon.Length()) % polygon.Length()) + 1);
|
||||||
|
const gp_Pnt& b = polygon.Value(i);
|
||||||
|
const gp_Pnt& c = polygon.Value((i % polygon.Length()) + 1);
|
||||||
|
const gp_Vec d1 = c.XYZ() - a.XYZ();
|
||||||
|
const gp_Vec d2 = b.XYZ() - a.XYZ();
|
||||||
|
const double dt = d2.Dot(d1) / d1.Dot(d1);
|
||||||
|
const gp_Vec d3 = d1.Scaled(dt);
|
||||||
|
const gp_Pnt b2 = a.XYZ() + d3.XYZ();
|
||||||
|
if (b.Distance(b2) < tol) {
|
||||||
|
to_remove[i-1] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = to_remove.size() - 1; i >= 0; --i) {
|
||||||
|
if (to_remove[i]) {
|
||||||
|
polygon.Remove(i+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IfcGeom::Kernel::wire_to_sequence_of_point(const TopoDS_Wire& w, TColgp_SequenceOfPnt& p) {
|
||||||
|
TopExp_Explorer exp(w, TopAbs_EDGE);
|
||||||
|
for (; exp.More(); exp.Next()) {
|
||||||
|
double a, b;
|
||||||
|
Handle_Geom_Curve crv = BRep_Tool::Curve(TopoDS::Edge(exp.Current()), a, b);
|
||||||
|
if (crv->DynamicType() != STANDARD_TYPE(Geom_Line)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exp.ReInit();
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (; exp.More(); exp.Next(), ++i) {
|
||||||
|
TopoDS_Vertex v1, v2;
|
||||||
|
TopExp::Vertices(TopoDS::Edge(exp.Current()), v1, v2, true);
|
||||||
|
if (exp.More()) {
|
||||||
|
if (i == 0) {
|
||||||
|
p.Append(BRep_Tool::Pnt(v1));
|
||||||
|
}
|
||||||
|
p.Append(BRep_Tool::Pnt(v2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IfcGeom::Kernel::sequence_of_point_to_wire(const TColgp_SequenceOfPnt& p, TopoDS_Wire& w, bool close) {
|
||||||
|
BRepBuilderAPI_MakePolygon builder;
|
||||||
|
for (int i = 1; i <= p.Length(); ++i) {
|
||||||
|
builder.Add(p.Value(i));
|
||||||
|
}
|
||||||
|
if (close) {
|
||||||
|
builder.Close();
|
||||||
|
}
|
||||||
|
w = builder.Wire();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename P>
|
template <typename P>
|
||||||
IfcGeom::BRepElement<P>* IfcGeom::Kernel::create_brep_for_representation_and_product(const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product) {
|
IfcGeom::BRepElement<P>* IfcGeom::Kernel::create_brep_for_representation_and_product(const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product) {
|
||||||
IfcGeom::Representation::BRep* shape;
|
IfcGeom::Representation::BRep* shape;
|
||||||
@@ -1083,4 +1187,29 @@ std::pair<std::string, double> IfcGeom::Kernel::initializeUnits(IfcSchema::IfcUn
|
|||||||
}
|
}
|
||||||
|
|
||||||
return std::pair<std::string, double>(unit_name, unit_magnitude);
|
return std::pair<std::string, double>(unit_name, unit_magnitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
const IfcSchema::IfcRepresentationItem* IfcGeom::Kernel::find_item_carrying_style(const IfcSchema::IfcRepresentationItem* item) {
|
||||||
|
IfcSchema::IfcStyledItem::list::ptr styles = item->StyledByItem();
|
||||||
|
if (styles->size()) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (item->is(IfcSchema::Type::IfcBooleanClippingResult)) {
|
||||||
|
// All instantiations of IfcBooleanOperand (type of FirstOperand) are subtypes of
|
||||||
|
// IfcGeometricRepresentationItem
|
||||||
|
item = (IfcSchema::IfcGeometricRepresentationItem*) ((IfcSchema::IfcBooleanClippingResult*) item)->FirstOperand();
|
||||||
|
|
||||||
|
IfcSchema::IfcStyledItem::list::ptr styles = item->StyledByItem();
|
||||||
|
if (styles->size()) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Ideally this would be done for other entities (such as IfcCsgSolid) as well.
|
||||||
|
// But neither are these very prevalent, nor does the current IfcOpenShell style
|
||||||
|
// mechanism enable to conveniently style subshapes, which would be necessary for
|
||||||
|
// distinctly styled union operands.
|
||||||
|
|
||||||
|
return item;
|
||||||
}
|
}
|
||||||
@@ -249,10 +249,20 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcHalfSpaceSolid* l, TopoDS_Shap
|
|||||||
bool IfcGeom::Kernel::convert(const IfcSchema::IfcPolygonalBoundedHalfSpace* l, TopoDS_Shape& shape) {
|
bool IfcGeom::Kernel::convert(const IfcSchema::IfcPolygonalBoundedHalfSpace* l, TopoDS_Shape& shape) {
|
||||||
TopoDS_Shape halfspace;
|
TopoDS_Shape halfspace;
|
||||||
if ( ! IfcGeom::Kernel::convert((IfcSchema::IfcHalfSpaceSolid*)l,halfspace) ) return false;
|
if ( ! IfcGeom::Kernel::convert((IfcSchema::IfcHalfSpaceSolid*)l,halfspace) ) return false;
|
||||||
|
|
||||||
TopoDS_Wire wire;
|
TopoDS_Wire wire;
|
||||||
if ( ! convert_wire(l->PolygonalBoundary(),wire) || ! wire.Closed() ) return false;
|
if ( ! convert_wire(l->PolygonalBoundary(),wire) || ! wire.Closed() ) return false;
|
||||||
|
|
||||||
gp_Trsf trsf;
|
gp_Trsf trsf;
|
||||||
convert(l->Position(),trsf);
|
if ( ! convert(l->Position(),trsf) ) return false;
|
||||||
|
|
||||||
|
TColgp_SequenceOfPnt points;
|
||||||
|
if (wire_to_sequence_of_point(wire, points)) {
|
||||||
|
remove_duplicate_points_from_loop(points, wire.Closed()); // Note: wire always closed, as per if statement above
|
||||||
|
remove_collinear_points_from_loop(points, wire.Closed());
|
||||||
|
sequence_of_point_to_wire(points, wire, wire.Closed());
|
||||||
|
}
|
||||||
|
|
||||||
TopoDS_Shape prism = BRepPrimAPI_MakePrism(BRepBuilderAPI_MakeFace(wire),gp_Vec(0,0,200));
|
TopoDS_Shape prism = BRepPrimAPI_MakePrism(BRepBuilderAPI_MakeFace(wire),gp_Vec(0,0,200));
|
||||||
gp_Trsf down; down.SetTranslation(gp_Vec(0,0,-100.0));
|
gp_Trsf down; down.SetTranslation(gp_Vec(0,0,-100.0));
|
||||||
prism.Move(trsf*down);
|
prism.Move(trsf*down);
|
||||||
@@ -277,6 +287,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcShellBasedSurfaceModel* l, Ifc
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IfcGeom::Kernel::convert(const IfcSchema::IfcBooleanResult* l, TopoDS_Shape& shape) {
|
bool IfcGeom::Kernel::convert(const IfcSchema::IfcBooleanResult* l, TopoDS_Shape& shape) {
|
||||||
|
|
||||||
TopoDS_Shape s1, s2;
|
TopoDS_Shape s1, s2;
|
||||||
IfcRepresentationShapeItems items1, items2;
|
IfcRepresentationShapeItems items1, items2;
|
||||||
TopoDS_Wire boundary_wire;
|
TopoDS_Wire boundary_wire;
|
||||||
@@ -330,10 +341,36 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcBooleanResult* l, TopoDS_Shape
|
|||||||
|
|
||||||
const IfcSchema::IfcBooleanOperator::IfcBooleanOperator op = l->Operator();
|
const IfcSchema::IfcBooleanOperator::IfcBooleanOperator op = l->Operator();
|
||||||
|
|
||||||
|
/*
|
||||||
|
// TK: A little debugging trick to output both operands for visual inspection
|
||||||
|
|
||||||
|
BRep_Builder builder;
|
||||||
|
TopoDS_Compound compound;
|
||||||
|
builder.MakeCompound(compound);
|
||||||
|
builder.Add(compound, s1);
|
||||||
|
builder.Add(compound, s2);
|
||||||
|
shape = compound;
|
||||||
|
return true;
|
||||||
|
*/
|
||||||
|
|
||||||
if (op == IfcSchema::IfcBooleanOperator::IfcBooleanOperator_DIFFERENCE) {
|
if (op == IfcSchema::IfcBooleanOperator::IfcBooleanOperator_DIFFERENCE) {
|
||||||
|
|
||||||
bool valid_cut = false;
|
bool valid_cut = false;
|
||||||
BRepAlgoAPI_Cut brep_cut(s1,s2);
|
|
||||||
|
#if OCC_VERSION_HEX < 0x60900
|
||||||
|
BRepAlgoAPI_Cut brep_cut(s1, s2);
|
||||||
|
#else
|
||||||
|
BRepAlgoAPI_Cut brep_cut;
|
||||||
|
TopTools_ListOfShape s1s;
|
||||||
|
s1s.Append(s1);
|
||||||
|
TopTools_ListOfShape s2s;
|
||||||
|
s2s.Append(s2);
|
||||||
|
brep_cut.SetFuzzyValue(getValue(GV_PRECISION));
|
||||||
|
brep_cut.SetArguments(s1s);
|
||||||
|
brep_cut.SetTools(s2s);
|
||||||
|
brep_cut.Build();
|
||||||
|
#endif
|
||||||
|
|
||||||
if ( brep_cut.IsDone() ) {
|
if ( brep_cut.IsDone() ) {
|
||||||
TopoDS_Shape result = brep_cut;
|
TopoDS_Shape result = brep_cut;
|
||||||
|
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcPolyline* l, TopoDS_Wire& resu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove points that are too close to one another
|
// Remove points that are too close to one another
|
||||||
remove_redundant_points_from_loop(polygon, false);
|
remove_duplicate_points_from_loop(polygon, false);
|
||||||
|
|
||||||
BRepBuilderAPI_MakePolygon w;
|
BRepBuilderAPI_MakePolygon w;
|
||||||
for (int i = 1; i <= polygon.Length(); ++i) {
|
for (int i = 1; i <= polygon.Length(); ++i) {
|
||||||
@@ -314,7 +314,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcPolyLoop* l, TopoDS_Wire& resu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove points that are too close to one another
|
// Remove points that are too close to one another
|
||||||
remove_redundant_points_from_loop(polygon, true);
|
remove_duplicate_points_from_loop(polygon, true);
|
||||||
|
|
||||||
int count = polygon.Length();
|
int count = polygon.Length();
|
||||||
if (original_count - count != 0) {
|
if (original_count - count != 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user