Skip to content

Commit

Permalink
PartDesign: Performance improvements in Transformed (pattern)
Browse files Browse the repository at this point in the history
- Use fuzzy fuse/cut method with tolerance Precision::Confusion()
- Use parallel computation feature
- Allow that individual patterned item fail to intersect the baseobject.
	Issue warning instead of error. This was done for performance reasons
	but it can also be considered a feature.
- Simplify the code a bit
- Distinguish between overlapping mode and non-overlapping mode. In non-overlapping mode
  the tool shapes are compounded instead of fused. For huge benefit in performance.
  • Loading branch information
davidosterberg authored and wwmayer committed Mar 13, 2021
1 parent 89b8e81 commit 03da9d3
Showing 1 changed file with 79 additions and 112 deletions.
191 changes: 79 additions & 112 deletions src/Mod/PartDesign/App/FeatureTransformed.cpp
Expand Up @@ -36,6 +36,9 @@
# include <Bnd_Box.hxx>
#endif

#ifndef FC_DEBUG
#include <ctime>
#endif

#include "FeatureTransformed.h"
#include "FeatureMultiTransform.h"
Expand Down Expand Up @@ -198,7 +201,11 @@ short Transformed::mustExecute() const

App::DocumentObjectExecReturn *Transformed::execute(void)
{
rejected.clear();

#ifndef FC_DEBUG
std::clock_t start0;
start0 = std::clock();
#endif

std::vector<App::DocumentObject*> originals = Originals.getValues();
if (originals.empty()) // typically InsideMultiTransform
Expand Down Expand Up @@ -246,10 +253,6 @@ App::DocumentObjectExecReturn *Transformed::execute(void)
supportShape.setTransform(Base::Matrix4D());
TopoDS_Shape support = supportShape.getShape();

typedef std::set<std::vector<gp_Trsf>::const_iterator> trsf_it;
typedef std::map<App::DocumentObject*, trsf_it> rej_it_map;
rej_it_map nointersect_trsfms;

// NOTE: It would be possible to build a compound from all original addShapes/subShapes and then
// transform the compounds as a whole. But we choose to apply the transformations to each
// Original separately. This way it is easier to discover what feature causes a fuse/cut
Expand All @@ -276,132 +279,93 @@ App::DocumentObjectExecReturn *Transformed::execute(void)
else {
return new App::DocumentObjectExecReturn("Only additive and subtractive features can be transformed");
}
TopoDS_Shape origShape = fuseShape.isNull()?cutShape.getShape():fuseShape.getShape();

TopoDS_Shape current = support;

// Transform the add/subshape and collect the resulting shapes for overlap testing
/*typedef std::vector<std::vector<gp_Trsf>::const_iterator> trsf_it_vec;
trsf_it_vec v_transformations;
std::vector<TopoDS_Shape> v_transformedShapes;*/
BRep_Builder builder;
TopoDS_Compound compShape;
builder.MakeCompound(compShape);
std::vector<TopoDS_Shape> shapes;
bool overlapping = false;

std::vector<gp_Trsf>::const_iterator t = transformations.begin();
++t; // Skip first transformation, which is always the identity transformation
for (; t != transformations.end(); ++t) {
// Make an explicit copy of the shape because the "true" parameter to BRepBuilderAPI_Transform
// seems to be pretty broken
BRepBuilderAPI_Copy copy(fuseShape.isNull()?cutShape.getShape():fuseShape.getShape());
BRepBuilderAPI_Copy copy(origShape);

shape = copy.Shape();
if (shape.IsNull())
return new App::DocumentObjectExecReturn("Transformed: Linked shape object is empty");

BRepBuilderAPI_Transform mkTrf(shape, *t, false); // No need to copy, now
if (!mkTrf.IsDone())
return new App::DocumentObjectExecReturn("Transformation failed", (*o));

shape = mkTrf.Shape();
try {
// Intersection checking for additive shape is redundant.
// Because according to CheckIntersection() source code, it is
// implemented using fusion and counting of the resulting
// solid, which will be done in the following modeling step
// anyway.
//
// There is little reason for doing intersection checking on
// subtractive shape either, because it does not produce
// multiple solids.
//
// if (!Part::checkIntersection(support, mkTrf.Shape(), false, true)) {


TopoDS_Shape current = support;

if (!fuseShape.isNull()) {
// We cannot wait to fuse a transformation with the support until all the transformations are done,
// because the "support" potentially changes with every transformation, basically when checking intersection
// above you need:
// 1. The original support
// 2. Any extra support gained by any previous transformation of any previous feature (multi-feature transform)
// 3. Any extra support gained by any previous transformation of this feature (feature multi-trasform)
//
// Therefore, if the transformation succeeded, then we fuse it with the support now, before checking the intersection
// of the next transformation.

/*v_transformations.push_back(t);
v_transformedShapes.push_back(mkTrf.Shape());*/

// Note: Transformations that do not intersect the support are ignored in the overlap tests

//insert scheme here.
/*TopoDS_Compound compoundTool;
std::vector<TopoDS_Shape> individualTools;
divideTools(v_transformedShapes, individualTools, compoundTool);*/

// Fuse/Cut the compounded transformed shapes with the support
//TopoDS_Shape result;

BRepAlgoAPI_Fuse mkFuse(current, shape);
if (!mkFuse.IsDone())
return new App::DocumentObjectExecReturn("Fusion with support failed", *o);

if(Part::TopoShape(current).countSubShapes(TopAbs_SOLID)
!= Part::TopoShape(mkFuse.Shape()).countSubShapes(TopAbs_SOLID))
{
#ifdef FC_DEBUG // do not write this in release mode because a message appears already in the task view
Base::Console().Warning("Transformed shape does not intersect support %s: Removed\n", (*o)->getNameInDocument());

shape.Move(*t);
shapes.emplace_back(shape);
builder.Add(compShape, shape);

overlapping = overlapping || (countSolids(TopoShape(origShape).fuse(shape))==1);

}

TopoDS_Shape toolShape;

if (overlapping) {

#ifndef FC_DEBUG
Base::Console().Message("Transformed: Overlapping feature mode (fusing tool shapes)\n");
#endif
nointersect_trsfms[*o].insert(t);
continue;
}
// we have to get the solids (fuse sometimes creates compounds)
current = this->getSolid(mkFuse.Shape());
// lets check if the result is a solid
if (current.IsNull())
return new App::DocumentObjectExecReturn("Resulting shape is not a solid", *o);

if (!cutShape.isNull()) {
BRepBuilderAPI_Copy copy(cutShape.getShape());
shape = copy.Shape();
if (shape.IsNull())
return new App::DocumentObjectExecReturn("Transformed: Linked shape object is empty");

BRepBuilderAPI_Transform mkTrf(shape, *t, false); // No need to copy, now
if (!mkTrf.IsDone())
return new App::DocumentObjectExecReturn("Transformation failed", (*o));
shape = mkTrf.Shape();
}
}
if (!cutShape.isNull()) {
BRepAlgoAPI_Cut mkCut(current, shape);
if (!mkCut.IsDone())
return new App::DocumentObjectExecReturn("Cut out of support failed", *o);
current = mkCut.Shape();
}
support = current; // Use result of this operation for fuse/cut of next original
} catch (Standard_Failure& e) {
// Note: Ignoring this failure is probably pointless because if the intersection check fails, the later
// fuse operation of the transformation result will also fail

std::string msg("Transformation: Intersection check failed");
if (e.GetMessageString() != NULL)
msg += std::string(": '") + e.GetMessageString() + "'";
return new App::DocumentObjectExecReturn(msg.c_str());

toolShape = TopoShape(origShape).fuse(shapes, Precision::Confusion());
} else {

#ifndef FC_DEBUG
Base::Console().Message("Transformed: Non-Overlapping feature mode (compound of tool shapes)\n");
#endif

toolShape = compShape;
}

if (!fuseShape.isNull()) {
std::unique_ptr<BRepAlgoAPI_BooleanOperation> mkBool(new BRepAlgoAPI_Fuse(current, toolShape));
if (!mkBool->IsDone()) {
std::stringstream error;
error << "Boolean operation failed";
return new App::DocumentObjectExecReturn(error.str());
}
current = mkBool->Shape();
} else {
std::unique_ptr<BRepAlgoAPI_BooleanOperation> mkBool(new BRepAlgoAPI_Cut(current, toolShape));
if (!mkBool->IsDone()) {
std::stringstream error;
error << "Boolean operation failed";
return new App::DocumentObjectExecReturn(error.str());
}
current = mkBool->Shape();
}
try {} catch (Standard_Failure& e) {
// Note: Ignoring this failure is probably pointless because if the intersection check fails, the later
// fuse operation of the transformation result will also fail

std::string msg("Transformation: Intersection check failed");
if (e.GetMessageString() != NULL)
msg += std::string(": '") + e.GetMessageString() + "'";
return new App::DocumentObjectExecReturn(msg.c_str());
}

support = current; // Use result of this operation for fuse/cut of next original
}
support = refineShapeIfActive(support);

for (rej_it_map::const_iterator it = nointersect_trsfms.begin(); it != nointersect_trsfms.end(); ++it)
for (trsf_it::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2)
rejected[it->first].push_back(**it2);
support = refineShapeIfActive(support);

int solidCount = countSolids(support);
if (solidCount > 1) {
return new App::DocumentObjectExecReturn("Transformed: Result has multiple solids. This is not supported at this time.");
Base::Console().Warning("Transformed: Result has multiple solids. Only keeping the first.\n");
}

this->Shape.setValue(getSolid(support));
this->Shape.setValue(getSolid(support)); // picking the first solid

if (rejected.size() > 0) {
return new App::DocumentObjectExecReturn("Transformation failed");
}
#ifndef FC_DEBUG
Base::Console().Message("Transformed: Elapsed CPU time: %f s\n", (std::clock() - start0 ) / (double)(CLOCKS_PER_SEC));
#endif

return App::DocumentObject::StdReturn;
}
Expand All @@ -412,6 +376,9 @@ TopoDS_Shape Transformed::refineShapeIfActive(const TopoDS_Shape& oldShape) cons
try {
Part::BRepBuilderAPI_RefineModel mkRefine(oldShape);
TopoDS_Shape resShape = mkRefine.Shape();
if (!TopoShape(resShape).isClosed()) {
return oldShape;
}
return resShape;
}
catch (Standard_Failure&) {
Expand Down

0 comments on commit 03da9d3

Please sign in to comment.