1
// SPDX-License-Identifier: LGPL-2.1-or-later
3
/***************************************************************************
4
* Copyright (c) 2019 Manuel Apeltauer, direkt cnc-systeme GmbH *
5
* Copyright (c) 2024 Werner Mayer <wmayer[at]users.sourceforge.net> *
7
* This file is part of FreeCAD. *
9
* FreeCAD is free software: you can redistribute it and/or modify it *
10
* under the terms of the GNU Lesser General Public License as *
11
* published by the Free Software Foundation, either version 2.1 of the *
12
* License, or (at your option) any later version. *
14
* FreeCAD is distributed in the hope that it will be useful, but *
15
* WITHOUT ANY WARRANTY; without even the implied warranty of *
16
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
17
* Lesser General Public License for more details. *
19
* You should have received a copy of the GNU Lesser General Public *
20
* License along with FreeCAD. If not, see *
21
* <https://www.gnu.org/licenses/>. *
23
**************************************************************************/
25
#include "PreCompiled.h"
27
#include <BRep_Tool.hxx>
28
#include <BRepBuilderAPI_MakeEdge.hxx>
29
#include <BRepBuilderAPI_MakeFace.hxx>
30
#include <BRepBuilderAPI_Transform.hxx>
31
#include <BRepCheck_Analyzer.hxx>
32
#include <BRepExtrema_DistShapeShape.hxx>
33
#include <BRepPrimAPI_MakePrism.hxx>
34
#include <BRepProj_Projection.hxx>
35
#include <Precision.hxx>
36
#include <ShapeAnalysis.hxx>
37
#include <ShapeAnalysis_FreeBounds.hxx>
38
#include <ShapeFix_Face.hxx>
39
#include <ShapeFix_Wire.hxx>
40
#include <ShapeFix_Wireframe.hxx>
41
#include <Standard_Failure.hxx>
42
#include <TopExp_Explorer.hxx>
44
#include <TopoDS_Builder.hxx>
48
#include "FeatureProjectOnSurface.h"
49
#include <Base/Exception.h>
54
PROPERTY_SOURCE(Part::ProjectOnSurface, Part::Feature)
55
static std::array<const char*, 4> modes = {"All", "Faces", "Edges", nullptr}; // NOLINT
57
ProjectOnSurface::ProjectOnSurface()
59
ADD_PROPERTY_TYPE(Mode,(0L), "Projection", App::Prop_None, "Projection mode");
60
Mode.setEnums(modes.data());
61
ADD_PROPERTY_TYPE(Height,(0.0), "Projection", App::Prop_None, "Extrusion height");
62
ADD_PROPERTY_TYPE(Offset,(0.0), "Projection", App::Prop_None, "Offset of solid");
63
ADD_PROPERTY_TYPE(Direction,(Base::Vector3d(0, 0, 1)), "Projection", App::Prop_None, "Direction of projection");
64
ADD_PROPERTY_TYPE(SupportFace,(nullptr), "Projection", App::Prop_None, "Support faceo");
65
ADD_PROPERTY_TYPE(Projection,(nullptr), "Projection", App::Prop_None, "Shapes to project onto support face");
68
App::DocumentObjectExecReturn* ProjectOnSurface::execute()
72
return App::DocumentObject::StdReturn;
74
catch (const Standard_Failure& error) {
75
throw Base::ValueError(error.GetMessageString());
79
void ProjectOnSurface::tryExecute()
81
TopoDS_Face supportFace = getSupportFace();
83
std::vector<TopoDS_Shape> shapes = getProjectionShapes();
84
const auto& vec = Direction.getValue();
85
gp_Dir dir(vec.x, vec.y, vec.z);
87
std::vector<TopoDS_Shape> results;
88
for (const auto& shape : shapes) {
89
auto shapes = createProjectedWire(shape, supportFace, dir);
90
results.insert(results.end(), shapes.begin(), shapes.end());
93
results = filterShapes(results);
94
auto currentPlacement = Placement.getValue();
95
Shape.setValue(createCompound(results));
96
Placement.setValue(currentPlacement);
99
TopoDS_Face ProjectOnSurface::getSupportFace() const
101
auto support = SupportFace.getValue<Part::Feature*>();
103
throw Base::ValueError("No support face specified");
106
std::vector<std::string> subStrings = SupportFace.getSubValues();
107
if (subStrings.size() != 1) {
108
throw Base::ValueError("Expect exactly one support face");
111
auto topoSupport = Feature::getTopoShape(support, subStrings[0].c_str(), true);
112
return TopoDS::Face(topoSupport.getShape());
115
std::vector<TopoDS_Shape> ProjectOnSurface::getProjectionShapes() const
117
std::vector<TopoDS_Shape> shapes;
118
auto objects = Projection.getValues();
119
auto subvalues = Projection.getSubValues();
120
if (objects.size() != subvalues.size()) {
121
throw Base::ValueError("Number of objects and sub-names differ");
124
for (std::size_t index = 0; index < objects.size(); index++) {
125
auto topoSupport = Feature::getTopoShape(objects[index], subvalues[index].c_str(), true);
126
shapes.push_back(topoSupport.getShape());
132
std::vector<TopoDS_Shape>
133
ProjectOnSurface::filterShapes(const std::vector<TopoDS_Shape>& shapes) const
135
std::vector<TopoDS_Shape> filtered;
136
const char* mode = Mode.getValueAsString();
137
if (strcmp(mode, "All") == 0) {
138
for (const auto& it : shapes) {
140
filtered.push_back(it);
144
else if (strcmp(mode, "Faces") == 0) {
145
for (const auto& it : shapes) {
146
if (!it.IsNull() && it.ShapeType() == TopAbs_FACE) {
147
filtered.push_back(it);
151
else if (strcmp(mode, "Edges") == 0) {
152
for (const auto& it : shapes) {
156
if (it.ShapeType() == TopAbs_EDGE || it.ShapeType() == TopAbs_WIRE) {
157
filtered.push_back(it);
159
else if (it.ShapeType() == TopAbs_FACE) {
160
auto wires = getWires(TopoDS::Face(it));
161
for (const auto& jt : wires) {
162
filtered.push_back(jt);
171
TopoDS_Shape ProjectOnSurface::createCompound(const std::vector<TopoDS_Shape>& shapes)
173
TopLoc_Location loc = getOffsetPlacement();
174
bool isIdentity = loc.IsIdentity();
175
TopoDS_Compound aCompound;
176
if (!shapes.empty()) {
177
TopoDS_Builder aBuilder;
178
aBuilder.MakeCompound(aCompound);
179
for (const auto& it : shapes) {
181
aBuilder.Add(aCompound, it);
184
aBuilder.Add(aCompound, it.Moved(loc));
188
return {std::move(aCompound)};
191
std::vector<TopoDS_Shape> ProjectOnSurface::createProjectedWire(const TopoDS_Shape& shape,
192
const TopoDS_Face& supportFace,
195
if (shape.IsNull()) {
198
if (shape.ShapeType() == TopAbs_FACE) {
199
auto wires = projectFace(TopoDS::Face(shape), supportFace, dir);
200
auto face = createFaceFromWire(wires, supportFace);
201
auto face_or_solid = createSolidIfHeight(face);
202
if (!face_or_solid.IsNull()) {
203
return {face_or_solid};
205
if (!face.IsNull()) {
211
if (shape.ShapeType() == TopAbs_WIRE || shape.ShapeType() == TopAbs_EDGE) {
212
return projectWire(shape, supportFace, dir);
218
TopoDS_Face ProjectOnSurface::createFaceFromWire(const std::vector<TopoDS_Shape>& wires,
219
const TopoDS_Face& supportFace) const
225
std::vector<TopoDS_Wire> wiresInParametricSpace = createWiresFromWires(wires, supportFace);
226
return createFaceFromParametricWire(wiresInParametricSpace, supportFace);
230
ProjectOnSurface::createFaceFromParametricWire(const std::vector<TopoDS_Wire>& wires,
231
const TopoDS_Face& supportFace) const
233
auto surface = BRep_Tool::Surface(supportFace);
235
// try to create a face from the wires
236
// the first wire is the otherwise
237
// the following wires are the inside wires
238
BRepBuilderAPI_MakeFace faceMaker;
240
for (const auto& wire : wires) {
243
// change the wire direction, otherwise no face is created
244
auto currentWire = TopoDS::Wire(wire.Reversed());
245
if (supportFace.Orientation() == TopAbs_REVERSED) {
248
faceMaker = BRepBuilderAPI_MakeFace(surface, currentWire);
249
ShapeFix_Face fix(faceMaker.Face());
251
auto aFace = fix.Face();
252
BRepCheck_Analyzer aChecker(aFace);
253
if (!aChecker.IsValid()) {
254
faceMaker = BRepBuilderAPI_MakeFace(surface, TopoDS::Wire(currentWire.Reversed()));
258
// make a copy of the current face maker
259
// if the face fails just try again with the copy
260
TopoDS_Face tempCopy = BRepBuilderAPI_MakeFace(faceMaker.Face()).Face();
261
faceMaker.Add(TopoDS::Wire(wire.Reversed()));
262
ShapeFix_Face fix(faceMaker.Face());
264
auto aFace = fix.Face();
265
BRepCheck_Analyzer aChecker(aFace);
266
if (!aChecker.IsValid()) {
267
faceMaker = BRepBuilderAPI_MakeFace(tempCopy);
268
faceMaker.Add(TopoDS::Wire(wire));
272
// auto doneFlag = faceMaker.IsDone();
273
// auto error = faceMaker.Error();
274
return faceMaker.Face();
277
std::vector<TopoDS_Wire>
278
ProjectOnSurface::createWiresFromWires(const std::vector<TopoDS_Shape>& wires,
279
const TopoDS_Face& supportFace) const
281
auto surface = BRep_Tool::Surface(supportFace);
283
// create a wire of all edges in parametric space on the surface of the face to
285
// --> otherwise BRepBuilderAPI_MakeFace can not make a face from the wire!
286
std::vector<TopoDS_Wire> wiresInParametricSpace;
287
for (const auto& wire : wires) {
288
std::vector<TopoDS_Shape> edges;
289
for (TopExp_Explorer xp(wire, TopAbs_EDGE); xp.More(); xp.Next()) {
290
edges.push_back(TopoDS::Edge(xp.Current()));
296
std::vector<TopoDS_Edge> edgesInParametricSpace;
297
for (const auto& edge : edges) {
298
Standard_Real first {};
299
Standard_Real last {};
300
auto currentCurve = BRep_Tool::CurveOnSurface(TopoDS::Edge(edge),
308
BRepBuilderAPI_MakeEdge mkEdge(currentCurve, surface, first, last);
309
auto edgeInParametricSpace = mkEdge.Edge();
310
edgesInParametricSpace.push_back(edgeInParametricSpace);
313
auto aWire = fixWire(edgesInParametricSpace, supportFace);
314
wiresInParametricSpace.push_back(aWire);
317
return wiresInParametricSpace;
320
TopoDS_Shape ProjectOnSurface::createSolidIfHeight(const TopoDS_Face& face) const
325
double height = Height.getValue();
326
if (height < Precision::Confusion() || Mode.getValue() != 0L) {
330
const auto& vec = Direction.getValue();
331
gp_Vec directionToExtrude(vec.x, vec.y, vec.z);
332
directionToExtrude.Reverse();
333
directionToExtrude.Multiply(height);
335
BRepPrimAPI_MakePrism extrude(face, directionToExtrude);
336
return extrude.Shape();
339
std::vector<TopoDS_Wire> ProjectOnSurface::getWires(const TopoDS_Face& face) const
341
std::vector<TopoDS_Wire> wires;
342
auto outerWire = ShapeAnalysis::OuterWire(face);
343
wires.push_back(outerWire);
344
for (TopExp_Explorer xp(face, TopAbs_WIRE); xp.More(); xp.Next()) {
345
auto currentWire = TopoDS::Wire(xp.Current());
346
if (!currentWire.IsSame(outerWire)) {
347
wires.push_back(currentWire);
354
TopoDS_Wire ProjectOnSurface::fixWire(const TopoDS_Shape& shape,
355
const TopoDS_Face& supportFace) const
357
std::vector<TopoDS_Edge> edges;
358
for (TopExp_Explorer xp(shape, TopAbs_EDGE); xp.More(); xp.Next()) {
359
edges.push_back(TopoDS::Edge(xp.Current()));
361
return fixWire(edges, supportFace);
364
TopoDS_Wire ProjectOnSurface::fixWire(const std::vector<TopoDS_Edge>& edges,
365
const TopoDS_Face& supportFace) const
367
// try to sort and heal all wires
368
// if the wires are not clean making a face will fail!
369
ShapeAnalysis_FreeBounds shapeAnalyzer;
370
Handle(TopTools_HSequenceOfShape) shapeList = new TopTools_HSequenceOfShape;
371
Handle(TopTools_HSequenceOfShape) aWireHandle;
372
Handle(TopTools_HSequenceOfShape) aWireWireHandle;
374
for (const auto& it : edges) {
375
shapeList->Append(it);
378
const double tolerance = 0.0001;
379
ShapeAnalysis_FreeBounds::ConnectEdgesToWires(shapeList, tolerance, false, aWireHandle);
380
ShapeAnalysis_FreeBounds::ConnectWiresToWires(aWireHandle, tolerance, false, aWireWireHandle);
381
if (!aWireWireHandle) {
384
for (auto it = 1; it <= aWireWireHandle->Length(); ++it) {
385
auto aShape = TopoDS::Wire(aWireWireHandle->Value(it));
386
ShapeFix_Wire aWireRepair(aShape, supportFace, tolerance);
387
aWireRepair.FixAddCurve3dMode() = 1;
388
aWireRepair.FixAddPCurveMode() = 1;
389
aWireRepair.Perform();
391
ShapeFix_Wireframe aWireFramFix(aWireRepair.Wire());
392
aWireFramFix.FixWireGaps();
393
aWireFramFix.FixSmallEdges();
394
return TopoDS::Wire(aWireFramFix.Shape());
400
TopoDS_Wire getProjectedWire(BRepProj_Projection& projection, const TopoDS_Shape& reference)
402
double minDistance = std::numeric_limits<double>::max();
403
TopoDS_Wire wireToTake;
404
for (; projection.More(); projection.Next()) {
405
auto it = projection.Current();
406
BRepExtrema_DistShapeShape distanceMeasure(it, reference);
407
distanceMeasure.Perform();
408
auto currentDistance = distanceMeasure.Value();
409
if (currentDistance > minDistance) {
413
minDistance = currentDistance;
420
std::vector<TopoDS_Shape> ProjectOnSurface::projectFace(const TopoDS_Face& face,
421
const TopoDS_Face& supportFace,
424
std::vector<TopoDS_Shape> shapes;
425
std::vector<TopoDS_Wire> wires = getWires(face);
426
for (const auto& wire : wires) {
427
BRepProj_Projection aProjection(wire, supportFace, dir);
428
TopoDS_Wire wireToTake = getProjectedWire(aProjection, face);
429
auto aWire = fixWire(wireToTake, supportFace);
430
shapes.push_back(aWire);
436
std::vector<TopoDS_Shape> ProjectOnSurface::projectWire(const TopoDS_Shape& wire,
437
const TopoDS_Face& supportFace,
440
std::vector<TopoDS_Shape> shapes;
441
BRepProj_Projection aProjection(wire, supportFace, dir);
442
TopoDS_Wire wireToTake = getProjectedWire(aProjection, wire);
443
for (TopExp_Explorer xp(wireToTake, TopAbs_EDGE); xp.More(); xp.Next()) {
444
shapes.push_back(TopoDS::Edge(xp.Current()));
450
TopLoc_Location ProjectOnSurface::getOffsetPlacement() const
452
double offset = Offset.getValue();
457
auto vec = Direction.getValue();
459
vec.Scale(offset, offset, offset);
462
gp_Trsf move = TopoShape::convert(mat);
463
return TopLoc_Location(move);
466
const char* ProjectOnSurface::getViewProviderName() const
468
return "PartGui::ViewProviderProjectOnSurface";