Merge branch 'main' into bgbsww-toponamingAddFeatureRefine
This commit is contained in:
@@ -40,6 +40,7 @@
|
||||
#include <Base/Tools.h>
|
||||
#include <Mod/Part/App/ExtrusionHelper.h>
|
||||
#include "Mod/Part/App/TopoShapeOpCode.h"
|
||||
#include <Mod/Part/App/PartFeature.h>
|
||||
|
||||
#include "FeatureExtrude.h"
|
||||
|
||||
@@ -131,6 +132,41 @@ bool FeatureExtrude::hasTaperedAngle() const
|
||||
fabs(TaperAngle2.getValue()) > Base::toRadians(Precision::Angular());
|
||||
}
|
||||
|
||||
TopoShape FeatureExtrude::makeShellFromUpToShape(TopoShape shape, TopoShape sketchshape, gp_Dir dir){
|
||||
|
||||
// Find nearest/furthest face
|
||||
std::vector<Part::cutTopoShapeFaces> cfaces =
|
||||
Part::findAllFacesCutBy(shape, sketchshape, dir);
|
||||
if (cfaces.empty()) {
|
||||
dir = -dir;
|
||||
cfaces = Part::findAllFacesCutBy(shape, sketchshape, dir);
|
||||
}
|
||||
struct Part::cutTopoShapeFaces *nearFace;
|
||||
struct Part::cutTopoShapeFaces *farFace;
|
||||
nearFace = farFace = &cfaces.front();
|
||||
for (auto &face : cfaces) {
|
||||
if (face.distsq > farFace->distsq) {
|
||||
farFace = &face;
|
||||
}
|
||||
else if (face.distsq < nearFace->distsq) {
|
||||
nearFace = &face;
|
||||
}
|
||||
}
|
||||
|
||||
if (nearFace != farFace) {
|
||||
std::vector<TopoShape> faceList;
|
||||
for (auto &face : shape.getSubTopoShapes(TopAbs_FACE)) {
|
||||
if (! (face == farFace->face)){
|
||||
// don't use the last face so the shell is open
|
||||
// and OCC works better
|
||||
faceList.push_back(face);
|
||||
}
|
||||
}
|
||||
return shape.makeElementCompound(faceList);
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
void FeatureExtrude::generatePrism(TopoDS_Shape& prism,
|
||||
const TopoDS_Shape& sketchshape,
|
||||
@@ -579,23 +615,25 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt
|
||||
faceCount = 1;
|
||||
}
|
||||
else if (method == "UpToShape") {
|
||||
try {
|
||||
faceCount = getUpToShapeFromLinkSubList(upToShape, UpToShape);
|
||||
upToShape.move(invObjLoc);
|
||||
}
|
||||
catch (Base::ValueError&){
|
||||
//no shape selected use the base
|
||||
faceCount = getUpToShapeFromLinkSubList(upToShape, UpToShape);
|
||||
upToShape.move(invObjLoc);
|
||||
if (faceCount == 0){
|
||||
// No shape selected, use the base
|
||||
upToShape = base;
|
||||
faceCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (faceCount == 1) {
|
||||
getUpToFace(upToShape, base, supportface, sketchshape, method, dir);
|
||||
getUpToFace(upToShape, base, sketchshape, method, dir);
|
||||
addOffsetToFace(upToShape, dir, Offset.getValue());
|
||||
}
|
||||
else if (fabs(Offset.getValue()) > Precision::Confusion()){
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Extrude: Can only offset one face"));
|
||||
else{
|
||||
if (fabs(Offset.getValue()) > Precision::Confusion()){
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Extrude: Can only offset one face"));
|
||||
}
|
||||
// open the shell by removing the furthest face
|
||||
upToShape = makeShellFromUpToShape(upToShape, sketchshape, dir);
|
||||
}
|
||||
|
||||
if (!supportface.hasSubShape(TopAbs_WIRE)) {
|
||||
@@ -645,13 +683,22 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt
|
||||
this->Shape.setValue(getSolid(prism));
|
||||
return App::DocumentObject::StdReturn;
|
||||
}
|
||||
prism.makeElementPrismUntil(base,
|
||||
sketchshape,
|
||||
supportface,
|
||||
upToShape,
|
||||
dir,
|
||||
TopoShape::PrismMode::None,
|
||||
true /*CheckUpToFaceLimits.getValue()*/);
|
||||
try {
|
||||
prism.makeElementPrismUntil(base,
|
||||
sketchshape,
|
||||
supportface,
|
||||
upToShape,
|
||||
dir,
|
||||
TopoShape::PrismMode::None,
|
||||
true /*CheckUpToFaceLimits.getValue()*/);
|
||||
}
|
||||
catch (Base::Exception& e) {
|
||||
if (method == "UpToShape" && faceCount > 1){
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP(
|
||||
"Exception",
|
||||
"Unable to reach the selected shape, please select faces"));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Part::ExtrusionParameters params;
|
||||
|
||||
@@ -71,6 +71,7 @@ protected:
|
||||
Base::Vector3d computeDirection(const Base::Vector3d& sketchVector, bool inverse);
|
||||
bool hasTaperedAngle() const;
|
||||
|
||||
|
||||
/// Options for buildExtrusion()
|
||||
enum class ExtrudeOption
|
||||
{
|
||||
@@ -84,6 +85,13 @@ protected:
|
||||
|
||||
App::DocumentObjectExecReturn* buildExtrusion(ExtrudeOptions options);
|
||||
|
||||
/**
|
||||
* generate an open shell from a given shape
|
||||
* by removing the farthest face from the sketchshape in the direction
|
||||
* if farthest is nearest (circular) then return the initial shape
|
||||
*/
|
||||
TopoShape makeShellFromUpToShape(TopoShape shape, TopoShape sketchshape, gp_Dir dir);
|
||||
|
||||
/**
|
||||
* Generates an extrusion of the input sketchshape and stores it in the given \a prism
|
||||
*/
|
||||
|
||||
@@ -51,10 +51,14 @@
|
||||
#include <Base/Stream.h>
|
||||
#include <Base/Tools.h>
|
||||
#include <Mod/Part/App/FaceMakerCheese.h>
|
||||
#include <Mod/Part/App/TopoShapeMapper.h>
|
||||
#include <Mod/Part/App/TopoShapeOpCode.h>
|
||||
|
||||
#include "FeatureHole.h"
|
||||
#include "json.hpp"
|
||||
|
||||
FC_LOG_LEVEL_INIT("PartDesign", true, true);
|
||||
|
||||
namespace PartDesign {
|
||||
|
||||
/* TRANSLATOR PartDesign::Hole */
|
||||
@@ -1882,30 +1886,76 @@ App::DocumentObjectExecReturn* Hole::execute()
|
||||
// we reuse the name protoHole (only now it is threaded)
|
||||
protoHole = mkFuse.Shape();
|
||||
}
|
||||
std::vector<TopoShape> holes;
|
||||
auto compound = findHoles(holes, profileshape, protoHole);
|
||||
|
||||
TopoDS_Compound holes = findHoles(profileshape.getShape(), protoHole);
|
||||
this->AddSubShape.setValue(holes);
|
||||
TopoShape result(0);
|
||||
|
||||
// For some reason it is faster to do the cut through a BooleanOperation.
|
||||
BRepAlgoAPI_Cut mkBool(base.getShape(), holes);
|
||||
if (!mkBool.IsDone()) {
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Boolean operation failed"));
|
||||
// set the subtractive shape property for later usage in e.g. pattern
|
||||
this->AddSubShape.setValue(compound);
|
||||
|
||||
if (base.isNull()) {
|
||||
Shape.setValue(compound);
|
||||
return App::DocumentObject::StdReturn;
|
||||
}
|
||||
TopoDS_Shape result = mkBool.Shape();
|
||||
|
||||
// First try cutting with compound which will be faster as it is done in
|
||||
// parallel
|
||||
bool retry = true;
|
||||
const char *maker;
|
||||
switch (getAddSubType()) {
|
||||
case Additive:
|
||||
maker = Part::OpCodes::Fuse;
|
||||
break;
|
||||
default:
|
||||
maker = Part::OpCodes::Cut;
|
||||
}
|
||||
try {
|
||||
if (base.isNull())
|
||||
result = compound;
|
||||
else
|
||||
result.makeElementBoolean(maker, {base,compound});
|
||||
result = getSolid(result);
|
||||
retry = false;
|
||||
} catch (Standard_Failure & e) {
|
||||
FC_WARN(getFullName() << ": boolean operation with compound failed ("
|
||||
<< e.GetMessageString() << "), retry...");
|
||||
} catch (Base::Exception & e) {
|
||||
FC_WARN(getFullName() << ": boolean operation with compound failed ("
|
||||
<< e.what() << "), retry...");
|
||||
}
|
||||
|
||||
// We have to get the solids (fuse sometimes creates compounds)
|
||||
base = getSolid(result);
|
||||
if (base.isNull())
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid"));
|
||||
base = refineShapeIfActive(base);
|
||||
if (retry) {
|
||||
int i = 0;
|
||||
for (auto & hole : holes) {
|
||||
++i;
|
||||
try {
|
||||
result.makeElementBoolean(maker, {base,hole});
|
||||
} catch (Standard_Failure &) {
|
||||
std::string msg(QT_TRANSLATE_NOOP("Exception", "Boolean operation failed on profile Edge"));
|
||||
msg += std::to_string(i);
|
||||
return new App::DocumentObjectExecReturn(msg.c_str());
|
||||
} catch (Base::Exception &e) {
|
||||
e.ReportException();
|
||||
std::string msg(QT_TRANSLATE_NOOP("Exception", "Boolean operation failed on profile Edge"));
|
||||
msg += std::to_string(i);
|
||||
return new App::DocumentObjectExecReturn(msg.c_str());
|
||||
}
|
||||
base = getSolid(result);
|
||||
if (base.isNull()) {
|
||||
std::string msg(QT_TRANSLATE_NOOP("Exception", "Boolean operation produced non-solid on profile Edge"));
|
||||
msg += std::to_string(i);
|
||||
return new App::DocumentObjectExecReturn(msg.c_str());
|
||||
}
|
||||
}
|
||||
result = base;
|
||||
}
|
||||
|
||||
if (!isSingleSolidRuleSatisfied(base.getShape())) {
|
||||
if (!isSingleSolidRuleSatisfied(result.getShape())) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported."));
|
||||
QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported."));
|
||||
}
|
||||
|
||||
this->Shape.setValue(base);
|
||||
this->Shape.setValue(result);
|
||||
|
||||
return App::DocumentObject::StdReturn;
|
||||
}
|
||||
@@ -1979,54 +2029,43 @@ gp_Vec Hole::computePerpendicular(const gp_Vec& zDir) const
|
||||
xDir.Normalize();
|
||||
return xDir;
|
||||
}
|
||||
|
||||
TopoDS_Compound Hole::findHoles(const TopoDS_Shape& profileshape,
|
||||
const TopoDS_Shape& protohole) const
|
||||
TopoShape Hole::findHoles(std::vector<TopoShape> &holes,
|
||||
const TopoShape& profileshape,
|
||||
const TopoDS_Shape& protoHole) const
|
||||
{
|
||||
BRep_Builder builder;
|
||||
TopoDS_Compound holes;
|
||||
builder.MakeCompound(holes);
|
||||
TopTools_IndexedMapOfShape edgeMap;
|
||||
TopExp::MapShapes(profileshape, TopAbs_EDGE, edgeMap);
|
||||
std::vector<gp_Pnt> holePointsList;
|
||||
for (int i = 1; i <= edgeMap.Extent(); i++) {
|
||||
bool dupCenter = false;
|
||||
TopoShape result(0);
|
||||
|
||||
int i = 0;
|
||||
for(const auto &profileEdge : profileshape.getSubTopoShapes(TopAbs_EDGE)) {
|
||||
++i;
|
||||
Standard_Real c_start;
|
||||
Standard_Real c_end;
|
||||
TopoDS_Edge edge = TopoDS::Edge(edgeMap(i));
|
||||
TopoDS_Edge edge = TopoDS::Edge(profileEdge.getShape());
|
||||
Handle(Geom_Curve) c = BRep_Tool::Curve(edge, c_start, c_end);
|
||||
|
||||
// Circle?
|
||||
if (c.IsNull() || c->DynamicType() != STANDARD_TYPE(Geom_Circle)) {
|
||||
if (c->DynamicType() != STANDARD_TYPE(Geom_Circle))
|
||||
continue;
|
||||
}
|
||||
|
||||
Handle(Geom_Circle) circle = Handle(Geom_Circle)::DownCast(c);
|
||||
gp_Pnt loc = circle->Axis().Location();
|
||||
|
||||
for (auto holePoint : holePointsList) {
|
||||
if (holePoint.IsEqual(loc, Precision::Confusion())) {
|
||||
Base::Console().Log(
|
||||
"PartDesign_Hole - There is a duplicate circle/curve center at %.2f : %.2f "
|
||||
": %.2f therefore not passing parameter\n",
|
||||
loc.X(),
|
||||
loc.Y(),
|
||||
loc.Z());
|
||||
dupCenter = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dupCenter) {
|
||||
holePointsList.push_back(loc);
|
||||
gp_Trsf localSketchTransformation;
|
||||
localSketchTransformation.SetTranslation(gp_Pnt(0, 0, 0), loc);
|
||||
TopoDS_Shape copy = protohole;
|
||||
copy.Move(localSketchTransformation);
|
||||
builder.Add(holes, copy);
|
||||
}
|
||||
gp_Trsf localSketchTransformation;
|
||||
localSketchTransformation.SetTranslation( gp_Pnt( 0, 0, 0 ),
|
||||
gp_Pnt(loc.X(), loc.Y(), loc.Z()) );
|
||||
|
||||
Part::ShapeMapper mapper;
|
||||
mapper.populate(Part::MappingStatus::Modified, profileEdge, TopoShape(protoHole).getSubTopoShapes(TopAbs_FACE));
|
||||
|
||||
TopoShape hole(-getID());
|
||||
hole.makeShapeWithElementMap(protoHole, mapper, {profileEdge});
|
||||
|
||||
// transform and generate element map.
|
||||
hole = hole.makeElementTransform(localSketchTransformation);
|
||||
holes.push_back(hole);
|
||||
}
|
||||
|
||||
return holes;
|
||||
return TopoShape().makeElementCompound(holes);
|
||||
}
|
||||
|
||||
TopoDS_Shape Hole::makeThread(const gp_Vec& xDir, const gp_Vec& zDir, double length)
|
||||
|
||||
@@ -224,7 +224,7 @@ private:
|
||||
void rotateToNormal(const gp_Dir& helixAxis, const gp_Dir& normalAxis, TopoDS_Shape& helixShape) const;
|
||||
gp_Vec computePerpendicular(const gp_Vec&) const;
|
||||
TopoDS_Shape makeThread(const gp_Vec&, const gp_Vec&, double);
|
||||
TopoDS_Compound findHoles(const TopoDS_Shape& profileshape, const TopoDS_Shape& protohole) const;
|
||||
TopoShape findHoles(std::vector<TopoShape> &holes, const TopoShape& profileshape, const TopoDS_Shape& protohole) const;
|
||||
|
||||
// helpers for nlohmann json
|
||||
friend void from_json(const nlohmann::json &j, CounterBoreDimension &t);
|
||||
|
||||
@@ -72,7 +72,11 @@ Loft::getSectionShape(const char *name,
|
||||
size_t expected_size)
|
||||
{
|
||||
std::vector<TopoShape> shapes;
|
||||
if (subs.empty() || std::find(subs.begin(), subs.end(), std::string()) != subs.end()) {
|
||||
// Be smart. If part of a sketch is selected, use the entire sketch unless it is a single vertex -
|
||||
// backward compatibility (#16630)
|
||||
auto subName = subs.empty() ? "" : subs.front();
|
||||
auto useEntireSketch = obj->isDerivedFrom(Part::Part2DObject::getClassTypeId()) && subName.find("Vertex") != 0;
|
||||
if (subs.empty() || std::find(subs.begin(), subs.end(), std::string()) != subs.end() || useEntireSketch ) {
|
||||
shapes.push_back(Part::Feature::getTopoShape(obj));
|
||||
if (shapes.back().isNull())
|
||||
FC_THROWM(Part::NullShapeException, "Failed to get shape of "
|
||||
|
||||
@@ -708,12 +708,10 @@ int ProfileBased::getUpToShapeFromLinkSubList(TopoShape& upToShape, const App::P
|
||||
}
|
||||
}
|
||||
if (ret == 0){
|
||||
throw Base::ValueError("SketchBased: No face selected");
|
||||
return 0;
|
||||
}
|
||||
|
||||
upToShape = faceList[0];
|
||||
|
||||
if (ret == 1){
|
||||
upToShape = faceList[0];
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -850,7 +848,6 @@ 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)
|
||||
@@ -889,13 +886,11 @@ void ProfileBased::getUpToFace(TopoShape& upToFace,
|
||||
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);
|
||||
// is not parallel to the extrusion direction
|
||||
BRepAdaptor_Surface adapt(face);
|
||||
|
||||
if (adapt2.GetType() == GeomAbs_Plane) {
|
||||
if (adapt1.Plane().Axis().IsNormal(adapt2.Plane().Axis(), Precision::Confusion())) {
|
||||
if (adapt.GetType() == GeomAbs_Plane) {
|
||||
if (dir.IsNormal(adapt.Plane().Axis().Direction(), Precision::Confusion())) {
|
||||
throw Base::ValueError(
|
||||
"SketchBased: Up to face: Must not be parallel to extrusion direction!");
|
||||
}
|
||||
|
||||
@@ -168,12 +168,11 @@ protected:
|
||||
|
||||
/// Create a shape with shapes and faces from a given LinkSubList
|
||||
/// return 0 if almost one full shape is selected else the face count
|
||||
int getUpToShapeFromLinkSubList(TopoShape& upToShape, const App::PropertyLinkSubList& refShape); // TODO static
|
||||
static int getUpToShapeFromLinkSubList(TopoShape& upToShape, const App::PropertyLinkSubList& refShape);
|
||||
|
||||
/// 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);
|
||||
|
||||
Reference in New Issue
Block a user