Files
OCCT/src/ModelingAlgorithms/TKOffset/GTests/BRepOffsetAPI_MakePipeShell_Test.cxx
T
Pasukhin Dmitry 39bbf2ed59 Testing - Migration QADraw tests to GTests (#1243)
- Added new GTests covering multiple historical regressions (intersection, projection, pipe shell, boolean ops, fillet, distances, gp transforms, string hashing).
- Registered new test sources in the relevant `GTests/FILES.cmake` lists.
- Removed legacy Tcl tests and removed the associated QADraw commands from `QABugs_*.cxx`.
2026-04-28 18:25:29 +01:00

247 lines
9.3 KiB
C++

// Copyright (c) 2025 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
#include <gtest/gtest.h>
#include <BRep_Builder.hxx>
#include <BRep_Tool.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepCheck_Analyzer.hxx>
#include <BRepGProp.hxx>
#include <BRepOffsetAPI_MakePipeShell.hxx>
#include <GC_MakeArcOfCircle.hxx>
#include <Geom_Plane.hxx>
#include <GProp_GProps.hxx>
#include <gp_Ax1.hxx>
#include <gp_Ax2.hxx>
#include <gp_Circ.hxx>
#include <gp_Dir.hxx>
#include <gp_Pln.hxx>
#include <gp_Pnt.hxx>
#include <Law_Linear.hxx>
#include <Precision.hxx>
#include <Standard_Handle.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shell.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Solid.hxx>
#include <TopoDS_Wire.hxx>
#include <TopExp_Explorer.hxx>
#include <cmath>
namespace
{
//! Computes the total linear length of a wire.
double wireLength(const TopoDS_Wire& theWire)
{
GProp_GProps aProps;
BRepGProp::LinearProperties(theWire, aProps);
return aProps.Mass();
}
//! Builds a bent tube solid (wallSolid) and an inner gas solid (gasSolid).
//! Mirrors the OCC332 Draw command algorithm:
//! - A circular cross-section profile is swept along an arc spine
//! - A linear scaling law varies the inner radius from radius_l to radius_r
//! - The outer radius is offset by wall_thickness throughout
//!
//! @param[in] theWallThickness wall thickness of the tube
//! @param[in] theDia1 inner diameter at start of bend
//! @param[in] theDia2 inner diameter at end of bend
//! @param[in] theMajorRadius radius of the bend arc (spine radius)
//! @param[in] theLength arc length along the spine
//! @param[out] theWallSolid resulting tube wall solid
//! @param[out] theGasSolid resulting inner gas solid
//! @return false if any construction step fails
bool buildBentTube(double theWallThickness,
double theDia1,
double theDia2,
double theMajorRadius,
double theLength,
TopoDS_Solid& theWallSolid,
TopoDS_Shape& theGasSolid)
{
const double aBendAngle = theLength / theMajorRadius;
if (aBendAngle >= M_PI)
return false;
const double aRadiusL = theDia1 / 2.0;
const double aRadiusR = theDia2 / 2.0;
gp_Ax2 anOrigin(gp_Pnt(5000.0, -300.0, 1000.0), gp_Dir(0.0, -1.0, -1.0));
gp_Pln aCirc1Plane(anOrigin.Location(), anOrigin.Direction());
gp_Circ aFaceCircle(anOrigin, aRadiusL);
gp_Circ anOutFaceCircle(anOrigin, aRadiusL + theWallThickness);
gp_Pnt aCircCenter = anOrigin.Location().Translated(theMajorRadius * anOrigin.XDirection());
gp_Ax1 aCircAxis(aCircCenter, anOrigin.YDirection());
gp_Pln aCirc2Plane = aCirc1Plane.Rotated(aCircAxis, aBendAngle);
gp_Ax2 aSpineAxis(aCircCenter, anOrigin.YDirection(), -anOrigin.XDirection());
gp_Circ aSpineCircle(aSpineAxis, theMajorRadius);
TopoDS_Wire aWire1 = BRepBuilderAPI_MakeWire(BRepBuilderAPI_MakeEdge(aFaceCircle)).Wire();
TopoDS_Wire anOutWire = BRepBuilderAPI_MakeWire(BRepBuilderAPI_MakeEdge(anOutFaceCircle)).Wire();
occ::handle<Geom_Curve> aSpineCurve(
GC_MakeArcOfCircle(aSpineCircle, 0.0, aBendAngle, true).Value());
occ::handle<Law_Linear> aLaw1 = new Law_Linear();
occ::handle<Law_Linear> aLaw2 = new Law_Linear();
aLaw1->Set(aSpineCurve->FirstParameter(), 1.0, aSpineCurve->LastParameter(), aRadiusR / aRadiusL);
aLaw2->Set(aSpineCurve->FirstParameter(),
1.0,
aSpineCurve->LastParameter(),
(aRadiusR + theWallThickness) / (aRadiusL + theWallThickness));
BRepBuilderAPI_MakeEdge aMkEdge(aSpineCurve);
if (!aMkEdge.IsDone())
return false;
TopoDS_Wire aSpineWire = BRepBuilderAPI_MakeWire(aMkEdge.Edge()).Wire();
BRepOffsetAPI_MakePipeShell aMkPipe1(aSpineWire);
aMkPipe1.SetTolerance(1.0e-8, 1.0e-8, 1.0e-6);
aMkPipe1.SetLaw(aWire1, aLaw1, false, false);
aMkPipe1.Build();
if (!aMkPipe1.IsDone())
return false;
BRepOffsetAPI_MakePipeShell aMkPipe2(aSpineWire);
aMkPipe2.SetTolerance(1.0e-8, 1.0e-8, 1.0e-6);
aMkPipe2.SetLaw(anOutWire, aLaw2, false, false);
aMkPipe2.Build();
if (!aMkPipe2.IsDone())
return false;
// Build opening face at start of bend (annular ring)
BRepBuilderAPI_MakeFace aMkFace;
occ::handle<Geom_Plane> aPlane1 = new Geom_Plane(aCirc1Plane);
aMkFace.Init(aPlane1, false, Precision::Confusion());
aMkFace.Add(TopoDS::Wire(aMkPipe2.FirstShape()));
aMkFace.Add(TopoDS::Wire(aMkPipe1.FirstShape().Reversed()));
if (!aMkFace.IsDone())
return false;
TopoDS_Face aFace1 = aMkFace.Face();
// Build opening face at end of bend (annular ring)
occ::handle<Geom_Plane> aPlane2 = new Geom_Plane(aCirc2Plane);
aMkFace.Init(aPlane2, false, Precision::Confusion());
aMkFace.Add(TopoDS::Wire(aMkPipe2.LastShape()));
aMkFace.Add(TopoDS::Wire(aMkPipe1.LastShape().Reversed()));
if (!aMkFace.IsDone())
return false;
TopoDS_Face aFace2 = aMkFace.Face();
// Assemble tube shell from inner pipe face, outer pipe face, and two annular ring faces
TopoDS_Shell aTubeShell;
BRep_Builder aBuilder;
aBuilder.MakeShell(aTubeShell);
TopExp_Explorer aFaceExp(aMkPipe1.Shape(), TopAbs_FACE);
if (aFaceExp.More())
aBuilder.Add(aTubeShell, aFaceExp.Current().Reversed());
// Extract the gas solid before modifying mkPipe1 to a solid
aMkPipe1.MakeSolid();
theGasSolid = aMkPipe1.Shape();
aFaceExp.Init(aMkPipe2.Shape(), TopAbs_FACE);
if (aFaceExp.More())
aBuilder.Add(aTubeShell, aFaceExp.Current());
aBuilder.Add(aTubeShell, aFace1.Reversed());
aBuilder.Add(aTubeShell, aFace2);
aTubeShell.Closed(BRep_Tool::IsClosed(aTubeShell));
aBuilder.MakeSolid(theWallSolid);
aBuilder.Add(theWallSolid, aTubeShell);
return true;
}
} // anonymous namespace
//=================================================================================================
// Bug OCC332: BRepOffsetAPI_MakePipeShell with linear scaling law along a bent tube.
// Verifies that:
// - Both wallSolid and gasSolid are geometrically valid shapes
// - wallSolid contains 4 circular cross-section wires matching expected circumferences:
// pi*dia1, pi*(dia1+2*wall), pi*dia2, pi*(dia2+2*wall)
// within 0.1% tolerance (as validated in the original TCL test).
TEST(BRepOffsetAPI_MakePipeShellTest, Bug332_BentTubeWithScalingLaw)
{
const double aWallThickness = 10.0;
const double aDia1 = 30.0;
const double aDia2 = 50.0;
const double aMajorRadius = 100.0;
const double aLength = 200.0;
TopoDS_Solid aWallSolid;
TopoDS_Shape aGasSolid;
const bool aBuilt =
buildBentTube(aWallThickness, aDia1, aDia2, aMajorRadius, aLength, aWallSolid, aGasSolid);
ASSERT_TRUE(aBuilt) << "buildBentTube construction failed";
// Validate both solids
EXPECT_TRUE(BRepCheck_Analyzer(aWallSolid).IsValid()) << "wallSolid is not valid";
EXPECT_TRUE(BRepCheck_Analyzer(aGasSolid).IsValid()) << "gasSolid is not valid";
// Collect all wire lengths in wallSolid
NCollection_Sequence<double> aWireLengths;
for (TopExp_Explorer anExp(aWallSolid, TopAbs_WIRE); anExp.More(); anExp.Next())
{
const double aLen = wireLength(TopoDS::Wire(anExp.Current()));
if (aLen > Precision::Confusion())
aWireLengths.Append(aLen);
}
// Expected circular circumferences from the TCL test (pi * diameter):
// Wire4 in TCL: pi * dia1 = pi * 30 (inner start, radius=15)
// Wire3 in TCL: pi * (dia1 + 2*wall) = pi * 50 (outer start, radius=25)
// Wire6 in TCL: pi * dia2 = pi * 50 (inner end, radius=25)
// Wire5 in TCL: pi * (dia2 + 2*wall) = pi * 70 (outer end, radius=35)
const double aPi = M_PI;
const double aExpLen1 = aPi * aDia1; // pi*30 ~ 94.25
const double aExpLen2 = aPi * (aDia1 + 2.0 * aWallThickness); // pi*50 ~ 157.08
// aExpLen3 = pi*dia2 = pi*50, which equals aExpLen2 when dia2/2 == dia1/2 + wall
const double aExpLen4 = aPi * (aDia2 + 2.0 * aWallThickness); // pi*70 ~ 219.91
const double aTol = 0.001; // 0.1% relative tolerance
auto countMatching = [&](double theExpected) -> int {
int aCount = 0;
for (const double aLen : aWireLengths)
{
if (std::abs(aLen - theExpected) / theExpected <= aTol)
++aCount;
}
return aCount;
};
EXPECT_GE(countMatching(aExpLen1), 1)
<< "No wire found with circumference pi*dia1 = " << aExpLen1;
// aExpLen2 and aExpLen3 are identical (dia2/2 == dia1/2 + wall), so expect at least 2 matches
EXPECT_GE(countMatching(aExpLen2), 2)
<< "Expected 2 wires with circumference pi*50 = " << aExpLen2;
EXPECT_GE(countMatching(aExpLen4), 1)
<< "No wire found with circumference pi*(dia2+2*wall) = " << aExpLen4;
}