Skip to content

Commit 43723b6

Browse files
authored
Merge pull request FreeCAD#12431 from bgbsww/bgbsww-toponamingMakeElementRuledSurface
Toponaming/Part: make element ruled surface
2 parents dd02306 + ac618d8 commit 43723b6

File tree

4 files changed

+225
-0
lines changed

4 files changed

+225
-0
lines changed

src/Mod/Part/App/PartFeatures.cpp

+22
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,27 @@ App::DocumentObjectExecReturn* RuledSurface::getShape(const App::PropertyLinkSub
114114
App::DocumentObjectExecReturn *RuledSurface::execute()
115115
{
116116
try {
117+
#ifdef FC_USE_TNP_FIX
118+
std::vector<TopoShape> shapes;
119+
std::array<App::PropertyLinkSub*,2> links = {&Curve1,&Curve2};
120+
for(auto link : links) {
121+
const auto &subs = link->getSubValues();
122+
if(subs.empty())
123+
shapes.push_back(getTopoShape(link->getValue()));
124+
else if(subs.size()!=1)
125+
return new App::DocumentObjectExecReturn("Not exactly one sub-shape linked.");
126+
else
127+
shapes.push_back(getTopoShape(link->getValue(),
128+
subs.front().c_str(),true));
129+
if(shapes.back().isNull())
130+
return new App::DocumentObjectExecReturn("Invalid link.");
131+
}
132+
TopoShape res(0);//, getDocument()->getStringHasher());
133+
res.makeElementRuledSurface(shapes, Orientation.getValue());
134+
this->Shape.setValue(res);
135+
return Part::Feature::execute();
136+
137+
#else
117138
App::DocumentObjectExecReturn* ret;
118139

119140
// get the first input shape
@@ -231,6 +252,7 @@ App::DocumentObjectExecReturn *RuledSurface::execute()
231252

232253
this->Shape.setValue(ruledShape);
233254
return App::DocumentObject::StdReturn;
255+
#endif
234256
}
235257
catch (Standard_Failure& e) {
236258

src/Mod/Part/App/TopoShape.h

+15
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,21 @@ class PartExport TopoShape: public Data::ComplexGeoData
786786
}
787787
};
788788

789+
/** Make a ruled surface
790+
*
791+
* @param sources: the source shapes, each of which must contain either a
792+
* single edge or a single wire.
793+
* @param orientation: A Qt::Orientation, where Qt::Horizontal is 1 and Qt::Vertical is 2.
794+
* @param op: optional string to be encoded into topo naming for indicating
795+
* the operation
796+
*
797+
* @return The original content of this TopoShape is discarded and replaced
798+
* with the new shape. The function returns the TopoShape itself as
799+
* a self reference so that multiple operations can be carried out
800+
* for the same shape in the same line of code.
801+
*/
802+
TopoShape &makeElementRuledSurface(const std::vector<TopoShape> &source, int orientation=0, const char *op=nullptr);
803+
789804
/** Core function to generate mapped element names from shape history
790805
*
791806
* @param shape: the new shape

src/Mod/Part/App/TopoShapeExpansion.cpp

+161
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,16 @@
2727
#ifndef _PreComp_
2828
#include <cmath>
2929

30+
#include <BRepAdaptor_Curve.hxx>
31+
#include <BRepAdaptor_CompCurve.hxx>
32+
# if OCC_VERSION_HEX < 0x070600
33+
# include <BRepAdaptor_HCurve.hxx>
34+
# include <BRepAdaptor_HCompCurve.hxx>
35+
# endif
3036

3137
#include <BRepBuilderAPI_MakeWire.hxx>
3238
#include <BRepCheck_Analyzer.hxx>
39+
#include <BRepFill.hxx>
3340
#include <BRepFill_Generator.hxx>
3441
#include <BRepTools.hxx>
3542
#include <BRep_Builder.hxx>
@@ -77,6 +84,12 @@
7784

7885
FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT
7986

87+
#if OCC_VERSION_HEX >= 0x070600
88+
using Adaptor3d_HCurve = Adaptor3d_Curve;
89+
using BRepAdaptor_HCurve = BRepAdaptor_Curve;
90+
using BRepAdaptor_HCompCurve = BRepAdaptor_CompCurve;
91+
#endif
92+
8093
namespace Part
8194
{
8295

@@ -1653,6 +1666,154 @@ TopoShape TopoShape::getSubTopoShape(TopAbs_ShapeEnum type, int idx, bool silent
16531666
return shapeMap.getTopoShape(*this, idx);
16541667
}
16551668

1669+
TopoShape& TopoShape::makeElementRuledSurface(const std::vector<TopoShape>& shapes,
1670+
int orientation,
1671+
const char* op)
1672+
{
1673+
if (!op) {
1674+
op = Part::OpCodes::RuledSurface;
1675+
}
1676+
1677+
if (shapes.size() != 2) {
1678+
FC_THROWM(Base::CADKernelError, "Wrong number of input shapes");
1679+
}
1680+
1681+
std::vector<TopoShape> curves(2);
1682+
int i = 0;
1683+
for (auto& s : shapes) {
1684+
if (s.isNull()) {
1685+
FC_THROWM(NullShapeException, "Null input shape");
1686+
}
1687+
auto type = s.shapeType();
1688+
if (type == TopAbs_WIRE || type == TopAbs_EDGE) {
1689+
curves[i++] = s;
1690+
continue;
1691+
}
1692+
auto countOfWires = s.countSubShapes(TopAbs_WIRE);
1693+
if (countOfWires > 1) {
1694+
FC_THROWM(Base::CADKernelError, "Input shape has more than one wire");
1695+
}
1696+
if (countOfWires == 1) {
1697+
curves[i++] = s.getSubTopoShape(TopAbs_WIRE, 1);
1698+
continue;
1699+
}
1700+
auto countOfEdges = s.countSubShapes(TopAbs_EDGE);
1701+
if (countOfEdges == 0) {
1702+
FC_THROWM(Base::CADKernelError, "Input shape has no edge");
1703+
}
1704+
if (countOfEdges == 1) {
1705+
curves[i++] = s.getSubTopoShape(TopAbs_EDGE, 1);
1706+
continue;
1707+
}
1708+
curves[i] = s.makeElementWires();
1709+
if (curves[i].isNull()) {
1710+
FC_THROWM(NullShapeException, "Null input shape");
1711+
}
1712+
if (curves[i].shapeType() != TopAbs_WIRE) {
1713+
FC_THROWM(Base::CADKernelError, "Input shape forms more than one wire");
1714+
}
1715+
++i;
1716+
}
1717+
1718+
if (curves[0].shapeType() != curves[1].shapeType()) {
1719+
for (auto& curve : curves) {
1720+
if (curve.shapeType() == TopAbs_EDGE) {
1721+
curve = curve.makeElementWires();
1722+
}
1723+
}
1724+
}
1725+
1726+
auto& S1 = curves[0];
1727+
auto& S2 = curves[1];
1728+
bool isWire = S1.shapeType() == TopAbs_WIRE;
1729+
1730+
// https://forum.freecadweb.org/viewtopic.php?f=8&t=24052
1731+
//
1732+
// if both shapes are sub-elements of one common shape then the fill
1733+
// algorithm leads to problems if the shape has set a placement. The
1734+
// workaround is to copy the sub-shape
1735+
S1 = S1.makeElementCopy();
1736+
S2 = S2.makeElementCopy();
1737+
1738+
if (orientation == 0) {
1739+
// Automatic
1740+
Handle(Adaptor3d_HCurve) a1;
1741+
Handle(Adaptor3d_HCurve) a2;
1742+
if (!isWire) {
1743+
BRepAdaptor_HCurve adapt1(TopoDS::Edge(S1.getShape()));
1744+
BRepAdaptor_HCurve adapt2(TopoDS::Edge(S2.getShape()));
1745+
a1 = new BRepAdaptor_HCurve(adapt1);
1746+
a2 = new BRepAdaptor_HCurve(adapt2);
1747+
}
1748+
else {
1749+
BRepAdaptor_HCompCurve adapt1(TopoDS::Wire(S1.getShape()));
1750+
BRepAdaptor_HCompCurve adapt2(TopoDS::Wire(S2.getShape()));
1751+
a1 = new BRepAdaptor_HCompCurve(adapt1);
1752+
a2 = new BRepAdaptor_HCompCurve(adapt2);
1753+
}
1754+
1755+
if (!a1.IsNull() && !a2.IsNull()) {
1756+
// get end points of 1st curve
1757+
gp_Pnt p1 = a1->Value(a1->FirstParameter());
1758+
gp_Pnt p2 = a1->Value(a1->LastParameter());
1759+
if (S1.getShape().Orientation() == TopAbs_REVERSED) {
1760+
std::swap(p1, p2);
1761+
}
1762+
1763+
// get end points of 2nd curve
1764+
gp_Pnt p3 = a2->Value(a2->FirstParameter());
1765+
gp_Pnt p4 = a2->Value(a2->LastParameter());
1766+
if (S2.getShape().Orientation() == TopAbs_REVERSED) {
1767+
std::swap(p3, p4);
1768+
}
1769+
1770+
// Form two triangles (P1,P2,P3) and (P4,P3,P2) and check their normals.
1771+
// If the dot product is negative then it's assumed that the resulting face
1772+
// is twisted, hence the 2nd edge is reversed.
1773+
gp_Vec v1(p1, p2);
1774+
gp_Vec v2(p1, p3);
1775+
gp_Vec n1 = v1.Crossed(v2);
1776+
1777+
gp_Vec v3(p4, p3);
1778+
gp_Vec v4(p4, p2);
1779+
gp_Vec n2 = v3.Crossed(v4);
1780+
1781+
if (n1.Dot(n2) < 0) {
1782+
S2.setShape(S2.getShape().Reversed(), false);
1783+
}
1784+
}
1785+
}
1786+
else if (orientation == 2) {
1787+
// Reverse
1788+
S2.setShape(S2.getShape().Reversed(), false);
1789+
}
1790+
1791+
TopoDS_Shape ruledShape;
1792+
if (!isWire) {
1793+
ruledShape = BRepFill::Face(TopoDS::Edge(S1.getShape()), TopoDS::Edge(S2.getShape()));
1794+
}
1795+
else {
1796+
ruledShape = BRepFill::Shell(TopoDS::Wire(S1.getShape()), TopoDS::Wire(S2.getShape()));
1797+
}
1798+
1799+
// Both BRepFill::Face() and Shell() modifies the original input edges
1800+
// without any API to provide relationship to the output edges. So we have
1801+
// to use searchSubShape() to build the relationship by ourselves.
1802+
1803+
TopoShape res(ruledShape.Located(TopLoc_Location()));
1804+
std::vector<TopoShape> edges;
1805+
for (const auto& c : curves) {
1806+
for (const auto& e : c.getSubTopoShapes(TopAbs_EDGE)) {
1807+
auto found = res.findSubShapesWithSharedVertex(e);
1808+
if (found.size() > 0) {
1809+
found.front().resetElementMap(e.elementMap());
1810+
edges.push_back(found.front());
1811+
}
1812+
}
1813+
}
1814+
// Use empty mapper and let makeShapeWithElementMap name the created surface with lower elements.
1815+
return makeShapeWithElementMap(res.getShape(), Mapper(), edges, op);
1816+
}
16561817

16571818
TopoShape& TopoShape::makeElementCompound(const std::vector<TopoShape>& shapes,
16581819
const char* op,

tests/src/Mod/Part/App/TopoShapeExpansion.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -1157,5 +1157,32 @@ TEST_F(TopoShapeExpansionTest, makeElementLinearizeFace)
11571157
EXPECT_EQ(surface2.GetType(), GeomAbs_Plane);
11581158
}
11591159

1160+
TEST_F(TopoShapeExpansionTest, makeElementRuledSurfaceEdges)
1161+
{
1162+
// Arrange
1163+
auto [cube1, cube2] = CreateTwoCubes();
1164+
TopoShape cube1TS {cube1, 1L};
1165+
std::vector<TopoShape> subEdges = cube1TS.getSubTopoShapes(TopAbs_EDGE);
1166+
std::vector<TopoShape> shapes2 = {subEdges[0], subEdges[1]};
1167+
// Act
1168+
TopoShape result2 = cube1TS.makeElementRuledSurface(shapes2, 0); // TODO: direction as enum?
1169+
// Assert
1170+
EXPECT_EQ(result2.countSubElements("Wire"), 1);
1171+
EXPECT_FLOAT_EQ(getArea(result2.getShape()), 0.32953611);
1172+
}
1173+
1174+
TEST_F(TopoShapeExpansionTest, makeElementRuledSurfaceWires)
1175+
{
1176+
// Arrange
1177+
auto [cube1, cube2] = CreateTwoCubes();
1178+
TopoShape cube1TS {cube1, 1L};
1179+
std::vector<TopoShape> subWires = cube1TS.getSubTopoShapes(TopAbs_WIRE);
1180+
std::vector<TopoShape> shapes = {subWires[0], subWires[1]};
1181+
// Act
1182+
TopoShape result = cube1TS.makeElementRuledSurface(shapes, 0); // TODO: direction as enum?
1183+
// Assert
1184+
EXPECT_EQ(result.countSubElements("Wire"), 4);
1185+
EXPECT_FLOAT_EQ(getArea(result.getShape()), 2.023056);
1186+
}
11601187

11611188
// NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)

0 commit comments

Comments
 (0)