TopoShape/Part: Bring in PartDesign dependencies

This commit is contained in:
Zheng, Lei
2024-04-07 11:47:13 -04:00
committed by bgbsww
parent 83ea7e4355
commit 82c3e107d7
12 changed files with 935 additions and 216 deletions

View File

@@ -1359,6 +1359,47 @@ std::vector<Part::cutFaces> Part::findAllFacesCutBy(
return result;
}
std::vector<Part::cutFaces> Part::findAllFacesCutBy(
const TopoShape& shape, const TopoShape& face, const gp_Dir& dir)
{
// Find the centre of gravity of the face
GProp_GProps props;
BRepGProp::SurfaceProperties(face.getShape(),props);
gp_Pnt cog = props.CentreOfMass();
// create a line through the centre of gravity
gp_Lin line = gce_MakeLin(cog, dir);
// Find intersection of line with all faces of the shape
std::vector<cutFaces> result;
BRepIntCurveSurface_Inter mkSection;
// TODO: Less precision than Confusion() should be OK?
for (mkSection.Init(shape.getShape(), line, Precision::Confusion()); mkSection.More(); mkSection.Next()) {
gp_Pnt iPnt = mkSection.Pnt();
double dsq = cog.SquareDistance(iPnt);
if (dsq < Precision::Confusion())
continue; // intersection with original face
// Find out which side of the original face the intersection is on
gce_MakeDir mkDir(cog, iPnt);
if (!mkDir.IsDone())
continue; // some error (appears highly unlikely to happen, though...)
if (mkDir.Value().IsOpposite(dir, Precision::Confusion()))
continue; // wrong side of face (opposite to extrusion direction)
cutFaces newF;
newF.face = mkSection.Face();
newF.face.mapSubElement(shape);
newF.distsq = dsq;
result.push_back(newF);
}
return result;
}
bool Part::checkIntersection(const TopoDS_Shape& first, const TopoDS_Shape& second,
const bool quick, const bool touch_is_intersection) {

View File

@@ -220,6 +220,10 @@ PartExport
std::vector<cutFaces> findAllFacesCutBy(const TopoDS_Shape& shape,
const TopoDS_Shape& face, const gp_Dir& dir);
PartExport
std::vector<cutFaces> findAllFacesCutBy(const TopoShape& shape,
const TopoShape& face, const gp_Dir& dir);
/**
* Check for intersection between the two shapes. Only solids are guaranteed to work properly
* There are two modes:

View File

@@ -1153,16 +1153,14 @@ public:
* a self reference so that multiple operations can be carried out
* for the same shape in the same line of code.
*/
// TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be
// unused. It is potentially useful if debugged.
// TopoShape &makeElementPrismUntil(const TopoShape &base,
// const TopoShape& profile,
// const TopoShape& supportFace,
// const TopoShape& upToFace,
// const gp_Dir& direction,
// PrismMode mode,
// Standard_Boolean checkLimits = Standard_True,
// const char *op=nullptr);
TopoShape &makeElementPrismUntil(const TopoShape &base,
const TopoShape& profile,
const TopoShape& supportFace,
const TopoShape& upToFace,
const gp_Dir& direction,
PrismMode mode,
Standard_Boolean checkLimits = Standard_True,
const char *op=nullptr);
/** Make a prism based on this shape that is either depression or protrusion of a profile shape up to a given face
*
@@ -1181,25 +1179,23 @@ public:
*
* @return Return the generated new shape. The TopoShape itself is not modified.
*/
// TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be
// unused. It is potentially useful if debugged.
// TopoShape makeElementPrismUntil(const TopoShape& profile,
// const TopoShape& supportFace,
// const TopoShape& upToFace,
// const gp_Dir& direction,
// PrismMode mode,
// Standard_Boolean checkLimits = Standard_True,
// const char *op=nullptr) const
// {
// return TopoShape(0,Hasher).makeElementPrismUntil(*this,
// profile,
// supportFace,
// upToFace,
// direction,
// mode,
// checkLimits,
// op);
// }
TopoShape makeElementPrismUntil(const TopoShape& profile,
const TopoShape& supportFace,
const TopoShape& upToFace,
const gp_Dir& direction,
PrismMode mode,
Standard_Boolean checkLimits = Standard_True,
const char *op=nullptr) const
{
return TopoShape(0,Hasher).makeElementPrismUntil(*this,
profile,
supportFace,
upToFace,
direction,
mode,
checkLimits,
op);
}
/* Make a shell or solid by sweeping profile wire along a spine

View File

@@ -4231,192 +4231,191 @@ TopoShape& TopoShape::makeElementPrism(const TopoShape& base, const gp_Vec& vec,
return makeElementShape(mkPrism, base, op);
}
// TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be
// unused. It is potentially useful if debugged.
// TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base,
// const TopoShape& profile,
// const TopoShape& supportFace,
// const TopoShape& __uptoface,
// const gp_Dir& direction,
// PrismMode Mode,
// Standard_Boolean checkLimits,
// const char* op)
//{
// if (!op) {
// op = Part::OpCodes::Prism;
// }
//
// BRepFeat_MakePrism PrismMaker;
//
// TopoShape _uptoface(__uptoface);
// if (checkLimits && _uptoface.shapeType(true) == TopAbs_FACE
// && !BRep_Tool::NaturalRestriction(TopoDS::Face(_uptoface.getShape()))) {
// // When using the face with BRepFeat_MakePrism::Perform(const TopoDS_Shape& Until)
// // then the algorithm expects that the 'NaturalRestriction' flag is set in order
// // to work as expected.
// BRep_Builder builder;
// _uptoface = _uptoface.makeElementCopy();
// builder.NaturalRestriction(TopoDS::Face(_uptoface.getShape()), Standard_True);
// }
//
// TopoShape uptoface(_uptoface);
// TopoShape base(_base);
//
// if (base.isNull()) {
// Mode = PrismMode::None;
// base = profile;
// }
//
// // Check whether the face has limits or not. Unlimited faces have no wire
// // Note: Datum planes are always unlimited
// if (checkLimits && uptoface.hasSubShape(TopAbs_WIRE)) {
// TopoDS_Face face = TopoDS::Face(uptoface.getShape());
// bool remove_limits = false;
// // Remove the limits of the upToFace so that the extrusion works even if profile is larger
// // than the upToFace
// for (auto& sketchface : profile.getSubTopoShapes(TopAbs_FACE)) {
// // Get outermost wire of sketch face
// TopoShape outerWire = sketchface.splitWires();
// BRepProj_Projection proj(TopoDS::Wire(outerWire.getShape()), face, direction);
// if (!proj.More() || !proj.Current().Closed()) {
// remove_limits = true;
// break;
// }
// }
//
// // It must also be checked that all projected inner wires of the upToFace
// // lie outside the sketch shape. If this is not the case then the sketch
// // shape is not completely covered by the upToFace. See #0003141
// if (!remove_limits) {
// std::vector<TopoShape> wires;
// uptoface.splitWires(&wires);
// for (auto& w : wires) {
// BRepProj_Projection proj(TopoDS::Wire(w.getShape()),
// profile.getShape(),
// -direction);
// if (proj.More()) {
// remove_limits = true;
// break;
// }
// }
// }
//
// if (remove_limits) {
// // Note: Using an unlimited face every time gives unnecessary failures for concave
// faces TopLoc_Location loc = face.Location(); BRepAdaptor_Surface adapt(face,
// Standard_False);
// // use the placement of the adapter, not of the upToFace
// loc = TopLoc_Location(adapt.Trsf());
// BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface(), Precision::Confusion());
// if (!mkFace.IsDone()) {
// remove_limits = false;
// }
// else {
// uptoface.setShape(located(mkFace.Shape(), loc), false);
// }
// }
// }
//
// TopoShape uptofaceCopy = uptoface;
// bool checkBase = false;
// auto retry = [&]() {
// if (!uptoface.isSame(_uptoface)) {
// // retry using the original up to face in case unnecessary failure
// // due to removing the limits
// uptoface = _uptoface;
// return true;
// }
// if ((!_base.isNull() && base.isSame(_base)) || (_base.isNull() && base.isSame(profile))) {
// // It is unclear under exactly what condition extrude up to face
// // can fail. Either the support face or the up to face must be part
// // of the base, or maybe some thing else.
// //
// // To deal with it, we retry again by disregard the supplied base,
// // and use up to face to extrude our own base. Later on, use the
// // supplied base (i.e. _base) to calculate the final shape if the
// // mode is FuseWithBase or CutWithBase.
// checkBase = true;
// uptoface = uptofaceCopy;
// base.makeElementPrism(_uptoface, direction);
// return true;
// }
// return false;
// };
//
// std::vector<TopoShape> srcShapes;
// TopoShape result;
// for (;;) {
// try {
// result = base;
//
// // We do not rely on BRepFeat_MakePrism to perform fuse or cut for
// // us because of its poor support of shape history.
// auto mode = PrismMode::None;
//
// for (auto& face : profile.getSubTopoShapes(
// profile.hasSubShape(TopAbs_FACE) ? TopAbs_FACE : TopAbs_WIRE)) {
// srcShapes.clear();
// if (!profile.isNull() && !result.findShape(profile.getShape())) {
// srcShapes.push_back(profile);
// }
// if (!supportFace.isNull() && !result.findShape(supportFace.getShape())) {
// srcShapes.push_back(supportFace);
// }
//
// // DO NOT include uptoface for element mapping. Because OCCT
// // BRepFeat_MakePrism will report all top extruded face being
// // modified by the uptoface. If there are more than one face in
// // the profile, this will cause unnecessary duplicated element
// // mapped name. And will also disrupte element history tracing
// // back to the profile sketch.
// //
// // if (!uptoface.isNull() && !this->findShape(uptoface.getShape()))
// // srcShapes.push_back(uptoface);
//
// srcShapes.push_back(result);
//
// PrismMaker.Init(result.getShape(),
// face.getShape(),
// TopoDS::Face(supportFace.getShape()),
// direction,
// mode,
// Standard_False);
// mode = PrismMode::FuseWithBase;
//
// PrismMaker.Perform(uptoface.getShape());
//
// if (!PrismMaker.IsDone() || PrismMaker.Shape().IsNull()) {
// FC_THROWM(Base::CADKernelError, "BRepFeat_MakePrism: extrusion failed");
// }
//
// result.makeElementShape(PrismMaker, srcShapes, uptoface, op);
// }
// break;
// }
// catch (Base::Exception&) {
// if (!retry()) {
// throw;
// }
// }
// catch (Standard_Failure&) {
// if (!retry()) {
// throw;
// }
// }
// }
//
// if (!_base.isNull() && Mode != PrismMode::None) {
// if (Mode == PrismMode::FuseWithBase) {
// result.makeElementFuse({_base, result});
// }
// else {
// result.makeElementCut({_base, result});
// }
// }
//
// *this = result;
// return *this;
//}
TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base,
const TopoShape& profile,
const TopoShape& supportFace,
const TopoShape& __uptoface,
const gp_Dir& direction,
PrismMode Mode,
Standard_Boolean checkLimits,
const char* op)
{
if (!op) {
op = Part::OpCodes::Prism;
}
BRepFeat_MakePrism PrismMaker;
TopoShape _uptoface(__uptoface);
if (checkLimits && _uptoface.shapeType(true) == TopAbs_FACE
&& !BRep_Tool::NaturalRestriction(TopoDS::Face(_uptoface.getShape()))) {
// When using the face with BRepFeat_MakePrism::Perform(const TopoDS_Shape& Until)
// then the algorithm expects that the 'NaturalRestriction' flag is set in order
// to work as expected.
BRep_Builder builder;
_uptoface = _uptoface.makeElementCopy();
builder.NaturalRestriction(TopoDS::Face(_uptoface.getShape()), Standard_True);
}
TopoShape uptoface(_uptoface);
TopoShape base(_base);
if (base.isNull()) {
Mode = PrismMode::None;
base = profile;
}
// Check whether the face has limits or not. Unlimited faces have no wire
// Note: Datum planes are always unlimited
if (checkLimits && uptoface.hasSubShape(TopAbs_WIRE)) {
TopoDS_Face face = TopoDS::Face(uptoface.getShape());
bool remove_limits = false;
// Remove the limits of the upToFace so that the extrusion works even if profile is larger
// than the upToFace
for (auto& sketchface : profile.getSubTopoShapes(TopAbs_FACE)) {
// Get outermost wire of sketch face
TopoShape outerWire = sketchface.splitWires();
BRepProj_Projection proj(TopoDS::Wire(outerWire.getShape()), face, direction);
if (!proj.More() || !proj.Current().Closed()) {
remove_limits = true;
break;
}
}
// It must also be checked that all projected inner wires of the upToFace
// lie outside the sketch shape. If this is not the case then the sketch
// shape is not completely covered by the upToFace. See #0003141
if (!remove_limits) {
std::vector<TopoShape> wires;
uptoface.splitWires(&wires);
for (auto& w : wires) {
BRepProj_Projection proj(TopoDS::Wire(w.getShape()),
profile.getShape(),
-direction);
if (proj.More()) {
remove_limits = true;
break;
}
}
}
if (remove_limits) {
// Note: Using an unlimited face every time gives unnecessary failures for concave
// faces
TopLoc_Location loc = face.Location(); BRepAdaptor_Surface adapt(face,
Standard_False);
// use the placement of the adapter, not of the upToFace
loc = TopLoc_Location(adapt.Trsf());
BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface(), Precision::Confusion());
if (!mkFace.IsDone()) {
remove_limits = false;
}
else {
uptoface.setShape(located(mkFace.Shape(), loc), false);
}
}
}
TopoShape uptofaceCopy = uptoface;
bool checkBase = false;
auto retry = [&]() {
if (!uptoface.isSame(_uptoface)) {
// retry using the original up to face in case unnecessary failure
// due to removing the limits
uptoface = _uptoface;
return true;
}
if ((!_base.isNull() && base.isSame(_base)) || (_base.isNull() && base.isSame(profile))) {
// It is unclear under exactly what condition extrude up to face
// can fail. Either the support face or the up to face must be part
// of the base, or maybe some thing else.
//
// To deal with it, we retry again by disregard the supplied base,
// and use up to face to extrude our own base. Later on, use the
// supplied base (i.e. _base) to calculate the final shape if the
// mode is FuseWithBase or CutWithBase.
checkBase = true;
uptoface = uptofaceCopy;
base.makeElementPrism(_uptoface, direction);
return true;
}
return false;
};
std::vector<TopoShape> srcShapes;
TopoShape result;
for (;;) {
try {
result = base;
// We do not rely on BRepFeat_MakePrism to perform fuse or cut for
// us because of its poor support of shape history.
auto mode = PrismMode::None;
for (auto& face : profile.getSubTopoShapes(
profile.hasSubShape(TopAbs_FACE) ? TopAbs_FACE : TopAbs_WIRE)) {
srcShapes.clear();
if (!profile.isNull() && !result.findShape(profile.getShape())) {
srcShapes.push_back(profile);
}
if (!supportFace.isNull() && !result.findShape(supportFace.getShape())) {
srcShapes.push_back(supportFace);
}
// DO NOT include uptoface for element mapping. Because OCCT
// BRepFeat_MakePrism will report all top extruded face being
// modified by the uptoface. If there are more than one face in
// the profile, this will cause unnecessary duplicated element
// mapped name. And will also disrupte element history tracing
// back to the profile sketch.
//
// if (!uptoface.isNull() && !this->findShape(uptoface.getShape()))
// srcShapes.push_back(uptoface);
srcShapes.push_back(result);
PrismMaker.Init(result.getShape(),
face.getShape(),
TopoDS::Face(supportFace.getShape()),
direction,
mode,
Standard_False);
mode = PrismMode::FuseWithBase;
PrismMaker.Perform(uptoface.getShape());
if (!PrismMaker.IsDone() || PrismMaker.Shape().IsNull()) {
FC_THROWM(Base::CADKernelError, "BRepFeat_MakePrism: extrusion failed");
}
result.makeElementShape(PrismMaker, srcShapes, uptoface, op);
}
break;
}
catch (Base::Exception&) {
if (!retry()) {
throw;
}
}
catch (Standard_Failure&) {
if (!retry()) {
throw;
}
}
}
if (!_base.isNull() && Mode != PrismMode::None) {
if (Mode == PrismMode::FuseWithBase) {
result.makeElementFuse({_base, result});
}
else {
result.makeElementCut({_base, result});
}
}
*this = result;
return *this;
}
TopoShape& TopoShape::makeElementRevolve(const TopoShape& _base,
const gp_Ax1& axis,

View File

@@ -100,6 +100,36 @@ TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape)
return {};
}
// TODO REMOVE THIS METHOD AND DONT TRANSFER IN?
bool Feature::allowMultiSolid() const {
auto body = getFeatureBody();
return body && !body->SingleSolid.getValue();
}
TopoShape Feature::getSolid(const TopoShape& shape, bool force)
{
if (shape.isNull())
throw Part::NullShapeException("Null shape");
int count = shape.countSubShapes(TopAbs_SOLID);
if(count>1) {
if(allowMultiSolid()) {
auto res = shape;
res.fixSolidOrientation();
return res;
}
throw Base::RuntimeError("Result has multiple solids.\n"
"To allow multiple solids, please set 'SingleSolid' property of the body to false");
}
if(count) {
auto res = shape.getSubTopoShape(TopAbs_SOLID,1);
res.fixSolidOrientation();
return res;
}
if (force)
return TopoShape();
return shape;
}
int Feature::countSolids(const TopoDS_Shape& shape, TopAbs_ShapeEnum type)
{
int result = 0;
@@ -240,6 +270,15 @@ TopoDS_Shape Feature::makeShapeFromPlane(const App::DocumentObject* obj)
return builder.Shape();
}
TopoShape Feature::makeTopoShapeFromPlane(const App::DocumentObject* obj)
{
BRepBuilderAPI_MakeFace builder(makePlnFromPlane(obj));
if (!builder.IsDone())
throw Base::CADKernelError("Feature: Could not create shape from base plane");
return TopoShape(obj->getID(), nullptr, builder.Shape());
}
Body* Feature::getFeatureBody() const {
auto body = Base::freecad_dynamic_cast<Body>(_Body.getValue());

View File

@@ -95,6 +95,7 @@ protected:
* Get a solid of the given shape. If no solid is found an exception is raised.
*/
static TopoDS_Shape getSolid(const TopoDS_Shape&);
TopoShape getSolid(const TopoShape &, bool force = true);
static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID );
/// Grab any point from the given face
@@ -102,6 +103,7 @@ protected:
/// Make a shape from a base plane (convenience method)
static gp_Pln makePlnFromPlane(const App::DocumentObject* obj);
static TopoDS_Shape makeShapeFromPlane(const App::DocumentObject* obj);
static TopoShape makeTopoShapeFromPlane(const App::DocumentObject* obj);
};
using FeaturePython = App::FeaturePythonT<Feature>;

View File

@@ -83,6 +83,16 @@ TopoDS_Shape FeatureAddSub::refineShapeIfActive(const TopoDS_Shape& oldShape) co
return oldShape;
}
TopoShape FeatureAddSub::refineShapeIfActive(const TopoShape& oldShape) const
{
if (this->Refine.getValue()) {
TopoShape shape(oldShape);
// this->fixShape(shape);
return shape.makeElementRefine();
}
return oldShape;
}
void FeatureAddSub::getAddSubShape(Part::TopoShape &addShape, Part::TopoShape &subShape)
{
if (addSubType == Additive)

View File

@@ -55,6 +55,7 @@ protected:
Type addSubType{Additive};
TopoDS_Shape refineShapeIfActive(const TopoDS_Shape&) const;
TopoShape refineShapeIfActive(const TopoShape&) const;
};
using FeatureAddSubPython = App::FeaturePythonT<FeatureAddSub>;

View File

@@ -40,6 +40,9 @@
#include <Mod/Part/App/ExtrusionHelper.h>
#include "FeatureExtrude.h"
#include "Mod/Part/App/TopoShapeOpCode.h"
FC_LOG_LEVEL_INIT("PartDesign", true, true)
using namespace PartDesign;
@@ -246,6 +249,63 @@ void FeatureExtrude::generatePrism(TopoDS_Shape& prism,
}
}
void FeatureExtrude::generatePrism(TopoShape& prism,
TopoShape sketchTopoShape,
const std::string& method,
const gp_Dir& dir,
const double L,
const double L2,
const bool midplane,
const bool reversed)
{
auto sketchShape = sketchTopoShape.getShape();
if (method == "Length" || method == "TwoLengths" || method == "ThroughAll") {
double Ltotal = L;
double Loffset = 0.;
if (method == "ThroughAll")
Ltotal = getThroughAllLength();
if (method == "TwoLengths") {
// midplane makes no sense here
Ltotal += L2;
if (reversed)
Loffset = -L;
else if (midplane)
Loffset = -0.5 * (L2 + L);
else
Loffset = -L2;
} else if (midplane)
Loffset = -Ltotal/2;
if (method == "TwoLengths" || midplane) {
gp_Trsf mov;
mov.SetTranslation(Loffset * gp_Vec(dir));
TopLoc_Location loc(mov);
sketchTopoShape.move(loc);
} else if (reversed)
Ltotal *= -1.0;
// Without taper angle we create a prism because its shells are in every case no B-splines and can therefore
// be use as support for further features like Pads, Lofts etc. B-spline shells can break certain features,
// see e.g. https://forum.freecad.org/viewtopic.php?p=560785#p560785
// It is better not to use BRepFeat_MakePrism here even if we have a support because the
// resulting shape creates problems with Pocket
try {
prism.makeElementPrism(sketchTopoShape, Ltotal*gp_Vec(dir)); // finite prism
}catch(Standard_Failure &) {
throw Base::RuntimeError("FeatureExtrusion: Length: Could not extrude the sketch!");
}
}
else {
std::stringstream str;
str << "FeatureExtrusion: Internal error: Unknown method '"
<< method << "' for generatePrism()";
throw Base::RuntimeError(str.str());
}
}
void FeatureExtrude::generateTaperedPrism(TopoDS_Shape& prism,
const TopoDS_Shape& sketchshape,
const std::string& method,
@@ -349,3 +409,270 @@ void FeatureExtrude::updateProperties(const std::string &method)
Reversed.setReadOnly(!isReversedEnabled);
UpToFace.setReadOnly(!isUpToFaceEnabled);
}
void FeatureExtrude::setupObject()
{
ProfileBased::setupObject();
}
App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions options)
{
bool makeface = options.testFlag(ExtrudeOption::MakeFace);
bool fuse = options.testFlag(ExtrudeOption::MakeFuse);
bool legacyPocket = options.testFlag(ExtrudeOption::LegacyPocket);
bool inverseDirection = options.testFlag(ExtrudeOption::InverseDirection);
std::string method(Type.getValueAsString());
// Validate parameters
double L = Length.getValue();
if ((method == "Length") && (L < Precision::Confusion()))
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Length too small"));
double L2 = 0;
if ((method == "TwoLengths")) {
L2 = Length2.getValue();
if (std::abs(L2) < Precision::Confusion())
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Second length too small"));
}
Part::Feature* obj = nullptr;
TopoShape sketchshape;
try {
obj = getVerifiedObject();
if (makeface) {
sketchshape = getVerifiedFace();
} else {
std::vector<TopoShape> shapes;
bool hasEdges = false;
auto subs = Profile.getSubValues(false);
if (subs.empty())
subs.emplace_back("");
bool failed = false;
for (auto & sub : subs) {
if (sub.empty() && subs.size()>1)
continue;
TopoShape shape = Part::Feature::getTopoShape(obj, sub.c_str(), true);
if (shape.isNull()) {
FC_ERR(getFullName() << ": failed to get profile shape "
<< obj->getFullName() << "." << sub);
failed = true;
}
hasEdges = hasEdges || shape.hasSubShape(TopAbs_EDGE);
shapes.push_back(shape);
}
if (failed)
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Failed to obtain profile shape"));
if (hasEdges)
sketchshape.makeElementWires(shapes);
else
sketchshape.makeElementCompound(shapes, nullptr, TopoShape::SingleShapeCompoundCreationPolicy::returnShape);
}
} catch (const Base::Exception& e) {
return new App::DocumentObjectExecReturn(e.what());
} catch (const Standard_Failure& e) {
return new App::DocumentObjectExecReturn(e.GetMessageString());
}
// if the Base property has a valid shape, fuse the prism into it
TopoShape base = getBaseTopoShape(true);
// get the normal vector of the sketch
Base::Vector3d SketchVector = getProfileNormal();
try {
this->positionByPrevious();
auto invObjLoc = getLocation().Inverted();
auto invTrsf = invObjLoc.Transformation();
base.move(invObjLoc);
Base::Vector3d paddingDirection = computeDirection(SketchVector);
// create vector in padding direction with length 1
gp_Dir dir(paddingDirection.x, paddingDirection.y, paddingDirection.z);
// The length of a gp_Dir is 1 so the resulting pad would have
// the length L in the direction of dir. But we want to have its height in the
// direction of the normal vector.
// Therefore we must multiply L by the factor that is necessary
// to make dir as long that its projection to the SketchVector
// equals the SketchVector.
// This is the scalar product of both vectors.
// Since the pad length cannot be negative, the factor must not be negative.
double factor = fabs(dir * gp_Dir(SketchVector.x, SketchVector.y, SketchVector.z));
// factor would be zero if vectors are orthogonal
if (factor < Precision::Confusion())
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception",
"Creation failed because direction is orthogonal to sketch's normal vector"));
// perform the length correction if not along custom vector
if (AlongSketchNormal.getValue()) {
L = L / factor;
L2 = L2 / factor;
}
// explicitly set the Direction so that the dialog shows also the used direction
// if the sketch's normal vector was used
Direction.setValue(paddingDirection);
dir.Transform(invTrsf);
if (sketchshape.isNull())
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception",
"Creating a face from sketch failed"));
sketchshape.move(invObjLoc);
TopoShape prism(0,getDocument()->getStringHasher());
if (method == "UpToFirst" || method == "UpToLast" || method == "UpToFace") {
// Note: This will return an unlimited planar face if support is a datum plane
TopoShape supportface = getSupportFace();
supportface.move(invObjLoc);
if (Reversed.getValue())
dir.Reverse();
// Find a valid face or datum plane to extrude up to
TopoShape upToFace;
if (method == "UpToFace") {
getUpToFaceFromLinkSub(upToFace, UpToFace);
upToFace.move(invObjLoc);
}
getUpToFace(upToFace, base, supportface, sketchshape, method, dir);
addOffsetToFace(upToFace, dir, Offset.getValue());
if (!supportface.hasSubShape(TopAbs_WIRE))
supportface = TopoShape();
if (legacyPocket) {
auto mode = base.isNull() ? TopoShape::PrismMode::None
: TopoShape::PrismMode::CutFromBase;
prism = base.makeElementPrismUntil(sketchshape, supportface, upToFace,
dir, mode, false/*CheckUpToFaceLimits.getValue()*/);
// DO NOT assign id to the generated prism, because this prism is
// actually the final result. We obtain the subtracted shape by cut
// this prism with the original base. Assigning a minus self id here
// will mess up with preselection highlight. It is enough to re-tag
// the profile shape above.
//
// prism.Tag = -this->getID();
// And the really expensive way to get the SubShape...
try {
TopoShape result(0,getDocument()->getStringHasher());
if (base.isNull())
result = prism;
else
result.makeElementCut({base,prism});
result = refineShapeIfActive(result);
this->AddSubShape.setValue(result);
}catch(Standard_Failure &) {
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Up to face: Could not get SubShape!"));
}
if (getAddSubType() == Additive)
prism = base.makeElementFuse(this->AddSubShape.getShape());
else
prism = refineShapeIfActive(prism);
this->Shape.setValue(getSolid(prism));
return App::DocumentObject::StdReturn;
}
prism.makeElementPrismUntil(base, sketchshape, supportface, upToFace,
dir, TopoShape::PrismMode::None, false /*CheckUpToFaceLimits.getValue()*/);
} else {
Part::ExtrusionParameters params;
params.dir = dir;
params.solid = makeface;
params.taperAngleFwd = this->TaperAngle.getValue() * M_PI / 180.0;
params.taperAngleRev = this->TaperAngle2.getValue() * M_PI / 180.0;
if (L2 == 0.0 && Midplane.getValue()) {
params.lengthFwd = L/2;
params.lengthRev = L/2;
if (params.taperAngleRev == 0.0)
params.taperAngleRev = params.taperAngleFwd;
} else {
params.lengthFwd = L;
params.lengthRev = L2;
}
if (std::fabs(params.taperAngleFwd) >= Precision::Angular()
|| std::fabs(params.taperAngleRev) >= Precision::Angular() ) {
if (fabs(params.taperAngleFwd) > M_PI * 0.5 - Precision::Angular()
|| fabs(params.taperAngleRev) > M_PI * 0.5 - Precision::Angular())
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception",
"Magnitude of taper angle matches or exceeds 90 degrees"));
if (Reversed.getValue())
params.dir.Reverse();
std::vector<TopoShape> drafts;
Part::ExtrusionHelper::makeElementDraft(params, sketchshape, drafts);
if (drafts.empty())
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Padding with draft angle failed"));
prism.makeElementCompound(drafts, nullptr, TopoShape::SingleShapeCompoundCreationPolicy::returnShape);
} else
generatePrism(prism, sketchshape, method, dir, L, L2,
Midplane.getValue(), Reversed.getValue());
}
// set the additive shape property for later usage in e.g. pattern
prism = refineShapeIfActive(prism);
this->AddSubShape.setValue(prism);
if (!base.isNull() && fuse) {
prism.Tag = -this->getID();
// Let's call algorithm computing a fuse operation:
TopoShape result(0,getDocument()->getStringHasher());
try {
const char *maker;
switch (getAddSubType()) {
case Subtractive:
maker = Part::OpCodes::Cut;
break;
default:
maker = Part::OpCodes::Fuse;
}
result.makeElementBoolean(maker, {base,prism});
}catch(Standard_Failure &){
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception",
"Fusion with base feature failed"));
}
// we have to get the solids (fuse sometimes creates compounds)
auto solRes = this->getSolid(result);
// lets check if the result is a solid
if (solRes.isNull())
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception",
"Resulting shape is not a solid"));
solRes = refineShapeIfActive(solRes);
this->Shape.setValue(getSolid(solRes));
} else if (prism.hasSubShape(TopAbs_SOLID)) {
if (prism.countSubShapes(TopAbs_SOLID) > 1)
prism.makeElementFuse(prism.getSubTopoShapes(TopAbs_SOLID));
prism = refineShapeIfActive(prism);
this->Shape.setValue(getSolid(prism));
} else {
prism = refineShapeIfActive(prism);
this->Shape.setValue(prism);
}
// eventually disable some settings that are not valid for the current method
updateProperties(method);
return App::DocumentObject::StdReturn;
}
catch (Standard_Failure& e) {
if (std::string(e.GetMessageString()) == "TopoDS::Face")
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception",
"Could not create face from sketch.\n"
"Intersecting sketch entities or multiple faces in a sketch are not allowed."));
else
return new App::DocumentObjectExecReturn(e.GetMessageString());
}
catch (Base::Exception& e) {
return new App::DocumentObjectExecReturn(e.what());
}
}

View File

@@ -60,12 +60,25 @@ public:
/** @name methods override feature */
//@{
short mustExecute() const override;
void setupObject() override;
//@}
protected:
Base::Vector3d computeDirection(const Base::Vector3d& sketchVector);
bool hasTaperedAngle() const;
/// Options for buildExtrusion()
enum class ExtrudeOption {
MakeFace = 1,
MakeFuse = 2,
LegacyPocket = 4,
InverseDirection = 8,
};
using ExtrudeOptions = Base::Flags<ExtrudeOption>;
App::DocumentObjectExecReturn *buildExtrusion(ExtrudeOptions options);
/**
* Generates an extrusion of the input sketchshape and stores it in the given \a prism
*/
@@ -78,6 +91,15 @@ protected:
const bool midplane,
const bool reversed);
void generatePrism(TopoShape& prism,
TopoShape sketchshape,
const std::string& method,
const gp_Dir& direction,
const double L,
const double L2,
const bool midplane,
const bool reversed);
// See BRepFeat_MakePrism
enum PrismMode {
CutFromBase = 0,
@@ -120,5 +142,6 @@ protected:
} //namespace PartDesign
ENABLE_BITMASK_OPERATORS(PartDesign::FeatureExtrude::ExtrudeOption)
#endif // PARTDESIGN_FEATURE_EXTRUDE_H

View File

@@ -62,6 +62,8 @@
#include "DatumPlane.h"
FC_LOG_LEVEL_INIT("PartDesign",true,true);
using namespace PartDesign;
PROPERTY_SOURCE(PartDesign::ProfileBased, PartDesign::FeatureAddSub)
@@ -253,6 +255,134 @@ TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const {
return TopoDS_Face();
}
TopoShape ProfileBased::getTopoShapeVerifiedFace(bool silent,
bool doFit,
bool allowOpen,
const App::DocumentObject *profile,
const std::vector<std::string> &_subs) const
{
auto obj = profile ? profile : Profile.getValue();
if(!obj || !obj->getNameInDocument()) {
if(silent)
return TopoShape();
throw Base::ValueError("No profile linked");
}
const auto &subs = profile ? _subs : Profile.getSubValues();
try {
TopoShape shape;
if(AllowMultiFace.getValue()) {
if (subs.empty())
shape = Part::Feature::getTopoShape(obj);
else {
std::vector<TopoShape> shapes;
for (auto &sub : subs) {
auto subshape = Part::Feature::getTopoShape(
obj, sub.c_str(), /*needSubElement*/true);
if (subshape.isNull())
FC_THROWM(Base::CADKernelError, "Sub shape not found: " <<
obj->getFullName() << "." << sub);
shapes.push_back(subshape);
}
shape.makeElementCompound(shapes);
}
} else {
std::string sub;
if(!obj->getTypeId().isDerivedFrom(Part::Part2DObject::getClassTypeId())) {
if(!subs.empty())
sub = subs[0];
}
shape = Part::Feature::getTopoShape(obj,sub.c_str(),!sub.empty());
}
if(shape.isNull()) {
if (silent)
return shape;
throw Base::CADKernelError("Linked shape object is empty");
}
TopoShape openshape;
if(!shape.hasSubShape(TopAbs_FACE)) {
try {
if(!shape.hasSubShape(TopAbs_WIRE))
shape = shape.makeElementWires();
if(shape.hasSubShape(TopAbs_WIRE)) {
shape.Hasher = getDocument()->getStringHasher();
if (allowOpen) {
std::vector<TopoShape> openwires;
std::vector<TopoShape> wires;
for (auto &wire : shape.getSubTopoShapes(TopAbs_WIRE)) {
if (!wire.isClosed())
openwires.push_back(wire);
else
wires.push_back(wire);
}
if (openwires.size()) {
openshape.makeElementCompound(openwires, nullptr, TopoShape ::SingleShapeCompoundCreationPolicy::returnShape);
if (wires.empty())
shape = TopoShape();
else
shape.makeElementCompound(wires, nullptr, TopoShape ::SingleShapeCompoundCreationPolicy::returnShape);
}
}
if (!shape.isNull()) {
if (AllowMultiFace.getValue())
shape = shape.makeElementFace(); // default to use FaceMakerBullseye
else
shape = shape.makeElementFace(nullptr, "Part::FaceMakerCheese");
}
}
} catch (const Base::Exception &) {
if (silent)
return TopoShape();
throw;
} catch (const Standard_Failure &) {
if (silent)
return TopoShape();
throw;
}
}
int count = shape.countSubShapes(TopAbs_FACE);
if(!count && !allowOpen) {
if(silent)
return TopoShape();
throw Base::CADKernelError("Cannot make face from profile");
}
// if (doFit && (std::abs(Fit.getValue()) > Precision::Confusion()
// || std::abs(InnerFit.getValue()) > Precision::Confusion())) {
//
// if (!shape.isNull())
// shape = shape.makEOffsetFace(Fit.getValue(),
// InnerFit.getValue(),
// static_cast<Part::TopoShape::JoinType>(FitJoin.getValue()),
// static_cast<Part::TopoShape::JoinType>(InnerFitJoin.getValue()));
// if (!openshape.isNull())
// openshape.makEOffset2D(Fit.getValue());
// }
if (!openshape.isNull()) {
if (shape.isNull())
shape = openshape;
else
shape.makeElementCompound({shape, openshape});
}
if(count>1) {
if(AllowMultiFace.getValue()
// || allowMultiSolid()
|| obj->isDerivedFrom(Part::Part2DObject::getClassTypeId()))
return shape;
FC_WARN("Found more than one face from profile");
}
if (!openshape.isNull())
return shape;
if (count)
return shape.getSubTopoShape(TopAbs_FACE,1);
return shape;
}catch (Standard_Failure &) {
if(silent)
return TopoShape();
throw;
}
}
std::vector<TopoDS_Wire> ProfileBased::getProfileWires() const {
std::vector<TopoDS_Wire> result;
@@ -292,6 +422,22 @@ std::vector<TopoDS_Wire> ProfileBased::getProfileWires() const {
return result;
}
std::vector<TopoShape> ProfileBased::getTopoShapeProfileWires() const {
// shape copy is a workaround for an obscure OCC bug which leads to empty
// tessellations for some faces. Making an explicit copy of the linked
// shape seems to fix it. The error mostly happens when re-computing the
// shape but sometimes also for the first time
auto shape = getProfileShape().makeElementCopy();
if(shape.hasSubShape(TopAbs_WIRE))
return shape.getSubTopoShapes(TopAbs_WIRE);
auto wires = shape.makeElementWires().getSubTopoShapes(TopAbs_WIRE);
if(wires.empty())
throw Part::NullShapeException("Linked shape object is not a wire");
return wires;
}
// Note: We cannot return a reference, because it will become Null.
// Not clear where, because we check for IsNull() here, but as soon as it is passed out of
// this method, it becomes null!
@@ -356,6 +502,34 @@ TopoDS_Face ProfileBased::getSupportFace(const App::PropertyLinkSub& link) const
return face;
}
TopoShape ProfileBased::getTopoShapeSupportFace() const {
TopoShape shape;
const Part::Part2DObject* sketch = getVerifiedSketch(true);
if (!sketch)
shape = getVerifiedFace();
else if (sketch->MapMode.getValue() == Attacher::mmFlatFace && sketch->AttachmentSupport.getValue()) {
const auto &Support = sketch->AttachmentSupport;
App::DocumentObject* ref = Support.getValue();
shape = Part::Feature::getTopoShape(
ref, Support.getSubValues().size() ? Support.getSubValues()[0].c_str() : "", true);
}
if (!shape.isNull()) {
if (shape.shapeType(true) != TopAbs_FACE) {
if (!shape.hasSubShape(TopAbs_FACE))
throw Base::ValueError("Null face in SketchBased::getSupportFace()!");
shape = shape.getSubTopoShape(TopAbs_FACE, 1);
}
gp_Pln pln;
if (!shape.findPlane(pln))
throw Base::TypeError("No planar face in SketchBased::getSupportFace()!");
return shape;
}
if (!sketch)
throw Base::RuntimeError("No planar support");
return Feature::makeShapeFromPlane(sketch);
}
int ProfileBased::getSketchAxisCount() const
{
Part::Part2DObject* sketch = static_cast<Part::Part2DObject*>(Profile.getValue());
@@ -540,6 +714,54 @@ void ProfileBased::getUpToFace(TopoDS_Face& upToFace,
}
}
void ProfileBased::getUpToFace(TopoShape& upToFace,
const TopoShape& support,
const TopoShape& supportface,
const TopoShape& sketchshape,
const std::string& method,
gp_Dir& dir)
{
if ((method == "UpToLast") || (method == "UpToFirst")) {
std::vector<Part::cutFaces> cfaces = Part::findAllFacesCutBy(support, sketchshape, dir);
if (cfaces.empty())
throw Base::ValueError("SketchBased: No faces found in this direction");
// Find nearest/furthest face
std::vector<Part::cutFaces>::const_iterator it, it_near, it_far;
it_near = it_far = cfaces.begin();
for (it = cfaces.begin(); it != cfaces.end(); it++)
if (it->distsq > it_far->distsq)
it_far = it;
else if (it->distsq < it_near->distsq)
it_near = it;
upToFace = (method == "UpToLast" ? it_far->face : it_near->face);
} else if (Part::findAllFacesCutBy(upToFace, sketchshape, dir).empty())
dir = -dir;
if (upToFace.shapeType(true) != TopAbs_FACE) {
if (!upToFace.hasSubShape(TopAbs_FACE))
throw Base::ValueError("SketchBased: Up to face: No face found");
upToFace = upToFace.getSubTopoShape(TopAbs_FACE, 1);
}
TopoDS_Face face = TopoDS::Face(upToFace.getShape());
// Check that the upToFace does not intersect the sketch face and
// is not parallel to the extrusion direction (for simplicity, supportface is used instead of sketchshape)
BRepAdaptor_Surface adapt1(TopoDS::Face(supportface.getShape()));
BRepAdaptor_Surface adapt2(face);
if (adapt2.GetType() == GeomAbs_Plane) {
if (adapt1.Plane().Axis().IsNormal(adapt2.Plane().Axis(), Precision::Confusion()))
throw Base::ValueError("SketchBased: Up to face: Must not be parallel to extrusion direction!");
}
// We must measure from sketchshape, not supportface, here
BRepExtrema_DistShapeShape distSS(sketchshape.getShape(), face);
if (distSS.Value() < Precision::Confusion())
throw Base::ValueError("SketchBased: Up to face: Must not intersect sketch!");
}
void ProfileBased::addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, double offset)
{
// Move the face in the extrusion direction
@@ -564,6 +786,18 @@ void ProfileBased::addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, dou
}
}
void ProfileBased::addOffsetToFace(TopoShape& upToFace, const gp_Dir& dir, double offset)
{
// Move the face in the extrusion direction
// TODO: For non-planar faces, we could consider offsetting the surface
if (fabs(offset) > Precision::Confusion()) {
gp_Trsf mov;
mov.SetTranslation(offset * gp_Vec(dir));
TopLoc_Location loc(mov);
upToFace.move(loc);
}
}
double ProfileBased::getThroughAllLength() const
{
TopoDS_Shape profileshape;
@@ -739,6 +973,13 @@ bool ProfileBased::checkLineCrossesFace(const gp_Lin& line, const TopoDS_Face& f
void ProfileBased::remapSupportShape(const TopoDS_Shape & newShape)
{
#if FC_USE_TNP_FIX
(void)newShape;
// Realthunder: with the new topological naming, I don't think this function
// is necessary. A missing element will cause an explicitly error, and the
// user will be force to manually select the element. Various editors, such
// as dress up editors, can perform element guessing when activated.
#else
TopTools_IndexedMapOfShape faceMap;
TopExp::MapShapes(newShape, TopAbs_FACE, faceMap);
@@ -830,6 +1071,7 @@ void ProfileBased::remapSupportShape(const TopoDS_Shape & newShape)
link->setValue(this, newSubValues);
}
}
#endif
}
namespace PartDesign {

View File

@@ -99,15 +99,34 @@ public:
*/
TopoDS_Shape getVerifiedFace(bool silent = false) const;
/**
* Verifies the linked Object and returns the shape used as profile
* @param silent: if profile property is malformed and the parameter is true
* silently returns nullptr, otherwise throw a Base::Exception.
* Default is false.
* @param doFit: Whether to fitting according to the 'Fit' property
* @param allowOpen: Whether allow open wire
* @param profile: optional profile object, if not given then use 'Profile' property
* @param subs: optional profile sub-object names, if not given then use 'Profile' property
*/
TopoShape getTopoShapeVerifiedFace(bool silent = false,
bool doFit = true,
bool allowOpen = false,
const App::DocumentObject *profile = nullptr,
const std::vector<std::string> &subs = {}) const;
/// Returns the wires the sketch is composed of
std::vector<TopoDS_Wire> getProfileWires() const;
std::vector<TopoShape> getTopoShapeProfileWires() const;
/// Returns the face of the sketch support (if any)
const TopoDS_Face getSupportFace() const;
TopoShape getTopoShapeSupportFace() const;
Base::Vector3d getProfileNormal() const;
Part::TopoShape getProfileShape() const;
TopoShape getProfileShape() const;
/// retrieves the number of axes in the linked sketch (defined as construction lines)
int getSketchAxisCount() const;
@@ -142,6 +161,22 @@ protected:
static void addOffsetToFace(TopoDS_Face& upToFace,
const gp_Dir& dir,
double offset);
/// Extract a face from a given LinkSub
static void getUpToFaceFromLinkSub(TopoShape& upToFace,
const App::PropertyLinkSub& refFace);
/// Find a valid face to extrude up to
static void getUpToFace(TopoShape& upToFace,
const TopoShape& support,
const TopoShape& supportface,
const TopoShape& sketchshape,
const std::string& method,
gp_Dir& dir);
/// Add an offset to the face
static void addOffsetToFace(TopoShape& upToFace,
const gp_Dir& dir,
double offset);
/// Check whether the wire after projection on the face is inside the face
static bool checkWireInsideFace(const TopoDS_Wire& wire,