[Part] handle inner wires for Extrude (#5367)

* [Part] handle inner wires for Extrude

this is a PR to make Extrude handle inner wires of sketches.
This commit is contained in:
Uwe
2022-01-29 21:50:55 +01:00
committed by GitHub
parent a26933bf4c
commit cce730c79d
2 changed files with 353 additions and 137 deletions

View File

@@ -26,14 +26,21 @@
# include <cmath>
# include <BRepAdaptor_Surface.hxx>
# include <BRepAdaptor_Curve.hxx>
# include <BRepAlgoAPI_Cut.hxx>
# include <BRepBuilderAPI_Copy.hxx>
# include <BRepBuilderAPI_MakeFace.hxx>
# include <BRepBuilderAPI_MakeSolid.hxx>
# include <BRepBuilderAPI_MakeWire.hxx>
# include <BRepBuilderAPI_Sewing.hxx>
# include <BRepClass3d_SolidClassifier.hxx>
# include <BRepGProp.hxx>
# include <BRepLib_FindSurface.hxx>
# include <BRepOffsetAPI_MakeOffset.hxx>
# include <BRepOffsetAPI_ThruSections.hxx>
# include <BRepPrimAPI_MakePrism.hxx>
# include <gp_Pln.hxx>
# include <gp_Trsf.hxx>
# include <GProp_GProps.hxx>
# include <Precision.hxx>
# include <ShapeAnalysis.hxx>
# include <ShapeFix_Wire.hxx>
@@ -45,15 +52,13 @@
#endif
#include "FeatureExtrusion.h"
#include <App/Application.h>
#include <Base/Tools.h>
#include <Base/Exception.h>
#include "Part2DObject.h"
using namespace Part;
PROPERTY_SOURCE(Part::Extrusion, Part::Feature)
const char* Extrusion::eDirModeStrings[] = {
@@ -315,7 +320,6 @@ TopoShape Extrusion::extrudeShape(const TopoShape& source, const Extrusion::Extr
if (result.IsNull())
throw NullShapeException("Result of extrusion is null shape.");
return TopoShape(result);
}
App::DocumentObjectExecReturn* Extrusion::execute(void)
@@ -337,153 +341,360 @@ App::DocumentObjectExecReturn* Extrusion::execute(void)
void Extrusion::makeDraft(const ExtrusionParameters& params, const TopoDS_Shape& shape, std::list<TopoDS_Shape>& drafts)
{
double distanceFwd = tan(params.taperAngleFwd) * params.lengthFwd;
double distanceRev = tan(params.taperAngleRev) * params.lengthRev;
std::vector<std::vector<TopoDS_Shape>> wiresections;
auto addWiresToWireSections =
[&shape](std::vector<std::vector<TopoDS_Shape>>& wiresections) -> size_t {
TopExp_Explorer ex;
size_t i = 0;
for (ex.Init(shape, TopAbs_WIRE); ex.More(); ex.Next(), ++i) {
wiresections.push_back(std::vector<TopoDS_Shape>());
wiresections[i].push_back(TopoDS::Wire(ex.Current()));
}
return i;
};
double distanceFwd = tan(params.taperAngleFwd)*params.lengthFwd;
double distanceRev = tan(params.taperAngleRev)*params.lengthRev;
gp_Vec vecFwd = gp_Vec(params.dir) * params.lengthFwd;
gp_Vec vecRev = gp_Vec(params.dir.Reversed()) * params.lengthRev;
bool bFwd = fabs(params.lengthFwd) > Precision::Confusion();
bool bRev = fabs(params.lengthRev) > Precision::Confusion();
bool bMid = !bFwd || !bRev || params.lengthFwd * params.lengthRev > 0.0; //include the source shape as loft section?
// only if there is a 2nd direction and the negated angle is equal to the first one
// we can omit the source shape as loft section
bool bMid = !bFwd || !bRev || -1.0 * params.taperAngleFwd != params.taperAngleRev;
TopoDS_Wire sourceWire;
if (shape.IsNull())
Standard_Failure::Raise("Not a valid shape");
if (shape.ShapeType() == TopAbs_WIRE) {
ShapeFix_Wire aFix;
aFix.Load(TopoDS::Wire(shape));
aFix.FixReorder();
aFix.FixConnected();
aFix.FixClosed();
sourceWire = aFix.Wire();
// store all wires of the shape into an array
size_t numWires = addWiresToWireSections(wiresections);
if (numWires == 0)
Standard_Failure::Raise("Extrusion: Input must not only consist if a vertex");
// to store the sections for the loft
std::list<TopoDS_Wire> list_of_sections;
std::vector<std::vector<TopoDS_Shape>> extrusionSections;
// we need for all found wires an offset copy of them
// we store them in an array
TopoDS_Wire offsetWire;
for (auto& wire : wiresections)
extrusionSections.push_back(std::vector<TopoDS_Shape>());
size_t rows = 0;
int numEdges = 0;
int numInnerWires = 0;
// We need to find out what are outer wires and what are inner ones
// methods like checking the center of mass etc. don't help us here.
// As solution we build a prism with every wire, then subtract every prism from each other.
// If the subtraction changes the initial prism, the subtracted prism has an inner wire.
std::vector<TopoDS_Shape> resultPrisms;
std::vector<bool> isInnerWire;
TopoDS_Shape singlePrism;
// first build the prisms
for (auto& wireVector : wiresections) {
for (auto& singleWire : wireVector) {
BRepBuilderAPI_MakeFace mkFace(TopoDS::Wire(singleWire));
auto tempFace = mkFace.Shape();
BRepPrimAPI_MakePrism mkPrism(tempFace, vecFwd);
if(!mkPrism.IsDone())
Standard_Failure::Raise("Extrusion: Generating prism failed");
singlePrism = mkPrism.Shape();
resultPrisms.push_back(singlePrism);
}
}
else if (shape.ShapeType() == TopAbs_FACE) {
TopoDS_Wire outerWire = ShapeAnalysis::OuterWire(TopoDS::Face(shape));
sourceWire = outerWire;
// now subtract them
// if the moment of inertia changes, we have an inner wire
GProp_GProps tempProperties;
Standard_Real momentOfInertiaInitial;
Standard_Real momentOfInertiaFinal;
std::vector<bool>::iterator itInner = isInnerWire.begin();
bool isInner;
for (auto itOuter = resultPrisms.begin(); itOuter != resultPrisms.end(); ++itOuter) {
isInner = false;
for (auto itInner = resultPrisms.begin(); itInner != resultPrisms.end(); ++itInner) {
if (itOuter == itInner)
continue;
// get center of mass of first shape
BRepGProp::VolumeProperties(*itInner, tempProperties);
momentOfInertiaInitial = tempProperties.MomentOfInertia(gp_Ax1(gp_Pnt(), params.dir));
BRepAlgoAPI_Cut mkCut(*itInner, *itOuter);
if (!mkCut.IsDone())
Standard_Failure::Raise("Extrusion: Cut out failed");
BRepGProp::VolumeProperties(mkCut.Shape(), tempProperties);
momentOfInertiaFinal = tempProperties.MomentOfInertia(gp_Ax1(gp_Pnt(), params.dir));
// if the whole shape was cut away the resulting shape is not Null but its MomentOfInertia is 0.0
// therefore we have an inner wire if the MomentOfInertia is not zero and changed
if ((momentOfInertiaInitial != momentOfInertiaFinal)
&& (momentOfInertiaFinal > Precision::Confusion())) {
isInner = true;
++numInnerWires;
break;
}
}
isInnerWire.push_back(isInner);
}
else if (shape.ShapeType() == TopAbs_COMPOUND) {
TopoDS_Iterator it(shape);
for (; it.More(); it.Next()) {
makeDraft(params, it.Value(), drafts);
// if all wires are inner ones, we take the first one and issue a warning
if ((numWires - numInnerWires) == 0) {
isInnerWire[0] = false;
Base::Console().Warning("Extrusion: could not determine what structure is the outer one.\n\
The first input one will now be taken as outer one.\n");
}
// at first create offset wires for the reversed part of extrusion
// it is important that these wires are the first loft section
if (bRev) {
// create an offset for all source wires
rows = 0;
for (auto& wireVector : wiresections) {
for (auto& singleWire : wireVector) {
// count number of edges
numEdges = 0;
TopExp_Explorer xp(singleWire, TopAbs_EDGE);
while (xp.More()) {
numEdges++;
xp.Next();
}
// create an offset copy of the wire
if (!isInnerWire[rows]) {
// this is an outer wire
createTaperedPrismOffset(TopoDS::Wire(singleWire), vecRev, distanceRev, numEdges, true, offsetWire);
}
else {
// inner wires must be reversed and get the negated offset
createTaperedPrismOffset(TopoDS::Wire(singleWire.Reversed()), vecRev, -distanceRev, numEdges, true, offsetWire);
}
if (offsetWire.IsNull())
return;
extrusionSections[rows].push_back(offsetWire);
}
++rows;
}
}
// add the source wire as middle section
// it is important to add them after the reversed part
if (bMid) {
// transfer all source wires as they are to the array from which we build the shells
rows = 0;
for (auto& wireVector : wiresections) {
for (auto& singleWire : wireVector) {
extrusionSections[rows].push_back(singleWire);
}
rows++;
}
}
// finally add the forward extrusion offset wires
// these wires must be the last loft section
if (bFwd) {
rows = 0;
for (auto& wireVector : wiresections) {
for (auto& singleWire : wireVector) {
// count number of edges
numEdges = 0;
TopExp_Explorer xp(singleWire, TopAbs_EDGE);
while (xp.More()) {
numEdges++;
xp.Next();
}
// create an offset copy of the wire
if (!isInnerWire[rows]) {
// this is an outer wire
createTaperedPrismOffset(TopoDS::Wire(singleWire), vecFwd, distanceFwd, numEdges, false, offsetWire);
}
else {
// inner wires must be reversed and get the negated offset
createTaperedPrismOffset(TopoDS::Wire(singleWire.Reversed()), vecFwd, -distanceFwd, numEdges, false, offsetWire);
}
if (offsetWire.IsNull())
return;
extrusionSections[rows].push_back(offsetWire);
}
++rows;
}
}
try {
// build all shells
std::vector<TopoDS_Shape> shells;
for (auto& wires : extrusionSections) {
BRepOffsetAPI_ThruSections mkTS(params.solid, /*ruled=*/Standard_True, Precision::Confusion());
for (auto& singleWire : wires) {
if (singleWire.ShapeType() == TopAbs_VERTEX)
mkTS.AddVertex(TopoDS::Vertex(singleWire));
else
mkTS.AddWire(TopoDS::Wire(singleWire));
}
mkTS.Build();
if (!mkTS.IsDone())
Standard_Failure::Raise("Extrusion: Loft could not be built");
shells.push_back(mkTS.Shape());
}
if (params.solid) {
// we only need to cut if we have inner wires
if (numInnerWires > 0) {
TopoDS_Shape result;
BRep_Builder builder;
TopoDS_Compound compOuter;
TopoDS_Shape outerCutShape;
// when there is more than one outer wire make a compound of the outer shells
rows = 0;
if ((numWires - numInnerWires) > 1) {
builder.MakeCompound(compOuter);
for (auto it = shells.begin(); it != shells.end(); ++it) {
if (!isInnerWire[rows])
builder.Add(compOuter, *it);
++rows;
}
outerCutShape = compOuter;
}
else { // take the shell directly without a compound
for (auto it = shells.begin(); it != shells.end(); ++it) {
if (!isInnerWire[rows]) {
outerCutShape = *it;
break;
}
++rows;
}
}
// now the compound of the inner, no matter if we only have one inner
TopoDS_Compound compInner;
builder.MakeCompound(compInner);
rows = 0;
for (auto it = shells.begin(); it != shells.end(); ++it) {
if (isInnerWire[rows])
builder.Add(compInner, *it);
++rows;
}
// cut the inner from the outer
BRepAlgoAPI_Cut mkCutFinal(outerCutShape, compInner);
if (!mkCutFinal.IsDone())
Standard_Failure::Raise("Extrusion: Cut of inner structures failed");
result = mkCutFinal.Shape();
// de-assemble the result to return every solid independently
for (TopoDS_Iterator anExp(result); anExp.More(); anExp.Next()) {
if (anExp.Value().ShapeType() == TopAbs_SOLID)
drafts.push_back(anExp.Value());
}
if (drafts.empty()) // should never happen, we already checked the success of the cut of solids
Standard_Failure::Raise("Extrusion: The extrusion result is no solid shape");
} else
// we already have the results
for (auto it = shells.begin(); it != shells.end(); ++it)
drafts.push_back(*it);
}
else { // no solid
BRepBuilderAPI_Sewing sewer;
sewer.SetTolerance(Precision::Confusion());
for (TopoDS_Shape& s : shells)
sewer.Add(s);
sewer.Perform();
drafts.push_back(sewer.SewedShape());
}
}
catch (Standard_Failure& e) {
throw Base::RuntimeError(e.GetMessageString());
}
catch (const Base::Exception& e) {
throw Base::RuntimeError(e.what());
}
catch (...) {
throw Base::CADKernelError("Extrusion: A fatal error occurred when making the loft");
}
}
void Extrusion::createTaperedPrismOffset(TopoDS_Wire sourceWire,
const gp_Vec& translation,
double offset,
int numEdges,
bool isSecond,
TopoDS_Wire& result) {
// if the wire consists of a single edge which has applied a placement
// then this placement must be reset because otherwise
// BRepOffsetAPI_MakeOffset shows weird behaviour by applying the placement
gp_Trsf tempTransform;
tempTransform.SetTranslation(translation);
TopLoc_Location loc(tempTransform);
TopoDS_Wire movedSourceWire = TopoDS::Wire(sourceWire.Moved(loc));
TopoDS_Shape offsetShape;
if (fabs(offset) > Precision::Confusion()) {
TopLoc_Location edgeLocation;
if (numEdges == 1) {
// create a new wire from the input wire to determine its location
// to reset the location after the offet operation
BRepBuilderAPI_MakeWire mkWire;
TopExp_Explorer xp(sourceWire, TopAbs_EDGE);
while (xp.More()) {
TopoDS_Edge edge = TopoDS::Edge(xp.Current());
edgeLocation = edge.Location();
edge.Location(TopLoc_Location());
mkWire.Add(edge);
xp.Next();
}
movedSourceWire = mkWire.Wire();
}
// create the offset shape
BRepOffsetAPI_MakeOffset mkOffset;
mkOffset.Init(GeomAbs_Arc);
mkOffset.Init(GeomAbs_Intersection);
mkOffset.AddWire(movedSourceWire);
try {
mkOffset.Perform(offset);
offsetShape = mkOffset.Shape();
}
catch (const Base::Exception& e) {
throw Base::RuntimeError(e.what());
result = TopoDS_Wire();
}
if (!mkOffset.IsDone()) {
Standard_Failure::Raise("Extrusion: Offset could not be created");
result = TopoDS_Wire();
}
if (numEdges == 1) {
// we need to move the offset wire first back to its original position
offsetShape.Move(edgeLocation);
// now apply the translation
offsetShape = offsetShape.Moved(loc);
}
}
else {
Standard_Failure::Raise("Only a wire or a face is supported");
offsetShape = movedSourceWire;
}
if (!sourceWire.IsNull()) {
std::list<TopoDS_Wire> list_of_sections;
// if the wire consists of a single edge which has applied a placement
// then this placement must be reset because otherwise the
// BRepOffsetAPI_MakeOffset shows weird behaviour by applying the placement
// twice on the output shape
//
// count all edges of the wire
int numEdges = 0;
TopExp_Explorer xp(sourceWire, TopAbs_EDGE);
while (xp.More()) {
numEdges++;
xp.Next();
}
auto makeOffset = [&numEdges, &sourceWire](const gp_Vec& translation, double offset) -> TopoDS_Wire {
BRepOffsetAPI_MakeOffset mkOffset;
#if OCC_VERSION_HEX >= 0x060800
mkOffset.Init(GeomAbs_Arc);
#endif
#if OCC_VERSION_HEX >= 0x070000
mkOffset.Init(GeomAbs_Intersection);
#endif
gp_Trsf mat;
mat.SetTranslation(translation);
TopLoc_Location loc(mat);
TopoDS_Wire movedSourceWire = TopoDS::Wire(sourceWire.Moved(loc));
TopoDS_Shape offsetShape;
if (fabs(offset) > Precision::Confusion()) {
TopLoc_Location wireLocation;
TopLoc_Location edgeLocation;
if (numEdges == 1) {
wireLocation = movedSourceWire.Location();
BRepBuilderAPI_MakeWire mkWire;
TopExp_Explorer xp(sourceWire, TopAbs_EDGE);
while (xp.More()) {
TopoDS_Edge edge = TopoDS::Edge(xp.Current());
edgeLocation = edge.Location();
edge.Location(TopLoc_Location());
mkWire.Add(edge);
xp.Next();
}
movedSourceWire = mkWire.Wire();
}
mkOffset.AddWire(movedSourceWire);
mkOffset.Perform(offset);
offsetShape = mkOffset.Shape();
offsetShape.Move(edgeLocation);
offsetShape.Move(wireLocation);
}
else {
//stupid OCC doesn't understand, what to do when offset value is zero =/
offsetShape = movedSourceWire;
}
if (offsetShape.IsNull())
Standard_Failure::Raise("Tapered shape is empty");
TopAbs_ShapeEnum type = offsetShape.ShapeType();
TopoDS_Wire resultWire;
if (type == TopAbs_WIRE) {
resultWire = TopoDS::Wire(offsetShape);
}
else if (type == TopAbs_EDGE) {
BRepBuilderAPI_MakeWire mkWire(TopoDS::Edge(offsetShape));
resultWire = mkWire.Wire();
}
else {
Standard_Failure::Raise("Tapered shape type is not supported");
}
return resultWire;
};
//first. add wire for reversed part of extrusion
if (bRev) {
list_of_sections.push_back(makeOffset(vecRev, distanceRev));
}
//next. Add source wire as middle section. Order is important.
if (bMid) {
list_of_sections.push_back(sourceWire);
}
//finally. Forward extrusion offset wire.
if (bFwd) {
list_of_sections.push_back(makeOffset(vecFwd, distanceFwd));
}
//make loft
BRepOffsetAPI_ThruSections mkGenerator(params.solid ? Standard_True : Standard_False, /*ruled=*/Standard_True);
for (std::list<TopoDS_Wire>::const_iterator it = list_of_sections.begin(); it != list_of_sections.end(); ++it) {
const TopoDS_Wire& wire = *it;
mkGenerator.AddWire(wire);
}
try {
#if defined(__GNUC__) && defined (FC_OS_LINUX)
Base::SignalException se;
#endif
mkGenerator.Build();
drafts.push_back(mkGenerator.Shape());
}
catch (Standard_Failure&) {
throw;
}
catch (...) {
throw Base::CADKernelError("Unknown exception from BRepOffsetAPI_ThruSections");
}
if (offsetShape.IsNull()) {
if (isSecond)
Base::Console().Error("Extrusion: end face of tapered against extrusion is empty\n");
else
Base::Console().Error("Extrusion: end face of tapered along extrusion is empty\n");
}
// assure we return a wire and no edge
TopAbs_ShapeEnum type = offsetShape.ShapeType();
if (type == TopAbs_WIRE) {
result = TopoDS::Wire(offsetShape);
}
else if (type == TopAbs_EDGE) {
BRepBuilderAPI_MakeWire mkWire2(TopoDS::Edge(offsetShape));
result = mkWire2.Wire();
}
else {
// this happens usually if type == TopAbs_COMPOUND and means the angle is too small
// since this is a common mistake users will quickly do, issue a warning dialog
// FIXME: Standard_Failure::Raise or App::DocumentObjectExecReturn don't output the message to the user
result = TopoDS_Wire();
if (isSecond)
Base::Console().Error("Extrusion: type of against extrusion end face is not supported.\n" \
"This means most probably that the against taper angle is too large.\n");
else
Base::Console().Error("Extrusion: type of along extrusion is not supported.\n" \
"This means most probably that the along taper angle is too large.\n");
}
}
@@ -558,7 +769,6 @@ void FaceMakerExtrusion::Build()
}
void Part::Extrusion::setupObject()
{
Part::Feature::setupObject();

View File

@@ -123,6 +123,12 @@ public: //mode enumerations
protected:
static void makeDraft(const ExtrusionParameters& params, const TopoDS_Shape&, std::list<TopoDS_Shape>&);
static void createTaperedPrismOffset(TopoDS_Wire sourceWire,
const gp_Vec& translation,
double offset,
int numEdges,
bool isSecond,
TopoDS_Wire& result);
protected:
virtual void setupObject() override;