Conversation
…ntations - Implement tests for converting hyperbolas to B-spline curves in Convert_HyperbolaToBSplineCurve_Test.cxx. - Implement tests for converting parabolas to B-spline curves in Convert_ParabolaToBSplineCurve_Test.cxx. - Implement tests for converting spheres to B-spline surfaces in Convert_SphereToBSplineSurface_Test.cxx. - Implement tests for converting tori to B-spline surfaces in Convert_TorusToBSplineSurface_Test.cxx. - Update FILES.cmake to include new test files. - Refactor GeomFill_PolynomialConvertor and GeomFill_QuasiAngularConvertor to handle deprecation warnings. - Modify AdvApprox_ApproxAFunction and related classes to use new pole access methods. - Update Geom_OsculatingSurface to use new pole access methods. - Refactor AdvApp2Var_ApproxAFunc2Var and related classes to use new pole access methods. - Simplify Geom2dConvert and GeomConvert functions to directly use new pole access methods.
…structures - Updated Geom_BezierSurface to utilize a new internal data structure (BSplSLib_SurfaceData) for managing poles, weights, and knot arrays. - Replaced handle-based accessors with direct access to internal arrays in Geom_BezierSurface. - Modified GeomGridEval_BSplineCurve, GeomGridEval_BSplineSurface, GeomGridEval_BezierCurve, and GeomGridEval_BezierSurface to use the new internal accessors for flat knots, poles, and weights. - Removed unnecessary handle types and streamlined data extraction processes for improved performance and readability.
- Removed unnecessary type aliases in BSplCLib_CurveData. - Reordered file inclusion in FILES.cmake for consistency. - Enhanced NCollection_Array2 with new resizing methods to improve data handling. - Simplified weight handling in Geom2d_BSplineCurve and Geom_BSplineCurve by removing redundant checks. - Updated knot and multiplicity handling in Geom_BSplineSurface to use new resizing methods. - Improved memory management in Geom_BezierCurve and Geom_BezierSurface by utilizing stack-allocated buffers for knots and multiplicities. - Changed comments to reflect the use of InternalFlatKnots in GeomGridEval_BSplineCurve for better clarity.
…and add tests for ResizeWithTrim functionality
… poles; enhance geometric verification tests for cone, cylinder, sphere, and torus conversions.
…lt values and improve memory management
… handling and improve memory management
…al and Bezier curve operations
… Bezier curves for weight handling
… BSpline curve constructors for improved memory management
- Removed dependency on BSplSLib_SurfaceData and replaced with direct usage of NCollection arrays for poles and weights. - Updated methods to return internal arrays for poles and weights. - Added new methods to retrieve Bezier knots and multiplicities for both U and V degrees. - Modified Geom_OsculatingSurface to accommodate changes in Geom_BezierSurface by accessing the new array methods directly.
…hods for accessing knots, poles, weights, and multiplicities - Deprecated old methods that returned arrays by reference and replaced them with new methods returning const references. - Updated internal implementations in Geom2d_BSplineCurve, Geom2d_BezierCurve, Geom_BSplineCurve, Geom_BezierCurve, and their respective .cxx files to utilize the new methods. - Adjusted the GeomGridEval classes to fetch data using the updated methods. - Removed unnecessary internal methods that provided access to weights and poles, streamlining the codebase.
…mber variable naming - Updated member variables in Geom_BezierCurve from `rational`, `closed`, `maxderivinv`, and `maxderivinvok` to `myRational`, `myClosed`, `myMaxDerivInv`, and `myMaxDerivInvOk` respectively for clarity and consistency. - Refactored corresponding methods to use the new member variable names. - Applied similar changes in Geom_BezierSurface, renaming `urational`, `vrational`, `umaxderivinv`, `vmaxderivinv`, and `maxderivinvok` to `myURational`, `myVRational`, `myUMaxDerivInv`, `myVMaxDerivInv`, and `myMaxDerivInvOk`. - Ensured that all references to the old variable names were updated throughout the class implementations.
…e calculations and improve code readability
…per default values
- Implemented tests for evaluation methods D0, D1, D2, D3, and DN. - Added tests for surface properties, bounds, and pole manipulation (set, insert, remove). - Included tests for weight handling in rational surfaces. - Verified behavior of surface transformations, segmentations, and isoparametric curves. - Ensured correct handling of rational surfaces and their properties. - Added tests for increasing surface degrees and checking maximum degree constraints.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
WalkthroughThis pull request refactors the internal data structures of B-spline and Bezier curve and surface classes across 2D and 3D geometry libraries. The primary changes involve replacing legacy handle-based storage (HArray1/HArray2) with direct member arrays (NCollection_Array1/Array2) prefixed with "my". The public API is modernized by adding const-reference accessors for knots, poles, weights, and flat knots, while marking old output-parameter methods as deprecated. Enhanced NCollection_Array2 adds resizing control via ResizeWithTrim and internal helper methods. Geometry grid evaluation code is simplified to use the new internal accessors. Extensive new unit tests validate curve and surface operations across evaluation, manipulation, and geometric transformations. 🚥 Pre-merge checks | ❌ 3❌ Failed checks (1 warning, 2 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/ModelingData/TKG2d/Geom2d/Geom2d_BSplineCurve_1.cxx (1)
493-517:⚠️ Potential issue | 🟡 MinorPre-existing copy-paste error in
LocalDNerror message.Line 498: The
Standard_DomainError_Raise_ifmessage says"Geom2d_BSplineCurve::LocalD3"but this is theLocalDNmethod. This is a pre-existing issue (the same error exists in the 3D counterpart), not introduced by this refactor, but worth fixing if you're already touching this file.Proposed fix
- Standard_DomainError_Raise_if(FromK1 == ToK2, "Geom2d_BSplineCurve::LocalD3"); + Standard_DomainError_Raise_if(FromK1 == ToK2, "Geom2d_BSplineCurve::LocalDN");src/ModelingData/TKG3d/Geom/Geom_BezierSurface.cxx (1)
1192-1230:⚠️ Potential issue | 🟡 Minor
SetWeightRowvalidation is weaker thanSetWeightCol— allows partial row updates.
SetWeightCol(line 1156) enforcesCPoleWeights.Length() != myPoles.ColLength()— an exact length match. In contrast,SetWeightRow(lines 1197–1198) only checks that bounds fall within range, so a caller can supply a subset of weights for the row. The remaining weights retain stale or default values, thenRational()is called on the full matrix, potentially yielding incorrect results.Consider aligning the validation to also enforce a full-row length match, as done in
SetWeightCol:Proposed fix
void Geom_BezierSurface::SetWeightRow(const int UIndex, const NCollection_Array1<double>& CPoleWeights) { if (UIndex < 1 || UIndex > myPoles.ColLength()) throw Standard_OutOfRange("Geom_BezierSurface::SetWeightRow"); - if (CPoleWeights.Lower() < 1 || CPoleWeights.Lower() > myPoles.RowLength() - || CPoleWeights.Upper() < 1 || CPoleWeights.Upper() > myPoles.RowLength()) + if (CPoleWeights.Length() != myPoles.RowLength()) { throw Standard_ConstructionError("Geom_BezierSurface::SetWeightRow"); }
🤖 Fix all issues with AI agents
In `@src/FoundationClasses/TKernel/NCollection/NCollection_Array2.hxx`:
- Around line 388-432: resizeImpl currently performs a 2D min(rows)*min(cols)
copy for the linear path, which drops trailing elements when total sizes differ;
modify resizeImpl (template parameter thePreserve2D) so that when thePreserve2D
== false you compute totalOld = mySizeRow * mySizeCol and totalNew = aNewNbRows
* aNewNbCols and copy count = std::min(totalOld, totalNew) elements in 1D order
from anOldPointer[i] into NCollection_Array1<TheItemType>::at(i) (i.e., a single
loop copying anOldPointer[i] -> at(i)); leave the existing 2D-preserve behavior
(and aOldStride) unchanged for thePreserve2D == true.
In `@src/ModelingData/TKG3d/Geom/Geom_BezierSurface.cxx`:
- Line 608: The call Init(myPoles, (myURational || myVRational) ? &myWeights :
nullptr) passes member objects into Init which then mutates them (via
myPoles.Resize), causing self-reference/data-loss risk; instead, after calling
Increase(...) do not call Init with member references—either update rationality
flags and directly clear or allocate myWeights as needed (modify myWeights
in-place based on myURational/myVRational) or pass a safe temporary/copy (e.g.,
a local const copy of myWeights or nullptr) into Init; locate the usage in
Geom_BezierSurface where Increase and Init are called and change it so Init
never receives pointers/references to members that Init itself may resize (use
direct post-Increase handling of myWeights or pass a copy).
In `@src/ModelingData/TKG3d/Geom/Geom_BSplineCurve_1.cxx`:
- Around line 731-771: The knot-comparison loop reuses the pole loop index so
knots with indices <= myPoles.Length() are never checked; in the method IsEqual
(compare uses myPoles, myKnots, myMults and theOther->Knot), reset or use a
fresh loop index for the knot comparison—e.g. start a new loop with i = 1 (or
use a new variable j) and iterate up to myKnots.Length(), comparing
fabs(myKnots(i) - theOther->Knot(i)) with Precision::Parametric(thePreci) so all
knots are validated.
🧹 Nitpick comments (6)
src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_BezierSurface.cxx (1)
208-261: Consider usingIsURational()/IsVRational()instead ofaWeights != nullptrfor the rationality flags.Lines 239–240 pass
isRational(derived fromaWeights != nullptr) for bothURationalandVRationalparameters ofBSplSLib::D3. While this produces correct results (uniform weights yield a no-op division),Geom_BezierSurfacetracks U/V rationality separately. Using the geometry's ownIsURational()/IsVRational()would be more precise and could avoid unnecessary rational arithmetic when weights are uniform in one direction.The same applies to
EvaluateGridDNat lines 328–329.Proposed refinement
- const bool isRational = (aWeights != nullptr); + const bool isURational = myGeom->IsURational(); + const bool isVRational = myGeom->IsVRational();Then use
isURationalandisVRationalin theBSplSLib::D3andBSplSLib::DNcalls.src/ModelingData/TKG3d/Geom/Geom_BezierSurface.cxx (5)
363-372: Copy constructor copies derivative cache but marks it invalid.
myUMaxDerivInvandmyVMaxDerivInvare copied fromtheOther, yetmyMaxDerivInvOkis forced tofalse, making those copied values dead until recomputed. If the source object had a valid cache, consider propagatingtheOther.myMaxDerivInvOkto avoid an unnecessary future recomputation inResolution().Proposed fix
Geom_BezierSurface::Geom_BezierSurface(const Geom_BezierSurface& theOther) : myPoles(theOther.myPoles), myWeights(theOther.myWeights), myURational(theOther.myURational), myVRational(theOther.myVRational), myUMaxDerivInv(theOther.myUMaxDerivInv), myVMaxDerivInv(theOther.myVMaxDerivInv), - myMaxDerivInvOk(false) + myMaxDerivInvOk(theOther.myMaxDerivInvOk) { }
422-448: Rationality check duplicates theRational()static helper.Lines 422–448 manually iterate over weights to set
myURational/myVRational, replicating theRational()function defined at lines 57–85. Consider replacing this block with a call toRational(PoleWeights, myURational, myVRational)to reduce duplication and keep the logic in one place.Proposed simplification
- int I, J; - myURational = false; - myVRational = false; - J = PoleWeights.LowerCol(); - while (!myVRational && J <= PoleWeights.UpperCol()) - { - I = PoleWeights.LowerRow(); - while (!myVRational && I <= PoleWeights.UpperRow() - 1) - { - myVRational = (std::abs(PoleWeights(I, J) - PoleWeights(I + 1, J)) - > Epsilon(std::abs(PoleWeights(I, J)))); - I++; - } - J++; - } - I = PoleWeights.LowerRow(); - while (!myURational && I <= PoleWeights.UpperRow()) - { - J = PoleWeights.LowerCol(); - while (!myURational && J <= PoleWeights.UpperCol() - 1) - { - myURational = (std::abs(PoleWeights(I, J) - PoleWeights(I, J + 1)) - > Epsilon(std::abs(PoleWeights(I, J)))); - J++; - } - I++; - } + Rational(PoleWeights, myURational, myVRational);
1997-2031:BezierUMultsandBezierVMultsshare identical static tables.Both methods define the same
THE_DATA[26][2]array and the sameTHE_MULTSlambda. Consider extracting them into a single file-level static (or a shared private helper) and indexing it byColLength()-1/RowLength()-1respectively.
1997-2012: Hardcoded table size (26) is coupled toBSplCLib::MaxDegree()without a compile-time or runtime guard.
THE_DATAandTHE_MULTShave exactly 26 entries (indices 0–25), matchingBSplCLib::MaxDegree() == 25. IfMaxDegree()is ever increased,THE_MULTS[myPoles.ColLength() - 1]silently goes out of bounds. Consider adding a defensive assertion:Proposed guard
const NCollection_Array1<int>& Geom_BezierSurface::BezierUMults() const { Standard_ProgramError_Raise_if(myPoles.IsEmpty(), "Geom_BezierSurface: empty poles"); + Standard_ProgramError_Raise_if(myPoles.ColLength() - 1 >= 26, + "Geom_BezierSurface: degree exceeds precomputed table size"); static const int THE_DATA[26][2] = {
2035-2059: Consolidate duplicate static table initialization inBezierUFlatKnotsandBezierVFlatKnots.Both methods create identical
THE_FKNOTSstatic tables. A single shared static would halve the footprint and improve maintainability. The underlying data fromBSplCLib::FlatBezierKnots()is astatic constarray guaranteed to persist, so the non-owningNCollection_Array1views are safe.
| //! Internal resize with data copy. | ||
| //! @tparam thePreserve2D if true, copies the common sub-matrix preserving | ||
| //! 2D element positions (row, col); if false, copies elements in linear order. | ||
| template <bool thePreserve2D> | ||
| void resizeImpl(int theRowLower, int theRowUpper, int theColLower, int theColUpper) | ||
| { | ||
| Standard_RangeError_Raise_if(theRowUpper < theRowLower || theColUpper < theColLower, | ||
| "NCollection_Array2::Resize"); | ||
| const size_t aNewNbRows = theRowUpper - theRowLower + 1; | ||
| const size_t aNewNbCols = theColUpper - theColLower + 1; | ||
| if (mySizeRow == aNewNbRows && mySizeCol == aNewNbCols) | ||
| { | ||
| NCollection_Array1<TheItemType>::Resize( | ||
| BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper), | ||
| LastPosition(theRowLower, theRowUpper, theColLower, theColUpper), | ||
| false); | ||
| mySizeRow = theRowUpper - theRowLower + 1; | ||
| mySizeCol = theColUpper - theColLower + 1; | ||
| myLowerRow = theRowLower; | ||
| myLowerCol = theColLower; | ||
| NCollection_Array1<TheItemType>::UpdateLowerBound( | ||
| BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper)); | ||
| return; | ||
| } | ||
| if (mySizeRow == 0 || mySizeCol == 0) | ||
| { | ||
| resizeNoData(theRowLower, theRowUpper, theColLower, theColUpper); | ||
| return; | ||
| } | ||
| const size_t aNewNbRows = theRowUpper - theRowLower + 1; | ||
| const size_t aNewNbCols = theColUpper - theColLower + 1; | ||
| const size_t aNbRowsToCopy = (std::min)(mySizeRow, aNewNbRows); | ||
| const size_t aNbColsToCopy = (std::min)(mySizeCol, aNewNbCols); | ||
| const size_t aOldStride = thePreserve2D ? mySizeCol : aNbColsToCopy; | ||
|
|
||
| NCollection_Array2<TheItemType> aTmpMovedCopy(std::move(*this)); | ||
| TheItemType* anOldPointer = &aTmpMovedCopy.ChangeFirst(); | ||
| NCollection_Array1<TheItemType>::Resize( | ||
| BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper), | ||
| LastPosition(theRowLower, theRowUpper, theColLower, theColUpper), | ||
| false); | ||
| mySizeRow = aNewNbRows; | ||
| mySizeCol = aNewNbCols; | ||
| myLowerRow = theRowLower; | ||
| myLowerCol = theColLower; | ||
| size_t aOldInter = 0; | ||
| mySizeRow = aNewNbRows; | ||
| mySizeCol = aNewNbCols; | ||
| myLowerRow = theRowLower; | ||
| myLowerCol = theColLower; | ||
| for (size_t aRowIter = 0; aRowIter < aNbRowsToCopy; ++aRowIter) | ||
| { | ||
| for (size_t aColIter = 0; aColIter < aNbColsToCopy; ++aColIter) | ||
| { | ||
| NCollection_Array1<TheItemType>::at(aRowIter * aNewNbCols + aColIter) = | ||
| std::move(anOldPointer[aOldInter++]); | ||
| std::move(anOldPointer[aRowIter * aOldStride + aColIter]); | ||
| } | ||
| } |
There was a problem hiding this comment.
Linear resize path can drop data on shape changes.
resizeImpl<false> only copies min(rows)*min(cols) elements. For reshapes with equal total size (e.g., 4×6 → 6×4), this loses trailing elements even though the API contract says “linear (row‑major) order.” Consider copying min(oldSize, newSize) elements in 1D order for the linear path.
💡 Suggested fix (1D copy for linear path)
- const size_t aNbRowsToCopy = (std::min)(mySizeRow, aNewNbRows);
- const size_t aNbColsToCopy = (std::min)(mySizeCol, aNewNbCols);
- const size_t aOldStride = thePreserve2D ? mySizeCol : aNbColsToCopy;
+ const size_t aOldNbRows = mySizeRow;
+ const size_t aOldNbCols = mySizeCol;
+ const size_t aNbRowsToCopy = (std::min)(aOldNbRows, aNewNbRows);
+ const size_t aNbColsToCopy = (std::min)(aOldNbCols, aNewNbCols);
+ const size_t aOldStride = thePreserve2D ? aOldNbCols : aNbColsToCopy;
@@
- for (size_t aRowIter = 0; aRowIter < aNbRowsToCopy; ++aRowIter)
- {
- for (size_t aColIter = 0; aColIter < aNbColsToCopy; ++aColIter)
- {
- NCollection_Array1<TheItemType>::at(aRowIter * aNewNbCols + aColIter) =
- std::move(anOldPointer[aRowIter * aOldStride + aColIter]);
- }
- }
+ if constexpr (thePreserve2D)
+ {
+ for (size_t aRowIter = 0; aRowIter < aNbRowsToCopy; ++aRowIter)
+ {
+ for (size_t aColIter = 0; aColIter < aNbColsToCopy; ++aColIter)
+ {
+ NCollection_Array1<TheItemType>::at(aRowIter * aNewNbCols + aColIter) =
+ std::move(anOldPointer[aRowIter * aOldStride + aColIter]);
+ }
+ }
+ }
+ else
+ {
+ const size_t aNbToCopy =
+ (std::min)(aOldNbRows * aOldNbCols, aNewNbRows * aNewNbCols);
+ for (size_t aIdx = 0; aIdx < aNbToCopy; ++aIdx)
+ {
+ NCollection_Array1<TheItemType>::at(aIdx) = std::move(anOldPointer[aIdx]);
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| //! Internal resize with data copy. | |
| //! @tparam thePreserve2D if true, copies the common sub-matrix preserving | |
| //! 2D element positions (row, col); if false, copies elements in linear order. | |
| template <bool thePreserve2D> | |
| void resizeImpl(int theRowLower, int theRowUpper, int theColLower, int theColUpper) | |
| { | |
| Standard_RangeError_Raise_if(theRowUpper < theRowLower || theColUpper < theColLower, | |
| "NCollection_Array2::Resize"); | |
| const size_t aNewNbRows = theRowUpper - theRowLower + 1; | |
| const size_t aNewNbCols = theColUpper - theColLower + 1; | |
| if (mySizeRow == aNewNbRows && mySizeCol == aNewNbCols) | |
| { | |
| NCollection_Array1<TheItemType>::Resize( | |
| BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper), | |
| LastPosition(theRowLower, theRowUpper, theColLower, theColUpper), | |
| false); | |
| mySizeRow = theRowUpper - theRowLower + 1; | |
| mySizeCol = theColUpper - theColLower + 1; | |
| myLowerRow = theRowLower; | |
| myLowerCol = theColLower; | |
| NCollection_Array1<TheItemType>::UpdateLowerBound( | |
| BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper)); | |
| return; | |
| } | |
| if (mySizeRow == 0 || mySizeCol == 0) | |
| { | |
| resizeNoData(theRowLower, theRowUpper, theColLower, theColUpper); | |
| return; | |
| } | |
| const size_t aNewNbRows = theRowUpper - theRowLower + 1; | |
| const size_t aNewNbCols = theColUpper - theColLower + 1; | |
| const size_t aNbRowsToCopy = (std::min)(mySizeRow, aNewNbRows); | |
| const size_t aNbColsToCopy = (std::min)(mySizeCol, aNewNbCols); | |
| const size_t aOldStride = thePreserve2D ? mySizeCol : aNbColsToCopy; | |
| NCollection_Array2<TheItemType> aTmpMovedCopy(std::move(*this)); | |
| TheItemType* anOldPointer = &aTmpMovedCopy.ChangeFirst(); | |
| NCollection_Array1<TheItemType>::Resize( | |
| BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper), | |
| LastPosition(theRowLower, theRowUpper, theColLower, theColUpper), | |
| false); | |
| mySizeRow = aNewNbRows; | |
| mySizeCol = aNewNbCols; | |
| myLowerRow = theRowLower; | |
| myLowerCol = theColLower; | |
| size_t aOldInter = 0; | |
| mySizeRow = aNewNbRows; | |
| mySizeCol = aNewNbCols; | |
| myLowerRow = theRowLower; | |
| myLowerCol = theColLower; | |
| for (size_t aRowIter = 0; aRowIter < aNbRowsToCopy; ++aRowIter) | |
| { | |
| for (size_t aColIter = 0; aColIter < aNbColsToCopy; ++aColIter) | |
| { | |
| NCollection_Array1<TheItemType>::at(aRowIter * aNewNbCols + aColIter) = | |
| std::move(anOldPointer[aOldInter++]); | |
| std::move(anOldPointer[aRowIter * aOldStride + aColIter]); | |
| } | |
| } | |
| //! Internal resize with data copy. | |
| //! `@tparam` thePreserve2D if true, copies the common sub-matrix preserving | |
| //! 2D element positions (row, col); if false, copies elements in linear order. | |
| template <bool thePreserve2D> | |
| void resizeImpl(int theRowLower, int theRowUpper, int theColLower, int theColUpper) | |
| { | |
| Standard_RangeError_Raise_if(theRowUpper < theRowLower || theColUpper < theColLower, | |
| "NCollection_Array2::Resize"); | |
| const size_t aNewNbRows = theRowUpper - theRowLower + 1; | |
| const size_t aNewNbCols = theColUpper - theColLower + 1; | |
| if (mySizeRow == aNewNbRows && mySizeCol == aNewNbCols) | |
| { | |
| myLowerRow = theRowLower; | |
| myLowerCol = theColLower; | |
| NCollection_Array1<TheItemType>::UpdateLowerBound( | |
| BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper)); | |
| return; | |
| } | |
| if (mySizeRow == 0 || mySizeCol == 0) | |
| { | |
| resizeNoData(theRowLower, theRowUpper, theColLower, theColUpper); | |
| return; | |
| } | |
| const size_t aOldNbRows = mySizeRow; | |
| const size_t aOldNbCols = mySizeCol; | |
| const size_t aNbRowsToCopy = (std::min)(aOldNbRows, aNewNbRows); | |
| const size_t aNbColsToCopy = (std::min)(aOldNbCols, aNewNbCols); | |
| const size_t aOldStride = thePreserve2D ? aOldNbCols : aNbColsToCopy; | |
| NCollection_Array2<TheItemType> aTmpMovedCopy(std::move(*this)); | |
| TheItemType* anOldPointer = &aTmpMovedCopy.ChangeFirst(); | |
| NCollection_Array1<TheItemType>::Resize( | |
| BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper), | |
| LastPosition(theRowLower, theRowUpper, theColLower, theColUpper), | |
| false); | |
| mySizeRow = aNewNbRows; | |
| mySizeCol = aNewNbCols; | |
| myLowerRow = theRowLower; | |
| myLowerCol = theColLower; | |
| if constexpr (thePreserve2D) | |
| { | |
| for (size_t aRowIter = 0; aRowIter < aNbRowsToCopy; ++aRowIter) | |
| { | |
| for (size_t aColIter = 0; aColIter < aNbColsToCopy; ++aColIter) | |
| { | |
| NCollection_Array1<TheItemType>::at(aRowIter * aNewNbCols + aColIter) = | |
| std::move(anOldPointer[aRowIter * aOldStride + aColIter]); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| const size_t aNbToCopy = | |
| (std::min)(aOldNbRows * aOldNbCols, aNewNbRows * aNewNbCols); | |
| for (size_t aIdx = 0; aIdx < aNbToCopy; ++aIdx) | |
| { | |
| NCollection_Array1<TheItemType>::at(aIdx) = std::move(anOldPointer[aIdx]); | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@src/FoundationClasses/TKernel/NCollection/NCollection_Array2.hxx` around
lines 388 - 432, resizeImpl currently performs a 2D min(rows)*min(cols) copy for
the linear path, which drops trailing elements when total sizes differ; modify
resizeImpl (template parameter thePreserve2D) so that when thePreserve2D ==
false you compute totalOld = mySizeRow * mySizeCol and totalNew = aNewNbRows *
aNewNbCols and copy count = std::min(totalOld, totalNew) elements in 1D order
from anOldPointer[i] into NCollection_Array1<TheItemType>::at(i) (i.e., a single
loop copying anOldPointer[i] -> at(i)); leave the existing 2D-preserve behavior
(and aOldStride) unchanged for thePreserve2D == true.
| myPoles = std::move(npoles); | ||
| } | ||
| Init(npoles, nweights); | ||
| Init(myPoles, (myURational || myVRational) ? &myWeights : nullptr); |
There was a problem hiding this comment.
Init(myPoles, &myWeights) passes self-references — fragile and data-loss risk.
Init takes its arguments by const reference/pointer, but here myPoles and myWeights are aliased to the member fields being modified inside Init. Inside Init (line 1945), myPoles.Resize(..., false) would discard data if dimensions don't match — but then myPoles = thePoles reads from the same (now-garbage) object.
Today this is safe only because Resize is a no-op when dimensions already match (which they do after Increase has already resized via std::move). However, this is fragile and will silently break if Resize behavior changes or if dimensions diverge.
A safer approach: after Increase, just re-check rationality and clean up weights directly, without funneling through Init.
Proposed fix
- Init(myPoles, (myURational || myVRational) ? &myWeights : nullptr);
+ // Re-check rationality after degree increase
+ if (myURational || myVRational)
+ {
+ Rational(myWeights, myURational, myVRational);
+ if (!(myURational || myVRational))
+ {
+ myWeights = NCollection_Array2<double>();
+ }
+ }
+ myMaxDerivInvOk = false;🤖 Prompt for AI Agents
In `@src/ModelingData/TKG3d/Geom/Geom_BezierSurface.cxx` at line 608, The call
Init(myPoles, (myURational || myVRational) ? &myWeights : nullptr) passes member
objects into Init which then mutates them (via myPoles.Resize), causing
self-reference/data-loss risk; instead, after calling Increase(...) do not call
Init with member references—either update rationality flags and directly clear
or allocate myWeights as needed (modify myWeights in-place based on
myURational/myVRational) or pass a safe temporary/copy (e.g., a local const copy
of myWeights or nullptr) into Init; locate the usage in Geom_BezierSurface where
Increase and Init are called and change it so Init never receives
pointers/references to members that Init itself may resize (use direct
post-Increase handling of myWeights or pass a copy).
| if (myKnots.IsEmpty() || myPoles.IsEmpty() || myMults.IsEmpty()) | ||
| return false; | ||
| if (deg != theOther->Degree()) | ||
| if (myDeg != theOther->Degree()) | ||
| return false; | ||
| if (knots->Length() != theOther->NbKnots() || poles->Length() != theOther->NbPoles()) | ||
| if (myKnots.Length() != theOther->NbKnots() || myPoles.Length() != theOther->NbPoles()) | ||
| return false; | ||
|
|
||
| int i = 1; | ||
| for (i = 1; i <= poles->Length(); i++) | ||
| for (i = 1; i <= myPoles.Length(); i++) | ||
| { | ||
| const gp_Pnt& aPole1 = poles->Value(i); | ||
| const gp_Pnt& aPole1 = myPoles(i); | ||
| const gp_Pnt& aPole2 = theOther->Pole(i); | ||
| if (fabs(aPole1.X() - aPole2.X()) > thePreci || fabs(aPole1.Y() - aPole2.Y()) > thePreci | ||
| || fabs(aPole1.Z() - aPole2.Z()) > thePreci) | ||
| return false; | ||
| } | ||
|
|
||
| for (; i <= knots->Length(); i++) | ||
| for (; i <= myKnots.Length(); i++) | ||
| { | ||
| if (fabs(knots->Value(i) - theOther->Knot(i)) > Precision::Parametric(thePreci)) | ||
| if (fabs(myKnots(i) - theOther->Knot(i)) > Precision::Parametric(thePreci)) | ||
| return false; | ||
| } | ||
|
|
||
| for (i = 1; i <= mults->Length(); i++) | ||
| for (i = 1; i <= myMults.Length(); i++) | ||
| { | ||
| if (mults->Value(i) != theOther->Multiplicity(i)) | ||
| if (myMults(i) != theOther->Multiplicity(i)) | ||
| return false; | ||
| } | ||
|
|
||
| if (rational != theOther->IsRational()) | ||
| if (myRational != theOther->IsRational()) | ||
| return false; | ||
|
|
||
| if (!rational) | ||
| if (!myRational) | ||
| return true; | ||
|
|
||
| for (i = 1; i <= weights->Length(); i++) | ||
| for (i = 1; i <= myWeights.Length(); i++) | ||
| { | ||
| if (fabs(double(weights->Value(i) - theOther->Weight(i))) > Epsilon(weights->Value(i))) | ||
| if (fabs(double(myWeights(i) - theOther->Weight(i))) > Epsilon(myWeights(i))) | ||
| return false; | ||
| } | ||
| return true; |
There was a problem hiding this comment.
IsEqual skips knot comparison due to index reuse.
The knot loop continues from the pole index, so early knots are never compared. This can incorrectly report equality for curves with differing knot vectors.
✅ Suggested fix
- for (; i <= myKnots.Length(); i++)
+ for (i = 1; i <= myKnots.Length(); i++)🤖 Prompt for AI Agents
In `@src/ModelingData/TKG3d/Geom/Geom_BSplineCurve_1.cxx` around lines 731 - 771,
The knot-comparison loop reuses the pole loop index so knots with indices <=
myPoles.Length() are never checked; in the method IsEqual (compare uses myPoles,
myKnots, myMults and theOther->Knot), reset or use a fresh loop index for the
knot comparison—e.g. start a new loop with i = 1 (or use a new variable j) and
iterate up to myKnots.Length(), comparing fabs(myKnots(i) - theOther->Knot(i))
with Precision::Parametric(thePreci) so all knots are validated.
…ineCurve and Geom_BezierSurface implementations
…acro definitions with direct member variable usage for improved clarity and maintainability
…owPreservesOldRegion and update comments for clarity
… to replace static array definitions with dynamic initialization for improved flexibility and maintainability
…and clarity - Renamed methods from Update* to update* for consistency in naming conventions. - Updated method calls in Geom_BSplineSurface and Geom_Bezier classes to reflect the new naming. - Removed redundant Internal* methods in Geom_BSplineSurface and Geom_BezierCurve, replacing them with direct calls to updated methods. - Refactored the initialization methods in Geom_BezierCurve and Geom_BezierSurface to follow a consistent naming pattern. - Updated references in GeomGridEval classes to use the new method names for flat knots and multiplicities. - Improved code readability and maintainability by standardizing method names and reducing redundancy.
… to use static constexpr for maximum size definition for improved clarity and maintainability
No description provided.