Modeling Algorithms - Select parts of the tool, not the cut result, in BRepFeat_MakeCylindricalHole - #1447
Conversation
|
Dear @gsdali please add GTests into the commit, where you tested your code. |
|
Dear @gsdali please prepare GTEst your changes.
Comment have not benefits as a part of code, please remove these comments |
|
I've seen the comment, we've just worked through our release against 8.0.1 and can now take a look at this. |
Thank you! It is not urgent ;) Our next release is far. Take your time ;) Many thanks for your contribution. |
…ricalHole
PerformThruNext, PerformUntilEnd, the ranged Perform(Radius, PFrom, PTo), and
PerformBlind choose which part of the drilling tool to keep by driving
BRepFeat_Builder with the wrong operation:
SetOperation(Fuse); // -> BOPAlgo_CUT
BOPAlgo_BOP::Perform(); // myShape := object CUT tool
PartsOfTool(parts); // explores myShape for solids
BRepFeat_Builder::PartsOfTool() collects the solids of myShape, which only
holds the tool split by the object after a COMMON operation. After a CUT,
myShape is the finished workpiece, so the selection loop compares barycentres
of the cut result and registers pieces of it as kept parts of the tool.
PerformResult() then takes the kept-parts path with a keep set containing no
tool part at all, returning the object with the cylinder's faces imprinted on
it and no material removed, while reporting BRepFeat_NoError throughout.
BRepFeat_Form (BRepFeat_Form.cxx:806) and BRepFeat_RibSlot
(BRepFeat_RibSlot.cxx:224), the other two users of the same builder, both call
the two-argument SetOperation(myFuse, bFlag) with bFlag true before Perform(),
selecting BOPAlgo_COMMON so that PartsOfTool() means what its name says.
PerformResult() resets myOperation from myFuse before building, so the
operation finally built is still a CUT either way. BRepFeat_MakeCylindricalHole
calls the one-argument overload at all four selecting sites. Perform(Radius),
the infinite-cylinder through-all mode, selects no parts and is unaffected.
The defect is hidden whenever the cut result has one solid, the case every
existing test covers. It shows up as soon as it has two: a drill axis crossing
two bodies of a compound, or, with no compound at all, a single bar the bore
severs in half.
Measured on two 50x50x20 plates stacked on the drill axis, drilled at r = 5
(one bore removes 1570.7963):
call status before after
PerformUntilEnd NoError 0.0000 3141.5927
PerformThruNext NoError 1570.7963 1570.7963 (unaffected)
PerformBlind(20) NoError 0.0000 1178.0972
Perform(R, 0, 70) NoError 0.0000 3141.5927
and on a single 8mm bar an r = 5 bore severs, where the CUT result is one
workpiece split into two pieces rather than two separate bodies:
PerformUntilEnd NoError 0.0000 1407.2952
PerformThruNext NoError 0.0000 1407.2952
Perform(R, 0, 30) NoError 0.0000 1407.2952
One behaviour changes along with the fix. A radius large enough to swallow the
whole workpiece used to report BRepFeat_InvalidPlacement for PerformUntilEnd
and PerformThruNext, because the CUT emptied myShape and nbparts was 0. Under
COMMON the tool meets the whole workpiece, nbparts is 1, and both modes now
return the same result Perform(Radius) already returns for the same input.
Also drops the trailing comment on the four SetOperation(Fuse, true) calls this fix adds,
matching the two sibling callers (BRepFeat_Form.cxx:806, BRepFeat_RibSlot.cxx:224), neither
of which comments its own call.
Tests: added BRepFeat_MakeCylindricalHole_Test.cxx in
src/ModelingAlgorithms/TKFeat/GTests/ (first test in this toolkit). Covers the two-plate
stack from the measured table above for PerformUntilEnd/PerformBlind/the ranged Perform,
plus Perform(Radius) and a single-plate case as controls. Verified both ways: linked with
an unmodified override ahead of the archive, the three affected modes remove ~0 material
(matching the table's 'before' column); linked with the patched translation unit, all five
tests pass with the measured volumes.
2c28614 to
3ba6f56
Compare
|
Thanks — pushed an update. GTest added: Comment removed: dropped |
PerformThruNext,PerformUntilEnd, the rangedPerform(Radius, PFrom, PTo), andPerformBlindall choose which part of the drilling tool to keep by drivingBRepFeat_Builderwith the wrong operation:BRepFeat_Builder::PartsOfTool()collects the solids ofmyShape, which only holds the tool split by the object after aBOPAlgo_COMMONoperation. After aBOPAlgo_CUT,myShapeis the finished workpiece, so the selection loop compares barycentres of the cut result and registers pieces of it as kept parts of the tool.PerformResult()then takes the kept-parts path with a keep set containing no tool part at all, returning the object with the cylinder's faces imprinted on it and no material removed, while reportingBRepFeat_NoErrorthroughout.BRepFeat_Form(BRepFeat_Form.cxx:806) andBRepFeat_RibSlot(BRepFeat_RibSlot.cxx:224), the other two users of the same builder, both call the two-argumentSetOperation(myFuse, bFlag)withbFlagtrue beforePerform(), selectingBOPAlgo_COMMONso thatPartsOfTool()means what its name says.PerformResult()resetsmyOperationfrommyFusebefore building, so the operation finally built is still aCUTeither way.BRepFeat_MakeCylindricalHolecalls the one-argument overload at all four selecting sites.Perform(Radius), the infinite-cylinder through-all mode, selects no parts and is unaffected.The defect is hidden whenever the cut result has one solid, the case every existing test covers. It shows up as soon as it has two: a drill axis crossing two bodies of a compound, or, with no compound at all, a single bar the bore severs in half.
Measured
Two 50 x 50 x 20 plates stacked on the drill axis, drilled at
r = 5from(0, 0, 15)along-Z(one bore removes1570.7963):Perform(R)NoErrorPerformUntilEndNoErrorPerformThruNextNoErrorPerformBlind(20)NoErrorPerform(R, 0, 70)NoErrorPerform(R, 0, 30)NoErrorPerform(R, 30, 70)NoErrorPerformBlind(20)'s length is measured from the axis origin(0, 0, 15), which sits 5 units above plate A's entry face at axis parameter 5, not from that face. A blind depth of 20 therefore reaches axis parameter 20, boring only 15 units of the plate's 20-unit thickness:1178.0972, matchingpi * r^2 * 15. That is the mode's own contract, not a discrepancy against the other rows.A single 8mm bar an
r = 5bore severs, where the CUT result is one workpiece split into two pieces rather than two separate bodies:PerformUntilEndNoErrorPerformThruNextNoErrorPerform(R, 0, 30)NoErrorA single plate (one solid before and after the CUT) is byte-identical before and after;
nbpartsnever reaches 2 there.Two behaviour changes worth flagging
A status change for an oversized radius. A radius large enough to swallow the whole workpiece used to report
BRepFeat_InvalidPlacementforPerformUntilEndandPerformThruNext, because the CUT emptiedmyShapeandnbpartswas 0. UnderBOPAlgo_COMMONthe tool meets the whole workpiece,nbpartsis 1, and both modes now return the same resultPerform(Radius)already returns for the same input (measured directly against this change: both go fromInvalidPlacementtoNoError, matchingPerform(Radius)'s existingNoError).A second, separate defect in the same heuristic, reported here but not fixed.
PerformThruNext's closest-interval fallback nests its// parbar > Lastbranch insideif (parbar < First), as theelseof the distance comparison, so the "beyondLast" case is unreachable as written:PerformBlind's equivalent fallback has no such structure. It comparesstd::abs(First - parbar) < dminuniformly. The fallback only runs when no tool part's barycentre lies in[First, Last], which none of the geometries measured here reach, so it is left alone: changing it without a case that exercises it would be a guess.Validation
git apply --checkagainst currentmaster(this PR's base): applies with no fuzz.git clang-formatagainst the repository's own.clang-format, restricted to the changed lines: no reformatting.BRepFeat_MakeCylindricalHole.cxxfrom currentmaster, once unmodified and once with this change, as override translation units linked ahead of a prebuilt OCCT 8.0.1 archive (-DNo_Exception, matching a release build), and ran a probe that drives all four modes plus the unaffectedPerform(Radius)over six geometries (a single plate, two- and three-plate compounds, a one-solid channel with two spans on the axis, a bar the bore severs, and a hollow box). The unmodified build reproduces every "before" figure above; the changed build reproduces every "after" figure, and the two non-regression geometries (channel, hollow box) are byte-identical between the two builds.InvalidPlacementtoNoError) the same way.BRepFeat_Builder::SetOperation/PerformResulton currentmasterdirectly to confirm the two-argument overload's semantics and thatPerformResult()re-derives the final operation frommyFuseregardless of the earlier call.Found while measuring hole-drilling extents for the OCCTSwift wrapper, which currently works around this bridge-side by driving
BRepAlgoAPI_Cutdirectly (Shape.drilled) rather than this class for a stack. Full writeup, reproducer and before/after transcripts: https://github.com/SecondMouseAU/OCCTSwift/tree/main/Scripts/repro/532-cylindrical-hole-part-selection