Merge pull request #12957 from bgbsww/bgbsww-toponamingMakeElementOffset

Toponaming/Part make element offset
This commit is contained in:
Chris Hennes
2024-03-18 16:07:14 -05:00
committed by GitHub
4 changed files with 949 additions and 38 deletions

View File

@@ -579,6 +579,14 @@ void TopoShape::setPyObject(PyObject* obj)
}
}
//void TopoShape::operator = (const TopoShape& sh)
//{
// if (this != &sh) {
// this->Tag = sh.Tag;
// this->_Shape = sh._Shape;
// }
//}
void TopoShape::convertTogpTrsf(const Base::Matrix4D& mtrx, gp_Trsf& trsf)
{
trsf.SetValues(mtrx[0][0],mtrx[0][1],mtrx[0][2],mtrx[0][3],

View File

@@ -220,7 +220,7 @@ enum class CheckScale
checkScale
};
enum class Copy
enum class CopyType
{
noCopy,
copy
@@ -249,6 +249,18 @@ enum class Spine
on
};
enum class FillType
{
noFill,
fill
};
enum class OpenResult
{
noOpenResult,
allowOpenResult
};
/** The representation for a CAD Shape
*/
// NOLINTNEXTLINE cppcoreguidelines-special-member-functions
@@ -882,6 +894,171 @@ public:
return TopoShape(0,Hasher).makeElementThickSolid(*this,faces,offset,tol,intersection,selfInter,
offsetMode,join,op);
}
/** Make a 3D offset of a given shape
*
* @param source: source shape
* @param offset: distance to offset
* @param tol: tolerance criterion for coincidence in generated shapes
* @param intersection: whether to check intersection in all generated parallel
* (OCCT document states the option is not fully implemented)
* @param selfInter: whether to eliminate self intersection
* (OCCT document states the option is not implemented)
* @param offsetMode: defines the construction type of parallels applied to free edges
* (OCCT document states the option is not implemented)
* @param join: join type. Only support JoinType::Arc and JoinType::Intersection.
* @param fill: whether to build a solid by fill the offset
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return The original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a self reference so that multiple operations can be carried out
* for the same shape in the same line of code.
*/
TopoShape& makeElementOffset(const TopoShape& source,
double offset,
double tol,
bool intersection = false,
bool selfInter = false,
short offsetMode = 0,
JoinType join = JoinType::arc,
FillType fill = FillType::noFill,
const char* op = nullptr);
/** Make a 3D offset of this shape
*
* @param offset: distance to offset
* @param tol: tolerance criterion for coincidence in generated shapes
* @param intersection: whether to check intersection in all generated parallel
* (OCCT document states the option is not fully implemented)
* @param selfInter: whether to eliminate self intersection
* (OCCT document states the option is not implemented)
* @param offsetMode: defines the construction type of parallels applied to free edges
* (OCCT document states the option is not implemented)
* @param fill: whether to build a solid by fill the offset
* @param join: join type. Only support JoinType::Arc and JoinType::Intersection.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
TopoShape makeElementOffset(double offset,
double tol,
bool intersection = false,
bool selfInter = false,
short offsetMode = 0,
JoinType join = JoinType::arc,
FillType fill = FillType::noFill,
const char* op = nullptr) const
{
return TopoShape(0, Hasher).makeElementOffset(*this,
offset,
tol,
intersection,
selfInter,
offsetMode,
join,
fill,
op);
}
/** Make a 2D offset of a given shape
*
* @param source: source shape of edge, wire, face, or compound
* @param offset: distance to offset
* @param allowOpenResult: whether to allow open edge/wire
* @param join: join type. Only support JoinType::Arc and JoinType::Intersection.
* @param intersection: if true, then offset all non-compound shape
* together to deal with possible intersection after
* expanding the shape. If false, then offset each
* shape separately.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return The original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a self reference so that multiple operations can be carried out
* for the same shape in the same line of code.
*/
TopoShape& makeElementOffset2D(const TopoShape& source,
double offset,
JoinType join = JoinType::arc,
FillType fill = FillType::noFill,
OpenResult allowOpenResult = OpenResult::allowOpenResult,
bool intersection = false,
const char* op = nullptr);
/** Make a 2D offset of a given shape
*
* @param source: source shape of edge, wire, face, or compound
* @param offset: distance to offset
* @param allowOpenResult: whether to allow open edge/wire
* @param join: join type. Only support JoinType::Arc and JoinType::Intersection.
* @param intersection: if true, then offset all non-compound shape
* together to deal with possible intersection after
* expanding the shape. If false, then offset each
* shape separately.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
TopoShape makeElementOffset2D(double offset,
JoinType join = JoinType::arc,
FillType fill = FillType::noFill,
OpenResult allowOpenResult = OpenResult::allowOpenResult,
bool intersection = false,
const char* op = nullptr) const
{
return TopoShape(0, Hasher)
.makeElementOffset2D(*this, offset, join, fill, allowOpenResult, intersection, op);
}
/** Make a 2D offset of face with separate control for outer and inner (hole) wires
*
* @param source: source shape of any type, but only faces inside will be used
* @param offset: distance to offset for outer wires of the faces
* @param innerOffset: distance to offset for inner wires of the faces
* @param join: join type of outer wire. Only support JoinType::Arc and JoinType::Intersection.
* @param innerJoin: join type of inner wire. Only support JoinType::Arc and
* JoinType::Intersection.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return The original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a self reference so that multiple operations can be carried out
* for the same shape in the same line of code.
*/
TopoShape& makeElementOffsetFace(const TopoShape& source,
double offset,
double innerOffset,
JoinType join = JoinType::arc,
JoinType innerJoin = JoinType::arc,
const char* op = nullptr);
/** Make a 2D offset of face with separate control for outer and inner (hole) wires
*
* @param source: source shape of any type, but only faces inside will be used
* @param offset: distance to offset for outer wires of the faces
* @param innerOffset: distance to offset for inner wires of the faces
* @param join: join type of outer wire. Only support JoinType::Arc and JoinType::Intersection.
* @param innerJoin: join type of inner wire. Only support JoinType::Arc and
* JoinType::Intersection.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
TopoShape makeElementOffsetFace(double offset,
double innerOffset,
JoinType join = JoinType::arc,
JoinType innerJoin = JoinType::arc,
const char* op = nullptr) const
{
return TopoShape(0, Hasher)
.makeElementOffsetFace(*this, offset, innerOffset, join, innerJoin, op);
}
/** Make revolved shell around a basis shape
@@ -1552,7 +1729,7 @@ public:
TopoShape& makeElementGTransform(const TopoShape& source,
const Base::Matrix4D& mat,
const char* op = nullptr,
Copy copy = Copy::noCopy);
CopyType copy = CopyType::noCopy);
/** Make a new shape with transformation that may contain non-uniform scaling
*
@@ -1568,7 +1745,7 @@ public:
*/
TopoShape makeElementGTransform(const Base::Matrix4D& mat,
const char* op = nullptr,
Copy copy = Copy::noCopy) const
CopyType copy = CopyType::noCopy) const
{
return TopoShape(Tag, Hasher).makeElementGTransform(*this, mat, op, copy);
}
@@ -1858,7 +2035,7 @@ public:
const Base::Matrix4D& mat,
const char* op = nullptr,
CheckScale checkScale = CheckScale::noScaleCheck,
Copy copy = Copy::noCopy);
CopyType copy = CopyType::noCopy);
/** Make a new shape with transformation
*
@@ -1881,7 +2058,7 @@ public:
const Base::Matrix4D& mat,
const char* op = nullptr,
CheckScale checkScale = CheckScale::noScaleCheck,
Copy copy = Copy::noCopy)
CopyType copy = CopyType::noCopy)
{
_makeElementTransform(source, mat, op, checkScale, copy);
return *this;
@@ -1905,7 +2082,7 @@ public:
TopoShape makeElementTransform(const Base::Matrix4D& mat,
const char* op = nullptr,
CheckScale checkScale = CheckScale::noScaleCheck,
Copy copy = Copy::noCopy) const
CopyType copy = CopyType::noCopy)
{
return TopoShape(Tag, Hasher).makeElementTransform(*this, mat, op, checkScale, copy);
}
@@ -1926,7 +2103,7 @@ public:
TopoShape& makeElementTransform(const TopoShape& shape,
const gp_Trsf& trsf,
const char* op = nullptr,
Copy copy = Copy::noCopy);
CopyType copy = CopyType::noCopy);
/** Make a new shape with transformation
*
@@ -1940,7 +2117,7 @@ public:
* modified
*/
TopoShape
makeElementTransform(const gp_Trsf& trsf, const char* op = nullptr, Copy copy = Copy::noCopy)
makeElementTransform(const gp_Trsf& trsf, const char* op = nullptr, CopyType copy = CopyType::noCopy)
{
return TopoShape(Tag, Hasher).makeElementTransform(*this, trsf, op, copy);
}

View File

@@ -52,6 +52,7 @@
#include <BRepBuilderAPI_GTransform.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeSolid.hxx>
#include <BRepBuilderAPI_NurbsConvert.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <BRepBuilderAPI_MakeSolid.hxx>
@@ -99,8 +100,11 @@
#include "TopoShapeMapper.h"
#include "FaceMaker.h"
#include "Geometry.h"
#include "BRepOffsetAPI_MakeOffsetFix.h"
#include <App/ElementNamingUtils.h>
#include <ShapeAnalysis_FreeBoundsProperties.hxx>
#include <BRepBuilderAPI_MakeSolid.hxx>
FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT
@@ -2270,6 +2274,538 @@ TopoShape& TopoShape::makeElementPipeShell(const std::vector<TopoShape>& shapes,
return makeElementShape(mkPipeShell, shapes, op);
}
TopoShape& TopoShape::makeElementOffset(const TopoShape& shape,
double offset,
double tol,
bool intersection,
bool selfInter,
short offsetMode,
JoinType join,
FillType fill,
const char* op)
{
if (!op) {
op = Part::OpCodes::Offset;
}
BRepOffsetAPI_MakeOffsetShape mkOffset;
mkOffset.PerformByJoin(shape.getShape(),
offset,
tol,
BRepOffset_Mode(offsetMode),
intersection ? Standard_True : Standard_False,
selfInter ? Standard_True : Standard_False,
GeomAbs_JoinType(join));
if (!mkOffset.IsDone()) {
FC_THROWM(Base::CADKernelError, "BRepOffsetAPI_MakeOffsetShape not done");
}
TopoShape res(Tag, Hasher);
res.makeElementShape(mkOffset, shape, op);
if (shape.hasSubShape(TopAbs_SOLID) && !res.hasSubShape(TopAbs_SOLID)) {
try {
res = res.makeElementSolid();
}
catch (Standard_Failure& e) {
FC_WARN("failed to make solid: " << e.GetMessageString());
}
}
if (fill == FillType::noFill) {
*this = res;
return *this;
}
// get perimeter wire of original shape.
// Wires returned seem to have edges in connection order.
ShapeAnalysis_FreeBoundsProperties freeCheck(shape.getShape());
freeCheck.Perform();
if (freeCheck.NbClosedFreeBounds() < 1) {
FC_THROWM(Base::CADKernelError, "no closed bounds");
}
BRep_Builder builder;
std::vector<TopoShape> shapes;
for (int index = 1; index <= freeCheck.NbClosedFreeBounds(); ++index) {
TopoShape originalWire(shape.Tag,
shape.Hasher,
freeCheck.ClosedFreeBound(index)->FreeBound());
originalWire.mapSubElement(shape);
const BRepAlgo_Image& img = mkOffset.MakeOffset().OffsetEdgesFromShapes();
// build offset wire.
TopoDS_Wire offsetWire;
builder.MakeWire(offsetWire);
for (const auto& s : originalWire.getSubShapes(TopAbs_EDGE)) {
if (!img.HasImage(s)) {
FC_THROWM(Base::CADKernelError, "no image for shape");
}
const TopTools_ListOfShape& currentImage = img.Image(s);
TopTools_ListIteratorOfListOfShape listIt;
int edgeCount(0);
TopoDS_Edge mappedEdge;
for (listIt.Initialize(currentImage); listIt.More(); listIt.Next()) {
if (listIt.Value().ShapeType() != TopAbs_EDGE) {
continue;
}
edgeCount++;
mappedEdge = TopoDS::Edge(listIt.Value());
}
if (edgeCount != 1) {
std::ostringstream stream;
stream << "wrong edge count: " << edgeCount << std::endl;
FC_THROWM(Base::CADKernelError, stream.str().c_str());
}
builder.Add(offsetWire, mappedEdge);
}
std::vector<TopoShape> wires;
wires.push_back(originalWire);
wires.push_back(TopoShape(Tag, Hasher, offsetWire));
wires.back().mapSubElement(res);
// It would be nice if we could get thruSections to build planar faces
// in all areas possible, so we could run through refine. I tried setting
// ruled to standard_true, but that didn't have the desired affect.
BRepOffsetAPI_ThruSections aGenerator;
aGenerator.AddWire(TopoDS::Wire(originalWire.getShape()));
aGenerator.AddWire(offsetWire);
aGenerator.Build();
if (!aGenerator.IsDone()) {
FC_THROWM(Base::CADKernelError, "ThruSections failed");
}
shapes.push_back(TopoShape(Tag, Hasher).makeElementShape(aGenerator, wires));
}
TopoShape perimeterCompound(Tag, Hasher);
perimeterCompound.makeElementCompound(shapes, op);
// still had to sew. not using the passed in parameter for sew.
// Sew has it's own default tolerance. Opinions?
BRepBuilderAPI_Sewing sewTool;
sewTool.Add(shape.getShape());
sewTool.Add(perimeterCompound.getShape());
sewTool.Add(res.getShape());
sewTool.Perform(); // Perform Sewing
TopoDS_Shape outputShape = sewTool.SewedShape();
if ((outputShape.ShapeType() == TopAbs_SHELL) && (outputShape.Closed())) {
BRepBuilderAPI_MakeSolid solidMaker(TopoDS::Shell(outputShape));
if (solidMaker.IsDone()) {
TopoDS_Solid temp = solidMaker.Solid();
// contrary to the occ docs the return value OrientCloseSolid doesn't
// indicate whether the shell was open or not. It returns true with an
// open shell and we end up with an invalid solid.
if (BRepLib::OrientClosedSolid(temp)) {
outputShape = temp;
}
}
}
shapes.clear();
shapes.push_back(shape);
shapes.push_back(res);
shapes.push_back(perimeterCompound);
*this = TopoShape(Tag, Hasher)
.makeShapeWithElementMap(outputShape, MapperSewing(sewTool), shapes, op);
return *this;
}
TopoShape& TopoShape::makeElementOffsetFace(const TopoShape& shape,
double offset,
double innerOffset,
JoinType joinType,
JoinType innerJoinType,
const char* op)
{
if (std::abs(innerOffset) < Precision::Confusion()
&& std::abs(offset) < Precision::Confusion()) {
*this = shape;
return *this;
}
if (shape.isNull()) {
FC_THROWM(Base::ValueError, "makeOffsetFace: input shape is null!");
}
if (!shape.hasSubShape(TopAbs_FACE)) {
FC_THROWM(Base::ValueError, "makeOffsetFace: no face found");
}
std::vector<TopoShape> res;
for (auto& face : shape.getSubTopoShapes(TopAbs_FACE)) {
std::vector<TopoShape> wires;
TopoShape outerWire = face.splitWires(&wires, ReorientForward);
if (wires.empty()) {
res.push_back(makeElementOffset2D(face,
offset,
joinType,
FillType::noFill,
OpenResult::noOpenResult,
false,
op));
continue;
}
if (outerWire.isNull()) {
FC_THROWM(Base::CADKernelError, "makeOffsetFace: missing outer wire!");
}
if (std::abs(offset) > Precision::Confusion()) {
outerWire = outerWire.makeElementOffset2D(offset,
joinType,
FillType::noFill,
OpenResult::noOpenResult,
false,
op);
}
if (std::abs(innerOffset) > Precision::Confusion()) {
TopoShape innerWires(0, Hasher);
innerWires.makeElementCompound(wires,
"",
SingleShapeCompoundCreationPolicy::returnShape);
innerWires = innerWires.makeElementOffset2D(innerOffset,
innerJoinType,
FillType::noFill,
OpenResult::noOpenResult,
true,
op);
wires = innerWires.getSubTopoShapes(TopAbs_WIRE);
}
wires.push_back(outerWire);
gp_Pln pln;
res.push_back(TopoShape(0, Hasher).makeElementFace(wires,
nullptr,
nullptr,
face.findPlane(pln) ? &pln : nullptr));
}
return makeElementCompound(res, "", SingleShapeCompoundCreationPolicy::returnShape);
}
TopoShape& TopoShape::makeElementOffset2D(const TopoShape& shape,
double offset,
JoinType joinType,
FillType fill,
OpenResult allowOpenResult,
bool intersection,
const char* op)
{
if (!op) {
op = Part::OpCodes::Offset2D;
}
if (shape.isNull()) {
FC_THROWM(Base::ValueError, "makeOffset2D: input shape is null!");
}
if (allowOpenResult == OpenResult::allowOpenResult && OCC_VERSION_HEX < 0x060900) {
FC_THROWM(Base::AttributeError, "openResult argument is not supported on OCC < 6.9.0.");
}
// OUTLINE OF MAKEOFFSET2D
// * Prepare shapes to process
// ** if _Shape is a compound, recursively call this routine for all subcompounds
// ** if intrsection, dump all non-compound children into shapes to process; otherwise call this
// routine recursively for all children
// ** if _shape isn't a compound, dump it straight to shapes to process
// * Test for shape types, and convert them all to wires
// * find plane
// * OCC call (BRepBuilderAPI_MakeOffset)
// * postprocessing (facemaking):
// ** convert offset result back to faces, if inputs were faces
// ** OR do offset filling:
// *** for closed wires, simply feed source wires + offset wires to smart facemaker
// *** for open wires, try to connect source anf offset result by creating new edges (incomplete
// implementation)
// ** actual call to FaceMakerBullseye, unified for all facemaking.
std::vector<TopoShape> shapesToProcess;
std::vector<TopoShape> shapesToReturn;
SingleShapeCompoundCreationPolicy outputPolicy = SingleShapeCompoundCreationPolicy::returnShape;
if (shape.getShape().ShapeType() == TopAbs_COMPOUND) {
if (!intersection) {
// simply recursively process the children, independently
expandCompound(shape, shapesToProcess);
outputPolicy = SingleShapeCompoundCreationPolicy::forceCompound;
}
else {
// collect non-compounds from this compound for collective offset. Process other shapes
// independently.
for (auto& s : shape.getSubTopoShapes()) {
if (s.getShape().ShapeType() == TopAbs_COMPOUND) {
// recursively process subcompounds
shapesToReturn.push_back(TopoShape(Tag, Hasher)
.makeElementOffset2D(s,
offset,
joinType,
fill,
allowOpenResult,
intersection,
op));
outputPolicy = SingleShapeCompoundCreationPolicy::forceCompound;
}
else {
shapesToProcess.push_back(s);
}
}
}
}
else {
shapesToProcess.push_back(shape);
}
if (shapesToProcess.size() > 0) {
TopoShape res(Tag, Hasher);
// although 2d offset supports offsetting a face directly, it seems there is
// no way to do a collective offset of multiple faces. So, we are doing it
// by getting all wires from the faces, and applying offsets to them, and
// reassembling the faces later.
std::vector<TopoShape> sourceWires;
bool haveWires = false;
bool haveFaces = false;
for (auto& s : shapesToProcess) {
const auto& sh = s.getShape();
switch (sh.ShapeType()) {
case TopAbs_EDGE:
sourceWires.push_back(s.makeElementWires());
haveWires = true;
break;
case TopAbs_WIRE:
sourceWires.push_back(s);
haveWires = true;
break;
case TopAbs_FACE: {
auto outerWire = s.splitWires(&sourceWires);
sourceWires.push_back(outerWire);
haveFaces = true;
} break;
default:
FC_THROWM(Base::TypeError,
"makeOffset2D: input shape is not an edge, wire or face or compound "
"of those.");
break;
}
}
if (haveWires && haveFaces) {
FC_THROWM(
Base::TypeError,
"makeOffset2D: collective offset of a mix of wires and faces is not supported");
}
if (haveFaces) {
allowOpenResult = OpenResult::noOpenResult;
}
// find plane.
gp_Pln workingPlane;
if (!TopoShape()
.makeElementCompound(sourceWires,
"",
SingleShapeCompoundCreationPolicy::returnShape)
.findPlane(workingPlane)) {
FC_THROWM(Base::CADKernelError, "makeOffset2D: wires are nonplanar or noncoplanar");
}
// do the offset..
TopoShape offsetShape;
if (fabs(offset) > Precision::Confusion()) {
BRepOffsetAPI_MakeOffsetFix mkOffset(GeomAbs_JoinType(joinType),
allowOpenResult == OpenResult::allowOpenResult);
for (auto& w : sourceWires) {
mkOffset.AddWire(TopoDS::Wire(w.getShape()));
}
try {
#if defined(__GNUC__) && defined(FC_OS_LINUX)
Base::SignalException se;
#endif
mkOffset.Perform(offset);
}
catch (Standard_Failure&) {
throw;
}
catch (...) {
FC_THROWM(Base::CADKernelError,
"BRepOffsetAPI_MakeOffset has crashed! (Unknown exception caught)");
}
if (mkOffset.Shape().IsNull()) {
FC_THROWM(NullShapeException, "makeOffset2D: result of offsetting is null!");
}
// Copying shape to fix strange orientation behavior, OCC7.0.0. See bug #2699
// http://www.freecadweb.org/tracker/view.php?id=2699
offsetShape = shape.makeElementShape(mkOffset, op).makeElementCopy();
}
else {
offsetShape = TopoShape(Tag, Hasher)
.makeElementCompound(sourceWires,
0,
SingleShapeCompoundCreationPolicy::returnShape);
}
std::vector<TopoShape> offsetWires;
// interestingly, if wires are removed, empty compounds are returned by MakeOffset (as of
// OCC 7.0.0) so, we just extract all nesting
expandCompound(offsetShape, offsetWires);
if (offsetWires.empty()) {
FC_THROWM(Base::CADKernelError, "makeOffset2D: offset result has no wires.");
}
std::vector<TopoShape> wiresForMakingFaces;
if (fill == FillType::noFill) {
if (haveFaces) {
wiresForMakingFaces.insert(wiresForMakingFaces.end(),
offsetWires.begin(),
offsetWires.end());
}
else {
shapesToReturn.insert(shapesToReturn.end(), offsetWires.begin(), offsetWires.end());
}
}
else {
// fill offset
if (fabs(offset) < Precision::Confusion()) {
FC_THROWM(Base::ValueError,
"makeOffset2D: offset distance is zero. Can't fill offset.");
}
// filling offset. There are three major cases to consider:
// 1. source wires and result wires are closed (simplest) -> make face
// from source wire + offset wire
//
// 2. source wire is open, but offset wire is closed (if not
// allowOpenResult). -> throw away source wire and make face right from
// offset result.
//
// 3. source and offset wire are both open (note that there may be
// closed islands in offset result) -> need connecting offset result to
// source wire with new edges
// first, lets split apart closed and open wires.
std::vector<TopoShape> closedWires;
std::vector<TopoShape> openWires;
for (auto& w : sourceWires) {
if (BRep_Tool::IsClosed(TopoDS::Wire(w.getShape()))) {
closedWires.push_back(w);
}
else {
openWires.push_back(w);
}
}
for (auto& w : offsetWires) {
if (BRep_Tool::IsClosed(TopoDS::Wire(w.getShape()))) {
closedWires.push_back(w);
}
else {
openWires.push_back(w);
}
}
wiresForMakingFaces.insert(wiresForMakingFaces.end(),
closedWires.begin(),
closedWires.end());
if (allowOpenResult == OpenResult::noOpenResult || openWires.size() == 0) {
// just ignore all open wires
}
else {
// We need to connect open wires to form closed wires.
// for now, only support offsetting one open wire -> there should be exactly two
// open wires for connecting
if (openWires.size() != 2) {
FC_THROWM(Base::CADKernelError,
"makeOffset2D: collective offset with filling of multiple wires is "
"not supported yet.");
}
TopoShape openWire1 = openWires.front();
TopoShape openWire2 = openWires.back();
// find open vertices
BRepTools_WireExplorer xp;
xp.Init(TopoDS::Wire(openWire1.getShape()));
TopoDS_Vertex v1 = xp.CurrentVertex();
for (; xp.More(); xp.Next()) {};
TopoDS_Vertex v2 = xp.CurrentVertex();
// find open vertices
xp.Init(TopoDS::Wire(openWire2.getShape()));
TopoDS_Vertex v3 = xp.CurrentVertex();
for (; xp.More(); xp.Next()) {};
TopoDS_Vertex v4 = xp.CurrentVertex();
// check
if (v1.IsNull()) {
FC_THROWM(NullShapeException, "v1 is null");
}
if (v2.IsNull()) {
FC_THROWM(NullShapeException, "v2 is null");
}
if (v3.IsNull()) {
FC_THROWM(NullShapeException, "v3 is null");
}
if (v4.IsNull()) {
FC_THROWM(NullShapeException, "v4 is null");
}
// assemble new wire
// we want the connection order to be
// v1 -> openWire1 -> v2 -> (new edge) -> v4 -> openWire2(rev) -> v3 -> (new edge)
// -> v1 let's check if it's the case. If not, we reverse one wire and swap its
// endpoints.
if (fabs(gp_Vec(BRep_Tool::Pnt(v2), BRep_Tool::Pnt(v3)).Magnitude() - fabs(offset))
<= BRep_Tool::Tolerance(v2) + BRep_Tool::Tolerance(v3)) {
openWire2._Shape.Reverse();
std::swap(v3, v4);
v3.Reverse();
v4.Reverse();
}
else if ((fabs(gp_Vec(BRep_Tool::Pnt(v2), BRep_Tool::Pnt(v4)).Magnitude()
- fabs(offset))
<= BRep_Tool::Tolerance(v2) + BRep_Tool::Tolerance(v4))) {
// orientation is as expected, nothing to do
}
else {
FC_THROWM(
Base::CADKernelError,
"makeOffset2D: fill offset: failed to establish open vertex relationship.");
}
// now directions of open wires are aligned. Finally. make new wire!
BRepBuilderAPI_MakeWire mkWire;
// add openWire1
BRepTools_WireExplorer it;
for (it.Init(TopoDS::Wire(openWire1.getShape())); it.More(); it.Next()) {
mkWire.Add(it.Current());
}
// add first joining edge
mkWire.Add(BRepBuilderAPI_MakeEdge(v2, v4).Edge());
// add openWire2, in reverse order
openWire2._Shape.Reverse();
for (it.Init(TopoDS::Wire(openWire2.getShape())); it.More(); it.Next()) {
mkWire.Add(it.Current());
}
// add final joining edge
mkWire.Add(BRepBuilderAPI_MakeEdge(v3, v1).Edge());
mkWire.Build();
wiresForMakingFaces.push_back(
TopoShape(Tag, Hasher).makeElementShape(mkWire, openWires, op));
}
}
// make faces
if (wiresForMakingFaces.size() > 0) {
TopoShape face(0, Hasher);
face.makeElementFace(wiresForMakingFaces, nullptr, nullptr, &workingPlane);
expandCompound(face, shapesToReturn);
}
}
return makeElementCompound(shapesToReturn, op, outputPolicy);
}
TopoShape& TopoShape::makeElementThickSolid(const TopoShape& shape,
const std::vector<TopoShape>& faces,
double offset,
@@ -2316,16 +2852,6 @@ TopoShape& TopoShape::makeElementThickSolid(const TopoShape& shape,
}
remFace.Append(face.getShape());
}
#if OCC_VERSION_HEX < 0x070200
BRepOffsetAPI_MakeThickSolid mkThick(shape.getShape(),
remFace,
offset,
tol,
BRepOffset_Mode(offsetMode),
intersection ? Standard_True : Standard_False,
selfInter ? Standard_True : Standard_False,
GeomAbs_JoinType(join));
#else
BRepOffsetAPI_MakeThickSolid mkThick;
mkThick.MakeThickSolidByJoin(shape.getShape(),
remFace,
@@ -2335,7 +2861,6 @@ TopoShape& TopoShape::makeElementThickSolid(const TopoShape& shape,
intersection ? Standard_True : Standard_False,
selfInter ? Standard_True : Standard_False,
GeomAbs_JoinType(join));
#endif
return makeElementShape(mkThick, shape, op);
}
@@ -2352,11 +2877,7 @@ TopoShape& TopoShape::makeElementWires(const std::vector<TopoShape>& shapes,
if (shapes.size() == 1) {
return makeElementWires(shapes[0], op, tol, policy, output);
}
return makeElementWires(TopoShape(Tag).makeElementCompound(shapes),
op,
tol,
policy,
output);
return makeElementWires(TopoShape(Tag).makeElementCompound(shapes), op, tol, policy, output);
}
@@ -2651,7 +3172,7 @@ bool TopoShape::_makeElementTransform(const TopoShape& shape,
const Base::Matrix4D& mat,
const char* op,
CheckScale checkScale,
Copy copy)
CopyType copy)
{
if (checkScale == CheckScale::checkScale) {
auto scaleType = mat.hasScale();
@@ -2667,18 +3188,17 @@ bool TopoShape::_makeElementTransform(const TopoShape& shape,
TopoShape& TopoShape::makeElementTransform(const TopoShape& shape,
const gp_Trsf& trsf,
const char* op,
Copy copy)
CopyType copy)
{
if (copy == Copy::noCopy) {
if (copy == CopyType::noCopy) {
// OCCT checks the ScaleFactor against gp::Resolution() which is DBL_MIN!!!
// No scaling is 1 as in 1:1
const bool scaling = Abs(Abs(trsf.ScaleFactor()) - 1) > Precision::Confusion();
const bool negative_scaling =
trsf.ScaleFactor() * trsf.HVectorialPart().Determinant() < 0.0;
copy = negative_scaling || scaling ? Copy::copy : Copy::noCopy;
copy = trsf.ScaleFactor() * trsf.HVectorialPart().Determinant() < 0.
|| Abs(Abs(trsf.ScaleFactor()) - 1) > Precision::Confusion()
? CopyType::copy
: CopyType::noCopy;
}
TopoShape tmp(shape);
if (copy == Copy::copy) {
if (copy == CopyType::copy) {
if (shape.isNull()) {
FC_THROWM(NullShapeException, "Null input shape");
}
@@ -2713,7 +3233,7 @@ TopoShape& TopoShape::makeElementTransform(const TopoShape& shape,
TopoShape& TopoShape::makeElementGTransform(const TopoShape& shape,
const Base::Matrix4D& mat,
const char* op,
Copy copy)
CopyType copy)
{
if (shape.isNull()) {
FC_THROWM(NullShapeException, "Null input shape");
@@ -2736,7 +3256,7 @@ TopoShape& TopoShape::makeElementGTransform(const TopoShape& shape,
// geometric transformation
TopoShape tmp(shape);
BRepBuilderAPI_GTransform mkTrf(shape.getShape(), matrix, copy == Copy::copy);
BRepBuilderAPI_GTransform mkTrf(shape.getShape(), matrix, copy == CopyType::copy);
tmp.setShape(mkTrf.Shape(), false);
if (op || (shape.Tag && shape.Tag != Tag)) {
setShape(tmp._Shape);
@@ -3383,9 +3903,7 @@ TopoShape& TopoShape::makeElementGeneralFuse(const std::vector<TopoShape>& _shap
if (tol > 0.0) {
mkGFA.SetFuzzyValue(tol);
}
#if OCC_VERSION_HEX >= 0x070000
mkGFA.SetNonDestructive(Standard_True);
#endif
mkGFA.Build();
if (!mkGFA.IsDone()) {
FC_THROWM(Base::CADKernelError, "GeneralFuse failed");

View File

@@ -1359,7 +1359,7 @@ TEST_F(TopoShapeExpansionTest, makeElementShellFromWires)
// Assert
TopoShape result = topoShape1.makeElementShellFromWires(shapes);
#if OCC_VERSION_HEX >= 0x070400
EXPECT_EQ(result.getShape().NbChildren(), 20); // 6 TODO: VERSION DEPENDENT?
EXPECT_EQ(result.getShape().NbChildren(), 20); // Have a NbChildren method?
#endif
EXPECT_EQ(result.countSubElements("Vertex"), 8);
EXPECT_EQ(result.countSubElements("Edge"), 32);
@@ -2642,4 +2642,212 @@ TEST_F(TopoShapeExpansionTest, traceElement)
}));
}
TEST_F(TopoShapeExpansionTest, makeElementOffset)
{
// Arrange
// Arrange
auto [cube1, cube2] = CreateTwoCubes();
auto tr {gp_Trsf()};
tr.SetTranslation(gp_Vec(gp_XYZ(-0.5, -0.5, 0)));
cube2.Move(TopLoc_Location(tr));
TopoShape topoShape1 {cube1, 1L};
TopoShape topoShape2 {cube2, 2L};
// Act
// TopoShape topoShape3 {6L};
TopoShape result {3L};
// topoShape1.makeElementFuse({topoShape2,topoShape2}); // op, tolerance
result.makeElementOffset(topoShape1, 0.25, 1e-07);
auto elements = elementMap(result);
Base::BoundBox3d bb = result.getBoundBox();
// Assert shape is correct
EXPECT_TRUE(
PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(-0.25, -0.25, -0.25, 1.25, 1.25, 1.25)));
EXPECT_FLOAT_EQ(getVolume(result.getShape()), 3.1544986);
// Assert elementMap is correct
// EXPECT_EQ(elements.size(), 98);
// EXPECT_EQ(elements.count(IndexedName("Face", 1)), 1);
// EXPECT_EQ(
// elements[IndexedName("Face", 1)],
// MappedName("Face2;:G;OFS;:H1:7,F"));
EXPECT_TRUE(elementsMatch(result,
{
"Edge10;:G;OFS;:H1:7,F",
"Edge10;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E",
"Edge10;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E",
"Edge10;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E",
"Edge10;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E",
"Edge11;:G;OFS;:H1:7,F",
"Edge11;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E",
"Edge11;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E",
"Edge11;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E",
"Edge11;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E",
"Edge12;:G;OFS;:H1:7,F",
"Edge12;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E",
"Edge12;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E",
"Edge12;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E",
"Edge12;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E",
"Edge1;:G;OFS;:H1:7,F",
"Edge1;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E",
"Edge1;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V",
"Edge1;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge1;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E",
"Edge1;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge1;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E",
"Edge1;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge1;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E",
"Edge2;:G;OFS;:H1:7,F",
"Edge2;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E",
"Edge2;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V",
"Edge2;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge2;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E",
"Edge2;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge2;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E",
"Edge2;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E",
"Edge3;:G;OFS;:H1:7,F",
"Edge3;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E",
"Edge3;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V",
"Edge3;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge3;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E",
"Edge3;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E",
"Edge3;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge3;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E",
"Edge4;:G;OFS;:H1:7,F",
"Edge4;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E",
"Edge4;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V",
"Edge4;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge4;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E",
"Edge4;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E",
"Edge4;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E",
"Edge5;:G;OFS;:H1:7,F",
"Edge5;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E",
"Edge5;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V",
"Edge5;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge5;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E",
"Edge5;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge5;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E",
"Edge5;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge5;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E",
"Edge6;:G;OFS;:H1:7,F",
"Edge6;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E",
"Edge6;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V",
"Edge6;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge6;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E",
"Edge6;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge6;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E",
"Edge6;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E",
"Edge7;:G;OFS;:H1:7,F",
"Edge7;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E",
"Edge7;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V",
"Edge7;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge7;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E",
"Edge7;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E",
"Edge7;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge7;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E",
"Edge8;:G;OFS;:H1:7,F",
"Edge8;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E",
"Edge8;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V",
"Edge8;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V",
"Edge8;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E",
"Edge8;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E",
"Edge8;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E",
"Edge9;:G;OFS;:H1:7,F",
"Edge9;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E",
"Edge9;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E",
"Edge9;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E",
"Edge9;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E",
"Face1;:G;OFS;:H1:7,F",
"Face2;:G;OFS;:H1:7,F",
"Face3;:G;OFS;:H1:7,F",
"Face4;:G;OFS;:H1:7,F",
"Face5;:G;OFS;:H1:7,F",
"Face6;:G;OFS;:H1:7,F",
"Vertex1;:G;OFS;:H1:7,F",
"Vertex2;:G;OFS;:H1:7,F",
"Vertex3;:G;OFS;:H1:7,F",
"Vertex4;:G;OFS;:H1:7,F",
"Vertex5;:G;OFS;:H1:7,F",
"Vertex6;:G;OFS;:H1:7,F",
"Vertex7;:G;OFS;:H1:7,F",
"Vertex8;:G;OFS;:H1:7,F",
}));
}
TEST_F(TopoShapeExpansionTest, makeElementOffsetFace)
{
// Arrange
const float Len = 3, Wid = 2, Rad = 1;
auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad);
// Act
TopoShape result {face1, 1L};
result.makeElementOffsetFace(result, 0.25, 0);
auto elements = elementMap(result);
Base::BoundBox3d bb = result.getBoundBox();
// Assert shape is correct
EXPECT_TRUE(
PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(-0.25, -0.25, 0.0, 3.25, 2.25, 0.0)));
// Assert elementMap is correct
// EXPECT_EQ(elements.size(), 19);
// EXPECT_EQ(elements.count(IndexedName("Face", 1)), 1);
// EXPECT_EQ(
// elements[IndexedName("Face", 1)],
// MappedName("Edge1;:G;OFF;:H1:7,E;FAC;:H1:4,F"));
EXPECT_TRUE(elementsMatch(result,
{
"Edge1;:G;OFF;:H1:7,E",
"Edge1;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V",
"Edge1;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V",
"Edge1;:G;OFF;:H1:7,E;FAC;:H1:4,F",
"Edge2;:G;OFF;:H1:7,E",
"Edge2;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V",
"Edge2;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V",
"Edge3;:G;OFF;:H1:7,E",
"Edge3;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V",
"Edge3;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V",
"Edge4;:G;OFF;:H1:7,E",
"Edge4;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V",
"Edge4;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V",
"Edge5;:H1,E",
"Vertex1;:G;OFF;:H1:7,E",
"Vertex2;:G;OFF;:H1:7,E",
"Vertex3;:G;OFF;:H1:7,E",
"Vertex4;:G;OFF;:H1:7,E",
"Vertex5;:H1,V",
}));
}
TEST_F(TopoShapeExpansionTest, makeElementOffset2D)
{
// Arrange
const float Len = 3, Wid = 2, Rad = 1;
auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad);
// Act
TopoShape result {wire1, 1L};
result.makeElementOffset2D(result, 0.25);
auto elements = elementMap(result);
Base::BoundBox3d bb = result.getBoundBox();
// Assert shape is correct
EXPECT_TRUE(
PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(-0.25, -0.25, 0.0, 3.25, 2.25, 0.0)));
// Assert elementMap is correct
// EXPECT_EQ(elements.size(), 10);
// EXPECT_EQ(elements.count(IndexedName("Edge", 1)), 1);
// EXPECT_EQ(
// elements[IndexedName("Edge", 1)],
// MappedName("Edge1;:G;OFF;:H1:7,E;OFF;:H1:4,E"));
EXPECT_TRUE(elementsMatch(result,
{
"Edge1;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V;OFF;:H1:4,V",
"Edge1;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V;OFF;:H1:4,V",
"Edge1;:G;OFF;:H1:7,E;OFF;:H1:4,E",
"Edge2;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V;OFF;:H1:4,V",
"Edge2;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V;OFF;:H1:4,V",
"Edge2;:G;OFF;:H1:7,E;OFF;:H1:4,E",
"Vertex1;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V;OFF;:H1:4,V",
"Vertex1;:G;OFF;:H1:7,E;OFF;:H1:4,E",
"Vertex3;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V;OFF;:H1:4,V",
"Vertex3;:G;OFF;:H1:7,E;OFF;:H1:4,E",
}));
}
// NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)