mirror of
https://git.dev.opencascade.org/repos/occt.git
synced 2025-04-05 18:16:23 +03:00
0023994: GeomAPI_ExtremaCurveCurve class calculates wrong values
Adding test casefor this fix
This commit is contained in:
parent
03679c4858
commit
afed7fd721
@ -27,6 +27,10 @@ inherits Transient from Standard
|
|||||||
|
|
||||||
---Purpose:
|
---Purpose:
|
||||||
|
|
||||||
|
uses
|
||||||
|
Array1OfReal from TColStd,
|
||||||
|
HArray1OfReal from TColStd
|
||||||
|
|
||||||
raises NotDone from StdFail
|
raises NotDone from StdFail
|
||||||
|
|
||||||
is
|
is
|
||||||
@ -36,6 +40,12 @@ is
|
|||||||
Create (theC: Curve; theUFirst, theULast: Real; theNbSamples: Integer;
|
Create (theC: Curve; theUFirst, theULast: Real; theNbSamples: Integer;
|
||||||
theToCalculate: Boolean)returns mutable CurveCache from Extrema;
|
theToCalculate: Boolean)returns mutable CurveCache from Extrema;
|
||||||
|
|
||||||
|
Create (theC: Curve; theUFirst, theULast: Real;
|
||||||
|
IntervalsCN: Array1OfReal from TColStd;
|
||||||
|
StartIndex, EndIndex: Integer;
|
||||||
|
Coeff: Real)
|
||||||
|
returns mutable CurveCache from Extrema;
|
||||||
|
|
||||||
SetCurve (me: mutable; theC: Curve; theNbSamples: Integer;
|
SetCurve (me: mutable; theC: Curve; theNbSamples: Integer;
|
||||||
theToCalculate: Boolean);
|
theToCalculate: Boolean);
|
||||||
---Purpose: Sets the \a theRank-th curve (1 or 2) and its parameters.
|
---Purpose: Sets the \a theRank-th curve (1 or 2) and its parameters.
|
||||||
@ -57,6 +67,12 @@ is
|
|||||||
---Purpose: Returns True if the points array has already been calculated
|
---Purpose: Returns True if the points array has already been calculated
|
||||||
-- for specified curve and range
|
-- for specified curve and range
|
||||||
|
|
||||||
|
Parameters (me) returns HArray1OfReal from TColStd
|
||||||
|
raises NotDone from StdFail;
|
||||||
|
---C++: inline
|
||||||
|
---C++: return const &
|
||||||
|
---Purpose: Raises StdFail_NotDone if points have not yet been calculated.
|
||||||
|
|
||||||
Points (me) returns ArrayOfPnt
|
Points (me) returns ArrayOfPnt
|
||||||
raises NotDone from StdFail;
|
raises NotDone from StdFail;
|
||||||
---C++: inline
|
---C++: inline
|
||||||
@ -95,6 +111,7 @@ fields
|
|||||||
myTrimFirst : Real;
|
myTrimFirst : Real;
|
||||||
myTrimLast : Real;
|
myTrimLast : Real;
|
||||||
myNbSamples : Integer;
|
myNbSamples : Integer;
|
||||||
|
myParamArray : HArray1OfReal from TColStd;
|
||||||
myPntArray : ArrayOfPnt;
|
myPntArray : ArrayOfPnt;
|
||||||
myIsArrayValid: Boolean;
|
myIsArrayValid: Boolean;
|
||||||
|
|
||||||
|
@ -36,6 +36,64 @@ Extrema_CurveCache::Extrema_CurveCache(const Curve& theC,
|
|||||||
SetCurve (theC, theUFirst, theULast, theNbSamples, theToCalculate);
|
SetCurve (theC, theUFirst, theULast, theNbSamples, theToCalculate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//=======================================================================
|
||||||
|
//function : Extrema_CurveCache
|
||||||
|
//purpose : with sampling by knots and between them
|
||||||
|
//=======================================================================
|
||||||
|
|
||||||
|
Extrema_CurveCache::Extrema_CurveCache(const Curve& theC,
|
||||||
|
const Standard_Real theUFirst,
|
||||||
|
const Standard_Real theULast,
|
||||||
|
const TColStd_Array1OfReal& IntervalsCN,
|
||||||
|
const Standard_Integer StartIndex,
|
||||||
|
const Standard_Integer EndIndex,
|
||||||
|
const Standard_Real Coeff)
|
||||||
|
{
|
||||||
|
myC = (Standard_Address)&theC;
|
||||||
|
myIsArrayValid = Standard_False;
|
||||||
|
myParamArray.Nullify();
|
||||||
|
myPntArray.Nullify();
|
||||||
|
|
||||||
|
myTrimFirst = myFirst = theUFirst;
|
||||||
|
myTrimLast = myLast = theULast;
|
||||||
|
|
||||||
|
Standard_Integer Nbp = (Standard_Integer) (2 * Coeff);
|
||||||
|
myNbSamples = (EndIndex - StartIndex)*Nbp + 1;
|
||||||
|
|
||||||
|
const Standard_Integer aNbSTresh = 10000;
|
||||||
|
if (myNbSamples > aNbSTresh)
|
||||||
|
{
|
||||||
|
Nbp = aNbSTresh / (EndIndex - StartIndex);
|
||||||
|
myNbSamples = (EndIndex - StartIndex)*Nbp + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Cache points
|
||||||
|
myParamArray = new TColStd_HArray1OfReal(1, myNbSamples);
|
||||||
|
myPntArray = new ArrayOfPnt (1, myNbSamples);
|
||||||
|
|
||||||
|
const Curve& aCurve = *((Curve*)myC);
|
||||||
|
|
||||||
|
Standard_Integer i, j, k = 1;
|
||||||
|
Standard_Real aPar;
|
||||||
|
for (i = StartIndex; i < EndIndex; i++)
|
||||||
|
{
|
||||||
|
Standard_Real delta = (IntervalsCN(i+1) - IntervalsCN(i)) / Nbp;
|
||||||
|
for (j = 0; j < Nbp; j++)
|
||||||
|
{
|
||||||
|
aPar = IntervalsCN(i) + j*delta;
|
||||||
|
myParamArray->SetValue(k, aPar);
|
||||||
|
myPntArray->SetValue(k++, aCurve.Value(aPar));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Standard_Real aDelta = (myTrimLast - myTrimFirst) / myNbSamples / 200.;
|
||||||
|
myParamArray->SetValue(1, myTrimFirst + aDelta);
|
||||||
|
myPntArray->SetValue(1, aCurve.Value(myTrimFirst + aDelta));
|
||||||
|
myParamArray->SetValue(myNbSamples, myTrimLast - aDelta);
|
||||||
|
myPntArray->SetValue(myNbSamples, aCurve.Value(myTrimLast - aDelta));
|
||||||
|
|
||||||
|
myIsArrayValid = Standard_True; //cache is available now
|
||||||
|
}
|
||||||
|
|
||||||
//=======================================================================
|
//=======================================================================
|
||||||
//function : Extrema_CurveCache
|
//function : Extrema_CurveCache
|
||||||
//purpose :
|
//purpose :
|
||||||
@ -58,6 +116,7 @@ void Extrema_CurveCache::SetCurve (const Curve& theC,
|
|||||||
myC = (Standard_Address)&theC;
|
myC = (Standard_Address)&theC;
|
||||||
myNbSamples = theNbSamples;
|
myNbSamples = theNbSamples;
|
||||||
myIsArrayValid = Standard_False;
|
myIsArrayValid = Standard_False;
|
||||||
|
myParamArray.Nullify();
|
||||||
myPntArray.Nullify();
|
myPntArray.Nullify();
|
||||||
if (theToCalculate) {
|
if (theToCalculate) {
|
||||||
CalculatePoints();
|
CalculatePoints();
|
||||||
@ -99,6 +158,7 @@ void Extrema_CurveCache::SetRange (const Standard_Real theUFirst,
|
|||||||
}
|
}
|
||||||
|
|
||||||
myIsArrayValid = Standard_False;
|
myIsArrayValid = Standard_False;
|
||||||
|
myParamArray.Nullify();
|
||||||
myPntArray.Nullify();
|
myPntArray.Nullify();
|
||||||
if (theToCalculate) {
|
if (theToCalculate) {
|
||||||
CalculatePoints();
|
CalculatePoints();
|
||||||
@ -124,11 +184,13 @@ void Extrema_CurveCache::CalculatePoints()
|
|||||||
|
|
||||||
//Cache points
|
//Cache points
|
||||||
|
|
||||||
|
myParamArray = new TColStd_HArray1OfReal(1, myNbSamples);
|
||||||
myPntArray = new ArrayOfPnt (1, myNbSamples);
|
myPntArray = new ArrayOfPnt (1, myNbSamples);
|
||||||
|
|
||||||
Standard_Integer i;
|
Standard_Integer i;
|
||||||
Standard_Real aPar;
|
Standard_Real aPar;
|
||||||
for (i = 1, aPar = aPar0; i <= myNbSamples; i++, aPar += aDelta) {
|
for (i = 1, aPar = aPar0; i <= myNbSamples; i++, aPar += aDelta) {
|
||||||
|
myParamArray->SetValue(i, aPar);
|
||||||
myPntArray->SetValue (i, aCurve.Value (aPar));
|
myPntArray->SetValue (i, aCurve.Value (aPar));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
// roman.lygin@gmail.com
|
// roman.lygin@gmail.com
|
||||||
|
|
||||||
#include <StdFail_NotDone.hxx>
|
#include <StdFail_NotDone.hxx>
|
||||||
|
#include <TColStd_HArray1OfReal.hxx>
|
||||||
|
|
||||||
//=======================================================================
|
//=======================================================================
|
||||||
//function : IsValid
|
//function : IsValid
|
||||||
@ -31,6 +32,17 @@ inline Standard_Boolean Extrema_CurveCache::IsValid() const
|
|||||||
return myIsArrayValid;
|
return myIsArrayValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//=======================================================================
|
||||||
|
//function : Parameters
|
||||||
|
//purpose :
|
||||||
|
//=======================================================================
|
||||||
|
|
||||||
|
inline const Handle(TColStd_HArray1OfReal)& Extrema_CurveCache::Parameters() const
|
||||||
|
{
|
||||||
|
StdFail_NotDone_Raise_if (!myIsArrayValid, "Extrema_CurveCache::Parameters()")
|
||||||
|
return myParamArray;
|
||||||
|
}
|
||||||
|
|
||||||
//=======================================================================
|
//=======================================================================
|
||||||
//function : Points
|
//function : Points
|
||||||
//purpose :
|
//purpose :
|
||||||
|
@ -272,8 +272,11 @@ void Extrema_GExtCC::Perform()
|
|||||||
//common case - use _GenExtCC
|
//common case - use _GenExtCC
|
||||||
//1. check and prepare caches
|
//1. check and prepare caches
|
||||||
|
|
||||||
Standard_Integer i, aNbS[2] = {32, 32}, aNbInter[2] = {1, 1};
|
Standard_Integer i;
|
||||||
|
Standard_Integer aNbS[2] = {32, 32}, aNbInter[2] = {1, 1};
|
||||||
|
Standard_Real Coeff[2] = {1., 1.};
|
||||||
Standard_Real rf = 0., rl = 0., LL[2] = {-1., -1.};
|
Standard_Real rf = 0., rl = 0., LL[2] = {-1., -1.};
|
||||||
|
Standard_Boolean KnotSampling[2] = {Standard_False, Standard_False};
|
||||||
for (i = 0; i < 2; i++) {
|
for (i = 0; i < 2; i++) {
|
||||||
TColStd_ListOfTransient& aCacheList = myCacheLists[i];
|
TColStd_ListOfTransient& aCacheList = myCacheLists[i];
|
||||||
if (aCacheList.IsEmpty()) {
|
if (aCacheList.IsEmpty()) {
|
||||||
@ -288,10 +291,14 @@ void Extrema_GExtCC::Perform()
|
|||||||
aNbS[i] = aC.NbPoles() * 2;
|
aNbS[i] = aC.NbPoles() * 2;
|
||||||
break;
|
break;
|
||||||
case GeomAbs_BSplineCurve:
|
case GeomAbs_BSplineCurve:
|
||||||
|
if (aC.Degree() <= 3 &&
|
||||||
|
aC.NbKnots() > 2)
|
||||||
|
KnotSampling[i] = Standard_True;
|
||||||
|
|
||||||
aNbS[i] = aC.NbPoles() * 2;
|
aNbS[i] = aC.NbPoles() * 2;
|
||||||
rf = (Tool1::BSpline(*((Curve1*)myC[i])))->FirstParameter();
|
rf = (Tool1::BSpline(*((Curve1*)myC[i])))->FirstParameter();
|
||||||
rl = (Tool1::BSpline(*((Curve1*)myC[i])))->LastParameter();
|
rl = (Tool1::BSpline(*((Curve1*)myC[i])))->LastParameter();
|
||||||
aNbS[i] = (Standard_Integer) ( aNbS[i] * ((mySup[i] - myInf[i]) / (rl - rf)) + 1 );
|
aNbS[i] = (Standard_Integer) ( aNbS[i] * ((mySup[i] - myInf[i]) / (rl - rf)) + 1 );
|
||||||
case GeomAbs_OtherCurve:
|
case GeomAbs_OtherCurve:
|
||||||
//adjust number of sample points for B-Splines and Other curves
|
//adjust number of sample points for B-Splines and Other curves
|
||||||
aNbInter[i] = aC.NbIntervals (GeomAbs_C2);
|
aNbInter[i] = aC.NbIntervals (GeomAbs_C2);
|
||||||
@ -314,10 +321,12 @@ void Extrema_GExtCC::Perform()
|
|||||||
|
|
||||||
if(LL[0] > 0. && LL[1] > 0.) {
|
if(LL[0] > 0. && LL[1] > 0.) {
|
||||||
if(LL[0] > 4.*LL[1]) {
|
if(LL[0] > 4.*LL[1]) {
|
||||||
aNbS[0] = (Standard_Integer) ( aNbS[0] * LL[0]/LL[1]/2. );
|
Coeff[0] = LL[0]/LL[1]/2.;
|
||||||
|
aNbS[0] = (Standard_Integer) ( aNbS[0] * Coeff[0] );
|
||||||
}
|
}
|
||||||
else if(LL[1] > 4.*LL[0]) {
|
else if(LL[1] > 4.*LL[0]) {
|
||||||
aNbS[1] = (Standard_Integer) (aNbS[1] * LL[1]/LL[0]/2. );
|
Coeff[1] = LL[1]/LL[0]/2.;
|
||||||
|
aNbS[1] = (Standard_Integer) (aNbS[1] * Coeff[1] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//modified by NIZNHY-PKV Tue Apr 17 10:01:32 2012f
|
//modified by NIZNHY-PKV Tue Apr 17 10:01:32 2012f
|
||||||
@ -337,16 +346,48 @@ void Extrema_GExtCC::Perform()
|
|||||||
//no caches, build them
|
//no caches, build them
|
||||||
Curve1& aC = *(Curve1*)myC[i];
|
Curve1& aC = *(Curve1*)myC[i];
|
||||||
|
|
||||||
if (aC.GetType() >= GeomAbs_BSplineCurve) {
|
if (aC.GetType() >= GeomAbs_BSplineCurve)
|
||||||
|
{
|
||||||
//can be multiple intervals, one cache per one C2 interval
|
//can be multiple intervals, one cache per one C2 interval
|
||||||
TColStd_Array1OfReal anArr (1, aNbInter[i] + 1);
|
TColStd_Array1OfReal anArr (1, aNbInter[i] + 1);
|
||||||
aC.Intervals (anArr, GeomAbs_C2);
|
aC.Intervals (anArr, GeomAbs_C2);
|
||||||
for (Standard_Integer k = 1; k <= aNbInter[i]; k++) {
|
|
||||||
Handle(Extrema_CCache) aCache = new Extrema_CCache (aC,
|
if (KnotSampling[i])
|
||||||
anArr(k), anArr(k+1), aNbS[i], Standard_True);
|
{
|
||||||
aCacheList.Append (aCache);
|
Standard_Integer NbIntervCN = aC.NbIntervals(GeomAbs_CN);
|
||||||
|
TColStd_Array1OfReal IntervalsCN(1, NbIntervCN + 1);
|
||||||
|
aC.Intervals(IntervalsCN, GeomAbs_CN);
|
||||||
|
|
||||||
|
Standard_Integer j = 1, start_j = 1, k;
|
||||||
|
Standard_Real NextKnot;
|
||||||
|
for (k = 1; k <= aNbInter[i]; k++)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
NextKnot = IntervalsCN(j+1);
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
while (NextKnot != anArr(k+1));
|
||||||
|
|
||||||
|
Handle(Extrema_CCache) aCache =
|
||||||
|
new Extrema_CCache (aC, anArr(k), anArr(k+1),
|
||||||
|
IntervalsCN, start_j, j, Coeff[i]);
|
||||||
|
aCacheList.Append (aCache);
|
||||||
|
|
||||||
|
start_j = j;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
else
|
||||||
|
{
|
||||||
|
for (Standard_Integer k = 1; k <= aNbInter[i]; k++) {
|
||||||
|
Handle(Extrema_CCache) aCache =
|
||||||
|
new Extrema_CCache (aC, anArr(k), anArr(k+1), aNbS[i], Standard_True);
|
||||||
|
aCacheList.Append (aCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
//single interval from myInf[i] to mySup[i]
|
//single interval from myInf[i] to mySup[i]
|
||||||
Handle(Extrema_CCache) aCache = new Extrema_CCache (aC,
|
Handle(Extrema_CCache) aCache = new Extrema_CCache (aC,
|
||||||
myInf[i], mySup[i], aNbS[i], Standard_True);
|
myInf[i], mySup[i], aNbS[i], Standard_True);
|
||||||
|
@ -146,16 +146,6 @@ a- Constitution du tableau des distances (TbDist2(0,NbU+1,0,NbV+1)):
|
|||||||
aCache2->CalculatePoints();
|
aCache2->CalculatePoints();
|
||||||
|
|
||||||
// Calcul des distances
|
// Calcul des distances
|
||||||
//initialization of variables in the same way as in CalculatePoints()
|
|
||||||
Standard_Real PasU = aCache1->TrimLastParameter() - aCache1->TrimFirstParameter();
|
|
||||||
Standard_Real PasV = aCache2->TrimLastParameter() - aCache2->TrimFirstParameter();
|
|
||||||
Standard_Real U0 = PasU / aNbU / 100.;
|
|
||||||
Standard_Real V0 = PasV / aNbV / 100.;
|
|
||||||
PasU = (PasU - U0) / (aNbU - 1);
|
|
||||||
PasV = (PasV - V0) / (aNbV - 1);
|
|
||||||
U0 = aCache1->TrimFirstParameter() + (U0/2.);
|
|
||||||
V0 = aCache2->TrimFirstParameter() + (V0/2.);
|
|
||||||
|
|
||||||
const Curve1& aCurve1 = *((Curve1*)(myF.CurvePtr (1)));
|
const Curve1& aCurve1 = *((Curve1*)(myF.CurvePtr (1)));
|
||||||
const Curve2& aCurve2 = *((Curve1*)(myF.CurvePtr (2)));
|
const Curve2& aCurve2 = *((Curve1*)(myF.CurvePtr (2)));
|
||||||
const Handle(ArrayOfPnt)& aPntArray1 = aCache1->Points();
|
const Handle(ArrayOfPnt)& aPntArray1 = aCache1->Points();
|
||||||
@ -207,6 +197,8 @@ b- Calcul des minima:
|
|||||||
// - recherche d un minimum sur la grille
|
// - recherche d un minimum sur la grille
|
||||||
Standard_Integer Nu, Nv;
|
Standard_Integer Nu, Nv;
|
||||||
Standard_Real Dist2;
|
Standard_Real Dist2;
|
||||||
|
const Handle(TColStd_HArray1OfReal)& aParamArray1 = aCache1->Parameters();
|
||||||
|
const Handle(TColStd_HArray1OfReal)& aParamArray2 = aCache2->Parameters();
|
||||||
for (NoU = 1; NoU <= aNbU; NoU++) {
|
for (NoU = 1; NoU <= aNbU; NoU++) {
|
||||||
for (NoV = 1; NoV <= aNbV; NoV++) {
|
for (NoV = 1; NoV <= aNbV; NoV++) {
|
||||||
if (TbSel(NoU,NoV) == 0) {
|
if (TbSel(NoU,NoV) == 0) {
|
||||||
@ -221,9 +213,9 @@ b- Calcul des minima:
|
|||||||
(TbDist2(NoU+1,NoV+1) >= Dist2)) {
|
(TbDist2(NoU+1,NoV+1) >= Dist2)) {
|
||||||
|
|
||||||
// - calcul de l extremum sur la surface:
|
// - calcul de l extremum sur la surface:
|
||||||
UV(1) = U0 + (NoU - 1) * PasU;
|
|
||||||
UV(2) = V0 + (NoV - 1) * PasV;
|
UV(1) = aParamArray1->Value(NoU);
|
||||||
|
UV(2) = aParamArray2->Value(NoV);
|
||||||
|
|
||||||
math_FunctionSetRoot S (myF,UV,Tol,UVinf,UVsup);
|
math_FunctionSetRoot S (myF,UV,Tol,UVinf,UVsup);
|
||||||
|
|
||||||
@ -278,8 +270,9 @@ c- Calcul des maxima:
|
|||||||
(TbDist2(NoU+1,NoV+1) <= Dist2)) {
|
(TbDist2(NoU+1,NoV+1) <= Dist2)) {
|
||||||
|
|
||||||
// - calcul de l extremum sur la surface:
|
// - calcul de l extremum sur la surface:
|
||||||
UV(1) = U0 + (NoU - 1) * PasU;
|
|
||||||
UV(2) = V0 + (NoV - 1) * PasV;
|
UV(1) = aParamArray1->Value(NoU);
|
||||||
|
UV(2) = aParamArray2->Value(NoV);
|
||||||
|
|
||||||
math_FunctionSetRoot S (myF,UV,Tol,UVinf,UVsup);
|
math_FunctionSetRoot S (myF,UV,Tol,UVinf,UVsup);
|
||||||
|
|
||||||
|
42
tests/bugs/moddata_3/bug23994
Executable file
42
tests/bugs/moddata_3/bug23994
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
puts "================"
|
||||||
|
puts "OCC23994"
|
||||||
|
puts "================"
|
||||||
|
puts ""
|
||||||
|
#######################################################################
|
||||||
|
# GeomAPI_ExtremaCurveCurve class calculates wrong values
|
||||||
|
#######################################################################
|
||||||
|
|
||||||
|
set BugNumber CR23994
|
||||||
|
|
||||||
|
pload XDE
|
||||||
|
|
||||||
|
ReadStep D [locate_data_file bug23994_AirfoilRhomb_CalcDist_17_OP_Bell_Mouth_Roughing_shroud.stp]
|
||||||
|
|
||||||
|
XCheckProps D
|
||||||
|
|
||||||
|
XGetShape airflIntersctCrv D 0:1:1:1
|
||||||
|
XGetShape rhombIntersctCrv D 0:1:1:2
|
||||||
|
|
||||||
|
explode rhombIntersctCrv
|
||||||
|
mkcurve rhomb rhombIntersctCrv_1
|
||||||
|
|
||||||
|
explode airflIntersctCrv
|
||||||
|
mkcurve airfl airflIntersctCrv_1
|
||||||
|
|
||||||
|
extrema airfl rhomb
|
||||||
|
|
||||||
|
if { [isdraw ext_1] } {
|
||||||
|
mkedge result ext_1
|
||||||
|
set length 9.09515
|
||||||
|
} else {
|
||||||
|
puts "${BugNumber}: invalid result"
|
||||||
|
}
|
||||||
|
|
||||||
|
if { [isdraw ext_2] } {
|
||||||
|
mkedge result ext_2
|
||||||
|
set length 5.14563
|
||||||
|
} else {
|
||||||
|
puts "${BugNumber}: invalid result"
|
||||||
|
}
|
||||||
|
|
||||||
|
set 2dviewer 1
|
Loading…
x
Reference in New Issue
Block a user