* Part: Fix Part_Extrude taper angle regression for internal faces The taper angle for holes (inner wires) in `Part_Extrude` was not being negated after the toponaming refactor, causing internal faces to taper in the wrong direction compared to v0.21.2. The original makeDraft function correctly handled inner wires by: - Negating taper for all inner wires in Part_Extrude - Negating taper only for multi-edge inner wires in PartDesign This logic was controlled via an isPartDesign function parameter. When makeElementDraft was introduced for toponaming support, it was designed to use innerTaperAngleFwd/innerTaperAngleRev fields that were never ported from realthunder's branch. The cleanup (commit 709eab3018) removed references to these non-existent fields, leaving makeElementDraft with no inner wire taper handling at all. So, this fix ports the makeDraft inner wire logic to makeElementDraft by adding the flag, and counting wires and then triggering proper angle flip depending on the flag/wire situation. --------- Co-authored-by: Chris Hennes <chennes@pioneerlibrarysystem.org>
721 lines
28 KiB
C++
721 lines
28 KiB
C++
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
/***************************************************************************
|
|
* Copyright (c) 2022 Uwe Stöhr <uwestoehr@lyx.org> *
|
|
* *
|
|
* This file is part of the FreeCAD CAx development system. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU Library General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Library General Public *
|
|
* License along with this library; see the file COPYING.LIB. If not, *
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
* Suite 330, Boston, MA 02111-1307, USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include <FCConfig.h>
|
|
|
|
#include <Mod/Part/App/FCBRepAlgoAPI_Cut.h>
|
|
#include <BRepBuilderAPI_MakeFace.hxx>
|
|
#include <BRepBuilderAPI_MakeWire.hxx>
|
|
#include <BRepBuilderAPI_Sewing.hxx>
|
|
#include <BRepGProp.hxx>
|
|
#include <BRepOffsetAPI_MakeOffset.hxx>
|
|
#include <BRepOffsetAPI_ThruSections.hxx>
|
|
#include <BRepPrimAPI_MakePrism.hxx>
|
|
#include <gp_Ax1.hxx>
|
|
#include <gp_Dir.hxx>
|
|
#include <gp_Trsf.hxx>
|
|
#include <GProp_GProps.hxx>
|
|
#include <Precision.hxx>
|
|
#include <TopExp_Explorer.hxx>
|
|
#include <TopoDS.hxx>
|
|
|
|
|
|
#include <Base/Console.h>
|
|
#include <Base/Exception.h>
|
|
#include <BRepClass3d_SolidClassifier.hxx>
|
|
#include <ShapeFix_Wire.hxx>
|
|
|
|
#include "ExtrusionHelper.h"
|
|
#include "TopoShape.h"
|
|
#include "BRepOffsetAPI_MakeOffsetFix.h"
|
|
#include "Geometry.h"
|
|
|
|
using namespace Part;
|
|
|
|
ExtrusionHelper::ExtrusionHelper() = default;
|
|
|
|
void ExtrusionHelper::makeDraft(
|
|
const TopoDS_Shape& shape,
|
|
const gp_Dir& direction,
|
|
const double LengthFwd,
|
|
const double LengthRev,
|
|
const double AngleFwd,
|
|
const double AngleRev,
|
|
bool isSolid,
|
|
std::list<TopoDS_Shape>& drafts,
|
|
bool isPartDesign
|
|
)
|
|
{
|
|
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.emplace_back();
|
|
wiresections[i].push_back(TopoDS::Wire(ex.Current()));
|
|
}
|
|
return i;
|
|
};
|
|
|
|
double distanceFwd = tan(AngleFwd) * LengthFwd;
|
|
double distanceRev = tan(AngleRev) * LengthRev;
|
|
gp_Vec vecFwd = gp_Vec(direction) * LengthFwd;
|
|
gp_Vec vecRev = gp_Vec(direction.Reversed()) * LengthRev;
|
|
|
|
bool bFwd = fabs(LengthFwd) > Precision::Confusion();
|
|
bool bRev = fabs(LengthRev) > Precision::Confusion();
|
|
// 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 * AngleFwd != AngleRev;
|
|
|
|
if (shape.IsNull()) {
|
|
Standard_Failure::Raise("Not a valid shape");
|
|
}
|
|
|
|
// 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;
|
|
|
|
// we need for all found wires an offset copy of them
|
|
// we store them in an array
|
|
TopoDS_Wire offsetWire;
|
|
std::vector<std::vector<TopoDS_Shape>> extrusionSections(
|
|
wiresections.size(),
|
|
std::vector<TopoDS_Shape>()
|
|
);
|
|
size_t rows = 0;
|
|
int numEdges = 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 moment of inertia changes by a subtraction, we have an inner wire prism.
|
|
//
|
|
// first build the prisms
|
|
std::vector<TopoDS_Shape> resultPrisms;
|
|
TopoDS_Shape singlePrism;
|
|
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);
|
|
}
|
|
}
|
|
// create an array with false to store later which wires are inner ones
|
|
std::vector<bool> isInnerWire(resultPrisms.size(), false);
|
|
std::vector<bool> checklist(resultPrisms.size(), true);
|
|
// finally check reecursively for inner wires
|
|
checkInnerWires(isInnerWire, direction, checklist, false, resultPrisms);
|
|
|
|
// count the number of inner wires
|
|
int numInnerWires = 0;
|
|
for (auto isInner : isInnerWire) {
|
|
if (isInner) {
|
|
++numInnerWires;
|
|
}
|
|
}
|
|
|
|
// 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, true, offsetWire);
|
|
}
|
|
else {
|
|
// there is an OCC bug with single-edge wires (circles), see inside
|
|
// createTaperedPrismOffset
|
|
if (numEdges > 1 || !isPartDesign) {
|
|
// inner wires must get the negated offset
|
|
createTaperedPrismOffset(
|
|
TopoDS::Wire(singleWire),
|
|
vecRev,
|
|
-distanceRev,
|
|
true,
|
|
offsetWire
|
|
);
|
|
}
|
|
else {
|
|
// circles in PartDesign must not get the negated offset
|
|
createTaperedPrismOffset(
|
|
TopoDS::Wire(singleWire),
|
|
vecRev,
|
|
distanceRev,
|
|
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,
|
|
false,
|
|
offsetWire
|
|
);
|
|
}
|
|
else {
|
|
// there is an OCC bug with single-edge wires (circles), see inside
|
|
// createTaperedPrismOffset
|
|
if (numEdges > 1 || !isPartDesign) {
|
|
// inner wires must get the negated offset
|
|
createTaperedPrismOffset(
|
|
TopoDS::Wire(singleWire),
|
|
vecFwd,
|
|
-distanceFwd,
|
|
false,
|
|
offsetWire
|
|
);
|
|
}
|
|
else {
|
|
// circles in PartDesign must not get the negated offset
|
|
createTaperedPrismOffset(
|
|
TopoDS::Wire(singleWire),
|
|
vecFwd,
|
|
distanceFwd,
|
|
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(isSolid, /*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 (isSolid) {
|
|
// we only need to cut if we have inner wires
|
|
if (numInnerWires > 0) {
|
|
// we take every outer wire prism and cut subsequently all inner wires prisms from
|
|
// it every resulting shape is the final drafted extrusion shape
|
|
GProp_GProps tempProperties;
|
|
Standard_Real momentOfInertiaInitial;
|
|
Standard_Real momentOfInertiaFinal;
|
|
std::vector<bool>::iterator isInnerWireIterator = isInnerWire.begin();
|
|
std::vector<bool>::iterator isInnerWireIteratorLoop;
|
|
for (auto itOuter = shells.begin(); itOuter != shells.end(); ++itOuter) {
|
|
if (*isInnerWireIterator) {
|
|
++isInnerWireIterator;
|
|
continue;
|
|
}
|
|
isInnerWireIteratorLoop = isInnerWire.begin();
|
|
for (auto itInner = shells.begin(); itInner != shells.end(); ++itInner) {
|
|
if (itOuter == itInner || !*isInnerWireIteratorLoop) {
|
|
++isInnerWireIteratorLoop;
|
|
continue;
|
|
}
|
|
// get MomentOfInertia of first shape
|
|
BRepGProp::VolumeProperties(*itOuter, tempProperties);
|
|
momentOfInertiaInitial = tempProperties.MomentOfInertia(
|
|
gp_Ax1(gp_Pnt(), direction)
|
|
);
|
|
FCBRepAlgoAPI_Cut mkCut(*itOuter, *itInner);
|
|
if (!mkCut.IsDone()) {
|
|
Standard_Failure::Raise("Extrusion: Final cut out failed");
|
|
}
|
|
BRepGProp::VolumeProperties(mkCut.Shape(), tempProperties);
|
|
momentOfInertiaFinal = tempProperties.MomentOfInertia(
|
|
gp_Ax1(gp_Pnt(), direction)
|
|
);
|
|
// if the whole shape was cut away the resulting shape is not Null but its
|
|
// MomentOfInertia is 0.0 therefore we have a valid cut if the
|
|
// MomentOfInertia is not zero and changed
|
|
if ((momentOfInertiaInitial != momentOfInertiaFinal)
|
|
&& (momentOfInertiaFinal > Precision::Confusion())) {
|
|
// immediately update the outer shape since more inner wire prism might
|
|
// cut it
|
|
*itOuter = mkCut.Shape();
|
|
}
|
|
++isInnerWireIteratorLoop;
|
|
}
|
|
drafts.push_back(*itOuter);
|
|
++isInnerWireIterator;
|
|
}
|
|
}
|
|
else {
|
|
// we already have the results
|
|
for (const auto& shell : shells) {
|
|
drafts.push_back(shell);
|
|
}
|
|
}
|
|
}
|
|
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 ExtrusionHelper::checkInnerWires(
|
|
std::vector<bool>& isInnerWire,
|
|
const gp_Dir direction,
|
|
std::vector<bool>& checklist,
|
|
bool forInner,
|
|
std::vector<TopoDS_Shape> prisms
|
|
)
|
|
{
|
|
// store the number of wires to be checked
|
|
size_t numCheckWiresInitial = 0;
|
|
for (auto checks : checklist) {
|
|
if (checks) {
|
|
++numCheckWiresInitial;
|
|
}
|
|
}
|
|
GProp_GProps tempProperties;
|
|
Standard_Real momentOfInertiaInitial;
|
|
Standard_Real momentOfInertiaFinal;
|
|
size_t numCheckWires = 0;
|
|
std::vector<bool>::iterator isInnerWireIterator = isInnerWire.begin();
|
|
std::vector<bool>::iterator toCheckIterator = checklist.begin();
|
|
// create an array with false used later to store what can be cancelled from the checklist
|
|
std::vector<bool> toDisable(checklist.size(), false);
|
|
int outer = -1;
|
|
// we cut every prism to be checked from the other to be checked ones
|
|
// if nothing happens, a prism can be cancelled from the checklist
|
|
for (auto itOuter = prisms.begin(); itOuter != prisms.end(); ++itOuter) {
|
|
++outer;
|
|
if (!*toCheckIterator) {
|
|
++isInnerWireIterator;
|
|
++toCheckIterator;
|
|
continue;
|
|
}
|
|
auto toCheckIteratorInner = checklist.begin();
|
|
bool saveIsInnerWireIterator = *isInnerWireIterator;
|
|
for (auto itInner = prisms.begin(); itInner != prisms.end(); ++itInner) {
|
|
if (itOuter == itInner || !*toCheckIteratorInner) {
|
|
++toCheckIteratorInner;
|
|
continue;
|
|
}
|
|
// get MomentOfInertia of first shape
|
|
BRepGProp::VolumeProperties(*itInner, tempProperties);
|
|
momentOfInertiaInitial = tempProperties.MomentOfInertia(gp_Ax1(gp_Pnt(), direction));
|
|
FCBRepAlgoAPI_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(), direction));
|
|
// 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())) {
|
|
*isInnerWireIterator = !forInner;
|
|
++numCheckWires;
|
|
*toCheckIterator = true;
|
|
break;
|
|
}
|
|
++toCheckIteratorInner;
|
|
}
|
|
if (saveIsInnerWireIterator == *isInnerWireIterator) {
|
|
// nothing was changed and we can remove it from the list to be checked
|
|
// but we cannot do this before the for loop was fully run
|
|
toDisable[outer] = true;
|
|
}
|
|
++isInnerWireIterator;
|
|
++toCheckIterator;
|
|
}
|
|
|
|
// cancel prisms from the checklist whose wire state did not change
|
|
size_t i = 0;
|
|
for (auto disable : toDisable) {
|
|
if (disable) {
|
|
checklist[i] = false;
|
|
}
|
|
++i;
|
|
}
|
|
|
|
// if all wires are inner ones, we take the first one as outer and issue a warning
|
|
if (numCheckWires == isInnerWire.size()) {
|
|
isInnerWire[0] = false;
|
|
checklist[0] = false;
|
|
--numCheckWires;
|
|
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");
|
|
}
|
|
|
|
// There can be cases with several wires all intersecting each other.
|
|
// Then it is impossible to find out what wire is an inner one
|
|
// and we can only treat all wires in the checklist as outer ones.
|
|
if (numCheckWiresInitial == numCheckWires) {
|
|
i = 0;
|
|
for (auto checks : checklist) {
|
|
if (checks) {
|
|
isInnerWire[i] = false;
|
|
checklist[i] = false;
|
|
--numCheckWires;
|
|
}
|
|
++i;
|
|
}
|
|
Base::Console().warning(
|
|
"Extrusion: too many self-intersection structures!\n\
|
|
Impossible to determine what structure is an inner one.\n\
|
|
All undeterminable structures will therefore be taken as outer ones.\n"
|
|
);
|
|
}
|
|
|
|
// recursively call the function until all wires are checked
|
|
if (numCheckWires > 1) {
|
|
checkInnerWires(isInnerWire, direction, checklist, !forInner, prisms);
|
|
}
|
|
}
|
|
|
|
void ExtrusionHelper::createTaperedPrismOffset(
|
|
TopoDS_Wire sourceWire,
|
|
const gp_Vec& translation,
|
|
double offset,
|
|
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, see
|
|
// https://dev.opencascade.org/content/brepoffsetapimakeoffset-wire-and-face-odd-occt-740
|
|
// therefore we use here the workaround of BRepOffsetAPI_MakeOffsetFix and not
|
|
// BRepOffsetAPI_MakeOffset
|
|
|
|
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;
|
|
// create the offset shape
|
|
BRepOffsetAPI_MakeOffsetFix 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());
|
|
}
|
|
if (!mkOffset.IsDone()) {
|
|
Standard_Failure::Raise("Extrusion: Offset could not be created");
|
|
}
|
|
}
|
|
else {
|
|
offsetShape = movedSourceWire;
|
|
}
|
|
if (offsetShape.IsNull()) {
|
|
if (isSecond) {
|
|
Base::Console().error(
|
|
"Extrusion: end face of tapered against extrusion is empty\n"
|
|
"This means most probably that the against taper angle is too large or small.\n"
|
|
);
|
|
}
|
|
else {
|
|
Base::Console().error(
|
|
"Extrusion: end face of tapered along extrusion is empty\n"
|
|
"This means most probably that the along taper angle is too large or small.\n"
|
|
);
|
|
}
|
|
Standard_Failure::Raise("Extrusion: end face of tapered extrusion is empty");
|
|
}
|
|
// 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 or small.\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 or small.\n"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExtrusionHelper::makeElementDraft(
|
|
const ExtrusionParameters& params,
|
|
const TopoShape& _shape,
|
|
std::vector<TopoShape>& drafts,
|
|
App::StringHasherRef hasher
|
|
)
|
|
{
|
|
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?
|
|
|
|
TopoShape shape = _shape;
|
|
TopoShape sourceWire;
|
|
if (shape.isNull()) {
|
|
Standard_Failure::Raise("Not a valid shape");
|
|
}
|
|
|
|
if (params.solid && !shape.hasSubShape(TopAbs_FACE)) {
|
|
shape = shape.makeElementFace(nullptr, params.faceMakerClass.c_str());
|
|
}
|
|
|
|
if (shape.shapeType() == TopAbs_FACE) {
|
|
std::vector<TopoShape> wires;
|
|
TopoShape outerWire = shape.splitWires(&wires, TopoShape::ReorientForward);
|
|
if (outerWire.isNull()) {
|
|
Standard_Failure::Raise("Missing outer wire");
|
|
}
|
|
if (wires.empty()) {
|
|
shape = outerWire;
|
|
}
|
|
else {
|
|
unsigned pos = drafts.size();
|
|
makeElementDraft(params, outerWire, drafts, hasher);
|
|
if (drafts.size() != pos + 1) {
|
|
Standard_Failure::Raise("Failed to make drafted extrusion");
|
|
}
|
|
std::vector<TopoShape> inner;
|
|
// process each inner wire individually to check edge count
|
|
// Inner wires (holes) need negated taper angles
|
|
for (auto& innerWire : wires) {
|
|
ExtrusionParameters innerParams = params;
|
|
|
|
int numEdges = 0;
|
|
TopExp_Explorer xp(innerWire.getShape(), TopAbs_EDGE);
|
|
while (xp.More()) {
|
|
numEdges++;
|
|
xp.Next();
|
|
}
|
|
|
|
if (numEdges > 1 || params.innerWireTaper == InnerWireTaper::Inverted) {
|
|
innerParams.taperAngleFwd = -params.taperAngleFwd;
|
|
innerParams.taperAngleRev = -params.taperAngleRev;
|
|
}
|
|
makeElementDraft(innerParams, innerWire, inner, hasher);
|
|
}
|
|
if (inner.empty()) {
|
|
Standard_Failure::Raise("Failed to make drafted extrusion with inner hole");
|
|
}
|
|
inner.insert(inner.begin(), drafts.back());
|
|
drafts.back().makeElementCut(inner);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (shape.shapeType() == TopAbs_WIRE) {
|
|
ShapeFix_Wire aFix;
|
|
aFix.Load(TopoDS::Wire(shape.getShape()));
|
|
aFix.FixReorder();
|
|
aFix.FixConnected();
|
|
aFix.FixClosed();
|
|
sourceWire.setShape(aFix.Wire());
|
|
sourceWire.Tag = shape.Tag;
|
|
sourceWire.mapSubElement(shape);
|
|
}
|
|
else if (shape.shapeType() == TopAbs_COMPOUND) {
|
|
for (auto& s : shape.getSubTopoShapes()) {
|
|
makeElementDraft(params, s, drafts, hasher);
|
|
}
|
|
}
|
|
else {
|
|
Standard_Failure::Raise("Only a wire or a face is supported");
|
|
}
|
|
|
|
if (!sourceWire.isNull()) {
|
|
std::vector<TopoShape> list_of_sections;
|
|
if (bRev) {
|
|
TopoDS_Wire offsetWire;
|
|
createTaperedPrismOffset(
|
|
TopoDS::Wire(sourceWire.getShape()),
|
|
vecRev,
|
|
distanceRev,
|
|
false,
|
|
offsetWire
|
|
);
|
|
list_of_sections.push_back(TopoShape(offsetWire, sourceWire.Tag));
|
|
}
|
|
|
|
// 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) {
|
|
TopoDS_Wire offsetWire;
|
|
createTaperedPrismOffset(
|
|
TopoDS::Wire(sourceWire.getShape()),
|
|
vecFwd,
|
|
distanceFwd,
|
|
false,
|
|
offsetWire
|
|
);
|
|
list_of_sections.push_back(TopoShape(offsetWire, sourceWire.Tag));
|
|
}
|
|
|
|
try {
|
|
#if defined(__GNUC__) && defined(FC_OS_LINUX)
|
|
Base::SignalException se;
|
|
#endif
|
|
|
|
// make loft
|
|
BRepOffsetAPI_ThruSections mkGenerator(
|
|
params.solid ? Standard_True : Standard_False,
|
|
/*ruled=*/Standard_True
|
|
);
|
|
for (auto& s : list_of_sections) {
|
|
mkGenerator.AddWire(TopoDS::Wire(s.getShape()));
|
|
}
|
|
|
|
mkGenerator.Build();
|
|
drafts.push_back(TopoShape(0, hasher).makeElementShape(mkGenerator, list_of_sections));
|
|
}
|
|
catch (Standard_Failure&) {
|
|
throw;
|
|
}
|
|
catch (...) {
|
|
throw Base::CADKernelError("Unknown exception from BRepOffsetAPI_ThruSections");
|
|
}
|
|
}
|
|
}
|