Merge branch 'main' into bgbsww-toponamingFeatureDressup10399
This commit is contained in:
@@ -87,6 +87,7 @@ short Feature::mustExecute() const
|
||||
return Part::Feature::mustExecute();
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape)
|
||||
{
|
||||
if (shape.IsNull())
|
||||
@@ -100,6 +101,16 @@ TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape)
|
||||
return {};
|
||||
}
|
||||
|
||||
TopoShape Feature::getSolid(const TopoShape& shape)
|
||||
{
|
||||
if (shape.isNull()) {
|
||||
throw Part::NullShapeException("Null shape");
|
||||
}
|
||||
auto res = shape.getSubTopoShape(TopAbs_SOLID, 1);
|
||||
res.fixSolidOrientation();
|
||||
return res;
|
||||
}
|
||||
|
||||
int Feature::countSolids(const TopoDS_Shape& shape, TopAbs_ShapeEnum type)
|
||||
{
|
||||
int result = 0;
|
||||
@@ -176,29 +187,40 @@ const TopoDS_Shape& Feature::getBaseShape() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
Part::TopoShape Feature::getBaseTopoShape(bool silent) const {
|
||||
Part::TopoShape Feature::getBaseTopoShape(bool silent) const
|
||||
{
|
||||
Part::TopoShape result;
|
||||
|
||||
const Part::Feature* BaseObject = getBaseObject(silent);
|
||||
if (!BaseObject)
|
||||
if (!BaseObject) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if(BaseObject != BaseFeature.getValue()) {
|
||||
if (BaseObject->isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId()) ||
|
||||
BaseObject->isDerivedFrom(PartDesign::SubShapeBinder::getClassTypeId()))
|
||||
{
|
||||
if(silent)
|
||||
if (BaseObject != BaseFeature.getValue()) {
|
||||
auto body = getFeatureBody();
|
||||
if (!body) {
|
||||
if (silent) {
|
||||
return result;
|
||||
}
|
||||
throw Base::RuntimeError("Missing container body");
|
||||
}
|
||||
if (BaseObject->isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId())
|
||||
|| BaseObject->isDerivedFrom(PartDesign::SubShapeBinder::getClassTypeId())) {
|
||||
if (silent) {
|
||||
return result;
|
||||
}
|
||||
throw Base::ValueError("Base shape of shape binder cannot be used");
|
||||
}
|
||||
}
|
||||
|
||||
result = BaseObject->Shape.getShape();
|
||||
if(!silent) {
|
||||
if (result.isNull())
|
||||
if (!silent) {
|
||||
if (result.isNull()) {
|
||||
throw Base::ValueError("Base feature's TopoShape is invalid");
|
||||
if (!result.hasSubShape(TopAbs_SOLID))
|
||||
}
|
||||
if (!result.hasSubShape(TopAbs_SOLID)) {
|
||||
throw Base::ValueError("Base feature's shape is not a solid");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -231,6 +253,7 @@ gp_Pln Feature::makePlnFromPlane(const App::DocumentObject* obj)
|
||||
return gp_Pln(gp_Pnt(pos.x,pos.y,pos.z), gp_Dir(normal.x,normal.y,normal.z));
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
TopoDS_Shape Feature::makeShapeFromPlane(const App::DocumentObject* obj)
|
||||
{
|
||||
BRepBuilderAPI_MakeFace builder(makePlnFromPlane(obj));
|
||||
@@ -240,6 +263,16 @@ 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());
|
||||
|
||||
@@ -94,14 +94,18 @@ protected:
|
||||
/**
|
||||
* Get a solid of the given shape. If no solid is found an exception is raised.
|
||||
*/
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
static TopoDS_Shape getSolid(const TopoDS_Shape&);
|
||||
static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID );
|
||||
TopoShape getSolid(const TopoShape&);
|
||||
static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID);
|
||||
|
||||
/// Grab any point from the given face
|
||||
static const gp_Pnt getPointFromFace(const TopoDS_Face& f);
|
||||
/// Make a shape from a base plane (convenience method)
|
||||
static gp_Pln makePlnFromPlane(const App::DocumentObject* obj);
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
static TopoDS_Shape makeShapeFromPlane(const App::DocumentObject* obj);
|
||||
static TopoShape makeTopoShapeFromPlane(const App::DocumentObject* obj);
|
||||
};
|
||||
|
||||
using FeaturePython = App::FeaturePythonT<Feature>;
|
||||
|
||||
@@ -64,6 +64,7 @@ short FeatureAddSub::mustExecute() const
|
||||
return PartDesign::Feature::mustExecute();
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
TopoDS_Shape FeatureAddSub::refineShapeIfActive(const TopoDS_Shape& oldShape) const
|
||||
{
|
||||
if (this->Refine.getValue()) {
|
||||
@@ -83,6 +84,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); // Todo: Not clear that this is required
|
||||
return shape.makeElementRefine();
|
||||
}
|
||||
return oldShape;
|
||||
}
|
||||
|
||||
void FeatureAddSub::getAddSubShape(Part::TopoShape &addShape, Part::TopoShape &subShape)
|
||||
{
|
||||
if (addSubType == Additive)
|
||||
|
||||
@@ -54,7 +54,9 @@ public:
|
||||
protected:
|
||||
Type addSubType{Additive};
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
TopoDS_Shape refineShapeIfActive(const TopoDS_Shape&) const;
|
||||
TopoShape refineShapeIfActive(const TopoShape&) const;
|
||||
};
|
||||
|
||||
using FeatureAddSubPython = App::FeaturePythonT<FeatureAddSub>;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2020 Werner Mayer <wmayer[at]users.sourceforge.net> *
|
||||
* *
|
||||
@@ -38,9 +39,12 @@
|
||||
#include <App/Document.h>
|
||||
#include <Base/Tools.h>
|
||||
#include <Mod/Part/App/ExtrusionHelper.h>
|
||||
#include "Mod/Part/App/TopoShapeOpCode.h"
|
||||
|
||||
#include "FeatureExtrude.h"
|
||||
|
||||
FC_LOG_LEVEL_INIT("PartDesign", true, true)
|
||||
|
||||
using namespace PartDesign;
|
||||
|
||||
PROPERTY_SOURCE(PartDesign::FeatureExtrude, PartDesign::ProfileBased)
|
||||
@@ -126,6 +130,7 @@ bool FeatureExtrude::hasTaperedAngle() const
|
||||
fabs(TaperAngle2.getValue()) > Base::toRadians(Precision::Angular());
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
void FeatureExtrude::generatePrism(TopoDS_Shape& prism,
|
||||
const TopoDS_Shape& sketchshape,
|
||||
const std::string& method,
|
||||
@@ -246,6 +251,72 @@ 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 +420,335 @@ 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 = getTopoShapeVerifiedFace();
|
||||
}
|
||||
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 = getTopoShapeSupportFace();
|
||||
supportface.move(invObjLoc);
|
||||
|
||||
if (Reversed.getValue()) {
|
||||
dir.Reverse();
|
||||
}
|
||||
|
||||
// Find a valid face or datum plane to extrude up to
|
||||
TopoShape upToFace;
|
||||
|
||||
if (method != "UpToShape") {
|
||||
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);
|
||||
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,
|
||||
true /*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, getDocument()->getStringHasher());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +60,26 @@ 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 +92,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 +143,6 @@ protected:
|
||||
|
||||
} //namespace PartDesign
|
||||
|
||||
ENABLE_BITMASK_OPERATORS(PartDesign::FeatureExtrude::ExtrudeOption)
|
||||
|
||||
#endif // PARTDESIGN_FEATURE_EXTRUDE_H
|
||||
|
||||
@@ -2000,7 +2000,7 @@ TopoDS_Compound Hole::findHoles(const TopoDS_Shape& profileshape,
|
||||
Handle(Geom_Curve) c = BRep_Tool::Curve(edge, c_start, c_end);
|
||||
|
||||
// Circle?
|
||||
if (c->DynamicType() != STANDARD_TYPE(Geom_Circle)) {
|
||||
if (c.IsNull() || c->DynamicType() != STANDARD_TYPE(Geom_Circle)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,13 @@ Pad::Pad()
|
||||
Length2.setConstraints(nullptr);
|
||||
}
|
||||
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
|
||||
App::DocumentObjectExecReturn* Pad::execute()
|
||||
{
|
||||
return buildExtrusion(ExtrudeOption::MakeFace | ExtrudeOption::MakeFuse);
|
||||
}
|
||||
#else
|
||||
App::DocumentObjectExecReturn *Pad::execute()
|
||||
{
|
||||
double L = Length.getValue();
|
||||
@@ -262,3 +269,4 @@ App::DocumentObjectExecReturn *Pad::execute()
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
#include "FeaturePrimitive.h"
|
||||
#include "FeaturePy.h"
|
||||
#include "Mod/Part/App/TopoShapeOpCode.h"
|
||||
|
||||
using namespace PartDesign;
|
||||
|
||||
@@ -65,19 +66,34 @@ FeaturePrimitive::FeaturePrimitive()
|
||||
Part::AttachExtension::initExtension(this);
|
||||
}
|
||||
|
||||
App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& primitiveShape)
|
||||
App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& primitive)
|
||||
{
|
||||
try {
|
||||
//transform the primitive in the correct coordinance
|
||||
FeatureAddSub::execute();
|
||||
|
||||
//if we have no base we just add the standard primitive shape
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
TopoShape primitiveShape;
|
||||
primitiveShape.setShape(primitive);
|
||||
|
||||
TopoShape base;
|
||||
try {
|
||||
// if we have a base shape we need to make sure that it does not get our transformation
|
||||
// to
|
||||
base = getBaseTopoShape().moved(getLocation().Inverted());
|
||||
primitiveShape.Tag = -this->getID();
|
||||
}
|
||||
|
||||
#else
|
||||
auto primitiveShape = primitive;
|
||||
TopoDS_Shape base;
|
||||
try {
|
||||
//if we have a base shape we need to make sure that it does not get our transformation to
|
||||
BRepBuilderAPI_Transform trsf(getBaseShape(), getLocation().Transformation().Inverted(), true);
|
||||
base = trsf.Shape();
|
||||
}
|
||||
#endif
|
||||
catch (const Base::Exception&) {
|
||||
|
||||
//as we use this for preview we can add it even if useless for subtractive
|
||||
@@ -90,14 +106,45 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri
|
||||
|
||||
return App::DocumentObject::StdReturn;
|
||||
}
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
AddSubShape.setValue(primitiveShape);
|
||||
|
||||
TopoShape boolOp(0);
|
||||
|
||||
const char* maker;
|
||||
switch (getAddSubType()) {
|
||||
case Additive:
|
||||
maker = Part::OpCodes::Fuse;
|
||||
break;
|
||||
case Subtractive:
|
||||
maker = Part::OpCodes::Cut;
|
||||
break;
|
||||
default:
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Unknown operation type"));
|
||||
}
|
||||
try {
|
||||
boolOp.makeElementBoolean(maker, {base, primitiveShape});
|
||||
}
|
||||
catch (Standard_Failure& e) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Failed to perform boolean operation"));
|
||||
}
|
||||
boolOp = this->getSolid(boolOp);
|
||||
// lets check if the result is a solid
|
||||
if (boolOp.isNull()) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid"));
|
||||
}
|
||||
#else
|
||||
TopoDS_Shape boolOp;
|
||||
if (getAddSubType() == FeatureAddSub::Additive) {
|
||||
|
||||
BRepAlgoAPI_Fuse mkFuse(base, primitiveShape);
|
||||
if (!mkFuse.IsDone())
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Adding the primitive failed"));
|
||||
// we have to get the solids (fuse sometimes creates compounds)
|
||||
TopoDS_Shape boolOp = this->getSolid(mkFuse.Shape());
|
||||
boolOp = this->getSolid(mkFuse.Shape());
|
||||
// lets check if the result is a solid
|
||||
if (boolOp.IsNull())
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid"));
|
||||
@@ -106,10 +153,6 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri
|
||||
if (solidCount > 1) {
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported."));
|
||||
}
|
||||
|
||||
boolOp = refineShapeIfActive(boolOp);
|
||||
Shape.setValue(getSolid(boolOp));
|
||||
AddSubShape.setValue(primitiveShape);
|
||||
}
|
||||
else if (getAddSubType() == FeatureAddSub::Subtractive) {
|
||||
|
||||
@@ -117,7 +160,7 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri
|
||||
if (!mkCut.IsDone())
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Subtracting the primitive failed"));
|
||||
// we have to get the solids (fuse sometimes creates compounds)
|
||||
TopoDS_Shape boolOp = this->getSolid(mkCut.Shape());
|
||||
boolOp = this->getSolid(mkCut.Shape());
|
||||
// lets check if the result is a solid
|
||||
if (boolOp.IsNull())
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid"));
|
||||
@@ -126,13 +169,11 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri
|
||||
if (solidCount > 1) {
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported."));
|
||||
}
|
||||
|
||||
boolOp = refineShapeIfActive(boolOp);
|
||||
Shape.setValue(getSolid(boolOp));
|
||||
AddSubShape.setValue(primitiveShape);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
boolOp = refineShapeIfActive(boolOp);
|
||||
Shape.setValue(getSolid(boolOp));
|
||||
AddSubShape.setValue(primitiveShape);
|
||||
}
|
||||
catch (Standard_Failure& e) {
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@
|
||||
#include "DatumPlane.h"
|
||||
|
||||
|
||||
FC_LOG_LEVEL_INIT("PartDesign",true,true);
|
||||
|
||||
using namespace PartDesign;
|
||||
|
||||
PROPERTY_SOURCE(PartDesign::ProfileBased, PartDesign::FeatureAddSub)
|
||||
@@ -163,6 +165,29 @@ Part::Feature* ProfileBased::getVerifiedObject(bool silent) const {
|
||||
return static_cast<Part::Feature*>(result);
|
||||
}
|
||||
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
TopoShape ProfileBased::getProfileShape() const
|
||||
{
|
||||
TopoShape shape;
|
||||
const auto& subs = Profile.getSubValues();
|
||||
auto profile = Profile.getValue();
|
||||
if (subs.empty()) {
|
||||
shape = Part::Feature::getTopoShape(profile);
|
||||
}
|
||||
else {
|
||||
std::vector<TopoShape> shapes;
|
||||
for (auto& sub : subs) {
|
||||
shapes.push_back(
|
||||
Part::Feature::getTopoShape(profile, sub.c_str(), /* needSubElement */ true));
|
||||
}
|
||||
shape = TopoShape(shape.Tag).makeElementCompound(shapes);
|
||||
}
|
||||
if (shape.isNull()) {
|
||||
throw Part::NullShapeException("Linked shape object is empty");
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
#else
|
||||
Part::TopoShape ProfileBased::getProfileShape() const
|
||||
{
|
||||
auto shape = getTopoShape(Profile.getValue());
|
||||
@@ -174,7 +199,8 @@ Part::TopoShape ProfileBased::getProfileShape() const
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
#endif
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const {
|
||||
|
||||
App::DocumentObject* result = Profile.getValue();
|
||||
@@ -253,7 +279,167 @@ 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");
|
||||
}
|
||||
|
||||
// Toponaming April 2024: This appears to be new feature, not TNP:
|
||||
// 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()
|
||||
|| 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
std::vector<TopoDS_Wire> ProfileBased::getProfileWires() const {
|
||||
std::vector<TopoDS_Wire> result;
|
||||
|
||||
@@ -292,9 +478,29 @@ 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!
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
const TopoDS_Face ProfileBased::getSupportFace() const
|
||||
{
|
||||
const Part::Part2DObject* sketch = getVerifiedSketch(true);
|
||||
@@ -307,7 +513,8 @@ const TopoDS_Face ProfileBased::getSupportFace() const
|
||||
|
||||
TopoDS_Face ProfileBased::getSupportFace(const Part::Part2DObject* sketch) const
|
||||
{
|
||||
if (sketch && sketch->MapMode.getValue() == Attacher::mmFlatFace && sketch->AttachmentSupport.getValue()) {
|
||||
if (sketch && sketch->MapMode.getValue() == Attacher::mmFlatFace
|
||||
&& sketch->AttachmentSupport.getValue()) {
|
||||
const auto& AttachmentSupport = sketch->AttachmentSupport;
|
||||
App::DocumentObject* ref = AttachmentSupport.getValue();
|
||||
|
||||
@@ -323,20 +530,24 @@ TopoDS_Face ProfileBased::getSupportFace(const Part::Part2DObject* sketch) const
|
||||
|
||||
// get the selected sub shape (a Face)
|
||||
const Part::TopoShape& shape = part->Shape.getShape();
|
||||
if (shape.getShape().IsNull())
|
||||
if (shape.getShape().IsNull()) {
|
||||
throw Base::ValueError("Sketch support shape is empty!");
|
||||
}
|
||||
|
||||
TopoDS_Shape sh = shape.getSubShape(sub[0].c_str());
|
||||
if (sh.IsNull())
|
||||
if (sh.IsNull()) {
|
||||
throw Base::ValueError("Null shape in SketchBased::getSupportFace()!");
|
||||
}
|
||||
|
||||
const TopoDS_Face face = TopoDS::Face(sh);
|
||||
if (face.IsNull())
|
||||
if (face.IsNull()) {
|
||||
throw Base::ValueError("Null face in SketchBased::getSupportFace()!");
|
||||
}
|
||||
|
||||
BRepAdaptor_Surface adapt(face);
|
||||
if (adapt.GetType() != GeomAbs_Plane)
|
||||
if (adapt.GetType() != GeomAbs_Plane) {
|
||||
throw Base::TypeError("No planar face in SketchBased::getSupportFace()!");
|
||||
}
|
||||
|
||||
return face;
|
||||
}
|
||||
@@ -356,6 +567,42 @@ 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 = getTopoShapeVerifiedFace();
|
||||
}
|
||||
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());
|
||||
@@ -415,6 +662,26 @@ void ProfileBased::onChanged(const App::Property* prop)
|
||||
FeatureAddSub::onChanged(prop);
|
||||
}
|
||||
|
||||
void ProfileBased::getUpToFaceFromLinkSub(TopoShape& upToFace, const App::PropertyLinkSub& refFace)
|
||||
{
|
||||
App::DocumentObject* ref = refFace.getValue();
|
||||
|
||||
if (!ref) {
|
||||
throw Base::ValueError("SketchBased: No face selected");
|
||||
}
|
||||
|
||||
if (ref->getTypeId().isDerivedFrom(App::Plane::getClassTypeId())) {
|
||||
upToFace = makeShapeFromPlane(ref);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& subs = refFace.getSubValues();
|
||||
upToFace = Part::Feature::getTopoShape(ref, subs.size() ? subs[0].c_str() : nullptr, true);
|
||||
if (!upToFace.hasSubShape(TopAbs_FACE)) {
|
||||
throw Base::ValueError("SketchBased: Up to face: Failed to extract face");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ProfileBased::getFaceFromLinkSub(TopoDS_Face& upToFace, const App::PropertyLinkSub& refFace)
|
||||
{
|
||||
@@ -447,6 +714,7 @@ void ProfileBased::getFaceFromLinkSub(TopoDS_Face& upToFace, const App::Property
|
||||
throw Base::ValueError("SketchBased: Failed to extract face");
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
void ProfileBased::getUpToFace(TopoDS_Face& upToFace,
|
||||
const TopoDS_Shape& support,
|
||||
const TopoDS_Shape& sketchshape,
|
||||
@@ -540,6 +808,67 @@ 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::cutTopoShapeFaces> 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::cutTopoShapeFaces>::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!");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
void ProfileBased::addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, double offset)
|
||||
{
|
||||
// Move the face in the extrusion direction
|
||||
@@ -564,6 +893,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 +1080,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 +1178,7 @@ void ProfileBased::remapSupportShape(const TopoDS_Shape & newShape)
|
||||
link->setValue(this, newSubValues);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace PartDesign {
|
||||
|
||||
@@ -97,17 +97,39 @@ public:
|
||||
* silently returns nullptr, otherwise throw a Base::Exception.
|
||||
* Default is false.
|
||||
*/
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
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
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
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;
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
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;
|
||||
@@ -128,8 +150,7 @@ protected:
|
||||
TopoDS_Face getSupportFace(const App::PropertyLinkSub& link) const;
|
||||
|
||||
/// Extract a face from a given LinkSub
|
||||
static void getFaceFromLinkSub(TopoDS_Face& upToFace,
|
||||
const App::PropertyLinkSub& refFace);
|
||||
static void getFaceFromLinkSub(TopoDS_Face& upToFace, const App::PropertyLinkSub& refFace);
|
||||
|
||||
/// Find a valid face to extrude up to
|
||||
static void getUpToFace(TopoDS_Face& upToFace,
|
||||
@@ -139,7 +160,20 @@ protected:
|
||||
const gp_Dir& dir);
|
||||
|
||||
/// Add an offset to the face
|
||||
static void addOffsetToFace(TopoDS_Face& upToFace,
|
||||
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);
|
||||
|
||||
|
||||
@@ -110,11 +110,38 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
else:
|
||||
print("TOPOLOGICAL NAMING PROBLEM IS PRESENT.")
|
||||
|
||||
def testPartDesignElementMapPad(self):
|
||||
""" Test that padding a sketch results in a correct element map. Note that comprehensive testing
|
||||
of the geometric functionality of the Pad is in TestPad.py """
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
padSketch = self.Doc.addObject('Sketcher::SketchObject', 'SketchPad')
|
||||
pad = self.Doc.addObject("PartDesign::Pad", "Pad")
|
||||
body.addObject(padSketch)
|
||||
body.addObject(pad)
|
||||
TestSketcherApp.CreateRectangleSketch(padSketch, (0, 0), (1, 1))
|
||||
pad.Profile = padSketch
|
||||
pad.Length = 1
|
||||
# Act
|
||||
self.Doc.recompute()
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
reverseMap = pad.Shape.ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
# Assert
|
||||
self.assertEqual(pad.Shape.ElementMapSize,30) # 4 duplicated Vertexes in here
|
||||
self.assertEqual(len(reverseMap),26)
|
||||
self.assertEqual(len(faces),6)
|
||||
self.assertEqual(len(edges),12)
|
||||
self.assertEqual(len(vertexes),8)
|
||||
|
||||
def testPartDesignElementMapBox(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
box = self.Doc.addObject('PartDesign::AdditiveBox', 'Box')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(box.Shape.childShapes()), 0)
|
||||
@@ -131,7 +158,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
cylinder = self.Doc.addObject('PartDesign::AdditiveCylinder', 'Cylinder')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(cylinder.Shape.childShapes()), 0)
|
||||
@@ -141,14 +168,22 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(cylinder)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 8)
|
||||
self.assertEqual(len(reverseMap),8)
|
||||
self.assertEqual(len(faces),3)
|
||||
self.assertEqual(len(edges),3)
|
||||
self.assertEqual(len(vertexes),2)
|
||||
|
||||
def testPartDesignElementMapSphere(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
sphere = self.Doc.addObject('PartDesign::AdditiveSphere', 'Sphere')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(sphere.Shape.childShapes()), 0)
|
||||
@@ -158,14 +193,22 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(sphere)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 6)
|
||||
self.assertEqual(len(reverseMap),6)
|
||||
self.assertEqual(len(faces),1)
|
||||
self.assertEqual(len(edges),3)
|
||||
self.assertEqual(len(vertexes),2)
|
||||
|
||||
def testPartDesignElementMapCone(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
cone = self.Doc.addObject('PartDesign::AdditiveCone', 'Cone')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(cone.Shape.childShapes()), 0)
|
||||
@@ -175,14 +218,22 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(cone)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 8)
|
||||
self.assertEqual(len(reverseMap),8)
|
||||
self.assertEqual(len(faces),3)
|
||||
self.assertEqual(len(edges),3)
|
||||
self.assertEqual(len(vertexes),2)
|
||||
|
||||
def testPartDesignElementMapEllipsoid(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
ellipsoid = self.Doc.addObject('PartDesign::AdditiveEllipsoid', 'Ellipsoid')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(ellipsoid.Shape.childShapes()), 0)
|
||||
@@ -192,14 +243,22 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(ellipsoid)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 6)
|
||||
self.assertEqual(len(reverseMap),6)
|
||||
self.assertEqual(len(faces),1)
|
||||
self.assertEqual(len(edges),3)
|
||||
self.assertEqual(len(vertexes),2)
|
||||
|
||||
def testPartDesignElementMapTorus(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
torus = self.Doc.addObject('PartDesign::AdditiveTorus', 'Torus')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(torus.Shape.childShapes()), 0)
|
||||
@@ -209,14 +268,22 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(torus)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 4)
|
||||
self.assertEqual(len(reverseMap),4)
|
||||
self.assertEqual(len(faces),1)
|
||||
self.assertEqual(len(edges),2)
|
||||
self.assertEqual(len(vertexes),1)
|
||||
|
||||
def testPartDesignElementMapPrism(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
prism = self.Doc.addObject('PartDesign::AdditivePrism', 'Prism')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(prism.Shape.childShapes()), 0)
|
||||
@@ -226,14 +293,22 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(prism)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 38)
|
||||
self.assertEqual(len(reverseMap),38)
|
||||
self.assertEqual(len(faces),8)
|
||||
self.assertEqual(len(edges),18)
|
||||
self.assertEqual(len(vertexes),12)
|
||||
|
||||
def testPartDesignElementMapWedge(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
wedge = self.Doc.addObject('PartDesign::AdditiveWedge', 'Wedge')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(wedge.Shape.childShapes()), 0)
|
||||
@@ -243,8 +318,16 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(wedge)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26)
|
||||
self.assertEqual(len(reverseMap),26)
|
||||
self.assertEqual(len(faces),6)
|
||||
self.assertEqual(len(edges),12)
|
||||
self.assertEqual(len(vertexes),8)
|
||||
|
||||
# body.BaseFeature = box
|
||||
|
||||
@@ -256,7 +339,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subbox = self.Doc.addObject('PartDesign::SubtractiveBox', 'Box')
|
||||
@@ -275,7 +358,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subcylinder = self.Doc.addObject('PartDesign::SubtractiveCylinder', 'Cylinder')
|
||||
@@ -294,7 +377,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subsphere = self.Doc.addObject('PartDesign::SubtractiveSphere', 'Sphere')
|
||||
@@ -313,7 +396,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subcone = self.Doc.addObject('PartDesign::SubtractiveCone', 'Cone')
|
||||
@@ -332,7 +415,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subellipsoid = self.Doc.addObject('PartDesign::SubtractiveEllipsoid', 'Ellipsoid')
|
||||
@@ -351,7 +434,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subtorus = self.Doc.addObject('PartDesign::SubtractiveTorus', 'Torus')
|
||||
@@ -370,7 +453,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subprism = self.Doc.addObject('PartDesign::SubtractivePrism', 'Prism')
|
||||
@@ -389,7 +472,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subwedge = self.Doc.addObject('PartDesign::SubtractiveWedge', 'Wedge')
|
||||
@@ -405,25 +488,35 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
|
||||
TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1))
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
pad = self.Doc.addObject('PartDesign::Pad', 'Pad')
|
||||
pad.Profile = sketch
|
||||
pad.BaseFeature = sketch
|
||||
body.addObject(sketch)
|
||||
body.addObject(pad)
|
||||
self.Doc.recompute()
|
||||
# Assert
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26)
|
||||
# self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
if App.GuiUp:
|
||||
# Todo: This triggers a 'hasher mismatch' warning in TopoShape::mapSubElement as called by
|
||||
# flushElementMap. This appears to be the case whenever you have a parent with a hasher
|
||||
# that has children without hashmaps. The warning seems to be spurious in this case, but
|
||||
# perhaps there is a solution involving setting hashmaps on all elements.
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 30)
|
||||
else:
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26)
|
||||
self.assertEqual(body.Shape.ElementMapSize,30)
|
||||
self.assertEqual(sketch.Shape.ElementMapSize,12)
|
||||
self.assertEqual(pad.Shape.ElementMapSize,30)
|
||||
# Todo: Assert that the names in the ElementMap are good; in particular that they are hashed with a # starting
|
||||
|
||||
def testPartDesignElementMapRevolution(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
|
||||
TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1))
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
revolution = self.Doc.addObject('PartDesign::Revolution', 'Revolution')
|
||||
@@ -444,7 +537,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
sketch2 = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
|
||||
TestSketcherApp.CreateRectangleSketch(sketch2, (0, 0), (2, 2))
|
||||
sketch2.Placement.move(App.Vector(0, 0, 3))
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
loft = self.Doc.addObject('PartDesign::AdditiveLoft', 'Loft')
|
||||
@@ -465,7 +558,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1))
|
||||
sketch2 = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
|
||||
TestSketcherApp.CreateRectangleSketch(sketch2, (0, 0), (2, 2))
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
pipe = self.Doc.addObject('PartDesign::AdditivePipe', 'Pipe')
|
||||
@@ -484,7 +577,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
|
||||
TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1))
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
helix = self.Doc.addObject('PartDesign::AdditiveHelix', 'Helix')
|
||||
@@ -519,6 +612,8 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
groove.Reversed = 0
|
||||
groove.Base = App.Vector(0, 0, 0)
|
||||
self.Doc.recompute()
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Assert
|
||||
# print(groove.Shape.childShapes()[0].ElementMap)
|
||||
# TODO: Complete me as part of the subtractive features
|
||||
@@ -638,14 +733,13 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
pad.Profile = sketch
|
||||
body.addObject(pad)
|
||||
self.Doc.recompute()
|
||||
if body.Shape.ElementMapVersion == "": # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Assert
|
||||
self.assertEqual(sketch.Shape.ElementMapSize, 12)
|
||||
self.assertEqual(pad.Shape.ElementMapSize, 30) # The sketch plus the pad in the map
|
||||
# TODO: differing results between main and LS3 on these values. Does it matter?
|
||||
# self.assertEqual(body.Shape.ElementMapSize,0) # 8?
|
||||
# self.Doc.recompute()
|
||||
# self.assertEqual(body.Shape.ElementMapSize,30) # 26
|
||||
|
||||
def testPlaneElementMap(self):
|
||||
@@ -657,7 +751,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
pad = self.Doc.addObject('PartDesign::Pad', 'Pad')
|
||||
pad.Profile = plane
|
||||
self.Doc.recompute()
|
||||
if pad.Shape.ElementMapVersion == "": # Skip without element maps.
|
||||
if pad.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Assert
|
||||
self.assertEqual(plane.Shape.ElementMapSize, 0)
|
||||
|
||||
Reference in New Issue
Block a user