From 070300f3a76e921ae1d141341f7e517ada90c614 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 25 Mar 2024 17:25:36 +0100 Subject: [PATCH] Part: make projection on surface parametric --- src/Mod/Part/App/AppPart.cpp | 2 + src/Mod/Part/App/CMakeLists.txt | 2 + src/Mod/Part/App/FeatureProjectOnSurface.cpp | 469 +++++++++++++++++++ src/Mod/Part/App/FeatureProjectOnSurface.h | 94 ++++ 4 files changed, 567 insertions(+) create mode 100644 src/Mod/Part/App/FeatureProjectOnSurface.cpp create mode 100644 src/Mod/Part/App/FeatureProjectOnSurface.h diff --git a/src/Mod/Part/App/AppPart.cpp b/src/Mod/Part/App/AppPart.cpp index 4190208200..cce743971c 100644 --- a/src/Mod/Part/App/AppPart.cpp +++ b/src/Mod/Part/App/AppPart.cpp @@ -85,6 +85,7 @@ #include "FeaturePartPolygon.h" #include "FeaturePartSection.h" #include "FeaturePartSpline.h" +#include "FeatureProjectOnSurface.h" #include "FeatureRevolution.h" #include "Geometry.h" #include "Geometry2d.h" @@ -444,6 +445,7 @@ PyMOD_INIT_FUNC(Part) Part::Extrusion ::init(); Part::Scale ::init(); Part::Revolution ::init(); + Part::ProjectOnSurface ::init(); Part::Mirroring ::init(); Part::ImportStep ::init(); Part::ImportIges ::init(); diff --git a/src/Mod/Part/App/CMakeLists.txt b/src/Mod/Part/App/CMakeLists.txt index ab4cd149a9..f12e9c631c 100644 --- a/src/Mod/Part/App/CMakeLists.txt +++ b/src/Mod/Part/App/CMakeLists.txt @@ -176,6 +176,8 @@ SET(Features_SRCS FeaturePartSection.h FeaturePartSpline.cpp FeaturePartSpline.h + FeatureProjectOnSurface.cpp + FeatureProjectOnSurface.h FeatureChamfer.cpp FeatureChamfer.h FeatureCompound.cpp diff --git a/src/Mod/Part/App/FeatureProjectOnSurface.cpp b/src/Mod/Part/App/FeatureProjectOnSurface.cpp new file mode 100644 index 0000000000..99c182f893 --- /dev/null +++ b/src/Mod/Part/App/FeatureProjectOnSurface.cpp @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2019 Manuel Apeltauer, direkt cnc-systeme GmbH * + * Copyright (c) 2024 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "FeatureProjectOnSurface.h" +#include + + +using namespace Part; + +PROPERTY_SOURCE(Part::ProjectOnSurface, Part::Feature) +static std::array modes = {"All", "Faces", "Edges", nullptr}; // NOLINT + +ProjectOnSurface::ProjectOnSurface() +{ + ADD_PROPERTY_TYPE(Mode,(0L), "Projection", App::Prop_None, "Projection mode"); + Mode.setEnums(modes.data()); + ADD_PROPERTY_TYPE(Height,(0.0), "Projection", App::Prop_None, "Extrusion height"); + ADD_PROPERTY_TYPE(Offset,(0.0), "Projection", App::Prop_None, "Offset of solid"); + ADD_PROPERTY_TYPE(Direction,(Base::Vector3d(0, 0, 1)), "Projection", App::Prop_None, "Direction of projection"); + ADD_PROPERTY_TYPE(SupportFace,(nullptr), "Projection", App::Prop_None, "Support faceo"); + ADD_PROPERTY_TYPE(Projection,(nullptr), "Projection", App::Prop_None, "Shapes to project onto support face"); +} + +App::DocumentObjectExecReturn* ProjectOnSurface::execute() +{ + try { + tryExecute(); + return App::DocumentObject::StdReturn; + } + catch (const Standard_Failure& error) { + throw Base::ValueError(error.GetMessageString()); + } +} + +void ProjectOnSurface::tryExecute() +{ + TopoDS_Face supportFace = getSupportFace(); + + std::vector shapes = getProjectionShapes(); + const auto& vec = Direction.getValue(); + gp_Dir dir(vec.x, vec.y, vec.z); + + std::vector results; + for (const auto& shape : shapes) { + auto shapes = createProjectedWire(shape, supportFace, dir); + results.insert(results.end(), shapes.begin(), shapes.end()); + } + + results = filterShapes(results); + auto currentPlacement = Placement.getValue(); + Shape.setValue(createCompound(results)); + Placement.setValue(currentPlacement); +} + +TopoDS_Face ProjectOnSurface::getSupportFace() const +{ + auto support = SupportFace.getValue(); + if (!support) { + throw Base::ValueError("No support face specified"); + } + + std::vector subStrings = SupportFace.getSubValues(); + if (subStrings.size() != 1) { + throw Base::ValueError("Expect exactly one support face"); + } + + auto topoSupport = Feature::getTopoShape(support, subStrings[0].c_str(), true); + return TopoDS::Face(topoSupport.getShape()); +} + +std::vector ProjectOnSurface::getProjectionShapes() const +{ + std::vector shapes; + auto objects = Projection.getValues(); + auto subvalues = Projection.getSubValues(); + if (objects.size() != subvalues.size()) { + throw Base::ValueError("Number of objects and sub-names differ"); + } + + for (std::size_t index = 0; index < objects.size(); index++) { + auto topoSupport = Feature::getTopoShape(objects[index], subvalues[index].c_str(), true); + shapes.push_back(topoSupport.getShape()); + } + + return shapes; +} + +std::vector +ProjectOnSurface::filterShapes(const std::vector& shapes) const +{ + std::vector filtered; + const char* mode = Mode.getValueAsString(); + if (strcmp(mode, "All") == 0) { + for (const auto& it : shapes) { + if (!it.IsNull()) { + filtered.push_back(it); + } + } + } + else if (strcmp(mode, "Faces") == 0) { + for (const auto& it : shapes) { + if (!it.IsNull() && it.ShapeType() == TopAbs_FACE) { + filtered.push_back(it); + } + } + } + else if (strcmp(mode, "Edges") == 0) { + for (const auto& it : shapes) { + if (it.IsNull()) { + continue; + } + if (it.ShapeType() == TopAbs_EDGE || it.ShapeType() == TopAbs_WIRE) { + filtered.push_back(it); + } + else if (it.ShapeType() == TopAbs_FACE) { + auto wires = getWires(TopoDS::Face(it)); + for (const auto& jt : wires) { + filtered.push_back(jt); + } + } + } + } + + return filtered; +} + +TopoDS_Shape ProjectOnSurface::createCompound(const std::vector& shapes) +{ + TopLoc_Location loc = getOffsetPlacement(); + bool isIdentity = loc.IsIdentity(); + TopoDS_Compound aCompound; + if (!shapes.empty()) { + TopoDS_Builder aBuilder; + aBuilder.MakeCompound(aCompound); + for (const auto& it : shapes) { + if (isIdentity) { + aBuilder.Add(aCompound, it); + } + else { + aBuilder.Add(aCompound, it.Moved(loc)); + } + } + } + return {std::move(aCompound)}; +} + +std::vector ProjectOnSurface::createProjectedWire(const TopoDS_Shape& shape, + const TopoDS_Face& supportFace, + const gp_Dir& dir) +{ + if (shape.IsNull()) { + return {}; + } + if (shape.ShapeType() == TopAbs_FACE) { + auto wires = projectFace(TopoDS::Face(shape), supportFace, dir); + auto face = createFaceFromWire(wires, supportFace); + auto face_or_solid = createSolidIfHeight(face); + if (!face_or_solid.IsNull()) { + return {face_or_solid}; + } + if (!face.IsNull()) { + return {face}; + } + + return wires; + } + if (shape.ShapeType() == TopAbs_WIRE || shape.ShapeType() == TopAbs_EDGE) { + return projectWire(shape, supportFace, dir); + } + + return {}; +} + +TopoDS_Face ProjectOnSurface::createFaceFromWire(const std::vector& wires, + const TopoDS_Face& supportFace) const +{ + if (wires.empty()) { + return {}; + } + + std::vector wiresInParametricSpace = createWiresFromWires(wires, supportFace); + return createFaceFromParametricWire(wiresInParametricSpace, supportFace); +} + +TopoDS_Face +ProjectOnSurface::createFaceFromParametricWire(const std::vector& wires, + const TopoDS_Face& supportFace) const +{ + auto surface = BRep_Tool::Surface(supportFace); + + // try to create a face from the wires + // the first wire is the otherwise + // the following wires are the inside wires + BRepBuilderAPI_MakeFace faceMaker; + bool first = true; + for (const auto& wire : wires) { + if (first) { + first = false; + // change the wire direction, otherwise no face is created + auto currentWire = TopoDS::Wire(wire.Reversed()); + if (supportFace.Orientation() == TopAbs_REVERSED) { + currentWire = wire; + } + faceMaker = BRepBuilderAPI_MakeFace(surface, currentWire); + ShapeFix_Face fix(faceMaker.Face()); + fix.Perform(); + auto aFace = fix.Face(); + BRepCheck_Analyzer aChecker(aFace); + if (!aChecker.IsValid()) { + faceMaker = BRepBuilderAPI_MakeFace(surface, TopoDS::Wire(currentWire.Reversed())); + } + } + else { + // make a copy of the current face maker + // if the face fails just try again with the copy + TopoDS_Face tempCopy = BRepBuilderAPI_MakeFace(faceMaker.Face()).Face(); + faceMaker.Add(TopoDS::Wire(wire.Reversed())); + ShapeFix_Face fix(faceMaker.Face()); + fix.Perform(); + auto aFace = fix.Face(); + BRepCheck_Analyzer aChecker(aFace); + if (!aChecker.IsValid()) { + faceMaker = BRepBuilderAPI_MakeFace(tempCopy); + faceMaker.Add(TopoDS::Wire(wire)); + } + } + } + // auto doneFlag = faceMaker.IsDone(); + // auto error = faceMaker.Error(); + return faceMaker.Face(); +} + +std::vector +ProjectOnSurface::createWiresFromWires(const std::vector& wires, + const TopoDS_Face& supportFace) const +{ + auto surface = BRep_Tool::Surface(supportFace); + + // create a wire of all edges in parametric space on the surface of the face to + // projected + // --> otherwise BRepBuilderAPI_MakeFace can not make a face from the wire! + std::vector wiresInParametricSpace; + for (const auto& wire : wires) { + std::vector edges; + for (TopExp_Explorer xp(wire, TopAbs_EDGE); xp.More(); xp.Next()) { + edges.push_back(TopoDS::Edge(xp.Current())); + } + if (edges.empty()) { + continue; + } + + std::vector edgesInParametricSpace; + for (const auto& edge : edges) { + Standard_Real first {}; + Standard_Real last {}; + auto currentCurve = BRep_Tool::CurveOnSurface(TopoDS::Edge(edge), + supportFace, + first, + last); + if (!currentCurve) { + continue; + } + + BRepBuilderAPI_MakeEdge mkEdge(currentCurve, surface, first, last); + auto edgeInParametricSpace = mkEdge.Edge(); + edgesInParametricSpace.push_back(edgeInParametricSpace); + } + + auto aWire = fixWire(edgesInParametricSpace, supportFace); + wiresInParametricSpace.push_back(aWire); + } + + return wiresInParametricSpace; +} + +TopoDS_Shape ProjectOnSurface::createSolidIfHeight(const TopoDS_Face& face) const +{ + if (face.IsNull()) { + return face; + } + double height = Height.getValue(); + if (height < Precision::Confusion() || Mode.getValue() != 0L) { + return face; + } + + const auto& vec = Direction.getValue(); + gp_Vec directionToExtrude(vec.x, vec.y, vec.z); + directionToExtrude.Reverse(); + directionToExtrude.Multiply(height); + + BRepPrimAPI_MakePrism extrude(face, directionToExtrude); + return extrude.Shape(); +} + +std::vector ProjectOnSurface::getWires(const TopoDS_Face& face) const +{ + std::vector wires; + auto outerWire = ShapeAnalysis::OuterWire(face); + wires.push_back(outerWire); + for (TopExp_Explorer xp(face, TopAbs_WIRE); xp.More(); xp.Next()) { + auto currentWire = TopoDS::Wire(xp.Current()); + if (!currentWire.IsSame(outerWire)) { + wires.push_back(currentWire); + } + } + + return wires; +} + +TopoDS_Wire ProjectOnSurface::fixWire(const TopoDS_Shape& shape, + const TopoDS_Face& supportFace) const +{ + std::vector edges; + for (TopExp_Explorer xp(shape, TopAbs_EDGE); xp.More(); xp.Next()) { + edges.push_back(TopoDS::Edge(xp.Current())); + } + return fixWire(edges, supportFace); +} + +TopoDS_Wire ProjectOnSurface::fixWire(const std::vector& edges, + const TopoDS_Face& supportFace) const +{ + // try to sort and heal all wires + // if the wires are not clean making a face will fail! + ShapeAnalysis_FreeBounds shapeAnalyzer; + Handle(TopTools_HSequenceOfShape) shapeList = new TopTools_HSequenceOfShape; + Handle(TopTools_HSequenceOfShape) aWireHandle; + Handle(TopTools_HSequenceOfShape) aWireWireHandle; + + for (const auto& it : edges) { + shapeList->Append(it); + } + + const double tolerance = 0.0001; + ShapeAnalysis_FreeBounds::ConnectEdgesToWires(shapeList, tolerance, false, aWireHandle); + ShapeAnalysis_FreeBounds::ConnectWiresToWires(aWireHandle, tolerance, false, aWireWireHandle); + if (!aWireWireHandle) { + return {}; + } + for (auto it = 1; it <= aWireWireHandle->Length(); ++it) { + auto aShape = TopoDS::Wire(aWireWireHandle->Value(it)); + ShapeFix_Wire aWireRepair(aShape, supportFace, tolerance); + aWireRepair.FixAddCurve3dMode() = 1; + aWireRepair.FixAddPCurveMode() = 1; + aWireRepair.Perform(); + + ShapeFix_Wireframe aWireFramFix(aWireRepair.Wire()); + aWireFramFix.FixWireGaps(); + aWireFramFix.FixSmallEdges(); + return TopoDS::Wire(aWireFramFix.Shape()); + } + return {}; +} + +namespace { +TopoDS_Wire getProjectedWire(BRepProj_Projection& projection, const TopoDS_Shape& reference) +{ + double minDistance = std::numeric_limits::max(); + TopoDS_Wire wireToTake; + for (; projection.More(); projection.Next()) { + auto it = projection.Current(); + BRepExtrema_DistShapeShape distanceMeasure(it, reference); + distanceMeasure.Perform(); + auto currentDistance = distanceMeasure.Value(); + if (currentDistance > minDistance) { + continue; + } + wireToTake = it; + minDistance = currentDistance; + } + + return wireToTake; +} +} + +std::vector ProjectOnSurface::projectFace(const TopoDS_Face& face, + const TopoDS_Face& supportFace, + const gp_Dir& dir) +{ + std::vector shapes; + std::vector wires = getWires(face); + for (const auto& wire : wires) { + BRepProj_Projection aProjection(wire, supportFace, dir); + TopoDS_Wire wireToTake = getProjectedWire(aProjection, face); + auto aWire = fixWire(wireToTake, supportFace); + shapes.push_back(aWire); + } + + return shapes; +} + +std::vector ProjectOnSurface::projectWire(const TopoDS_Shape& wire, + const TopoDS_Face& supportFace, + const gp_Dir& dir) +{ + std::vector shapes; + BRepProj_Projection aProjection(wire, supportFace, dir); + TopoDS_Wire wireToTake = getProjectedWire(aProjection, wire); + for (TopExp_Explorer xp(wireToTake, TopAbs_EDGE); xp.More(); xp.Next()) { + shapes.push_back(TopoDS::Edge(xp.Current())); + } + + return shapes; +} + +TopLoc_Location ProjectOnSurface::getOffsetPlacement() const +{ + double offset = Offset.getValue(); + if (offset == 0) { + return {}; + } + + auto vec = Direction.getValue(); + vec.Normalize(); + vec.Scale(offset, offset, offset); + Base::Matrix4D mat; + mat.move(vec); + gp_Trsf move = TopoShape::convert(mat); + return TopLoc_Location(move); +} + +const char* ProjectOnSurface::getViewProviderName() const +{ + return "PartGui::ViewProviderProjectOnSurface"; +} diff --git a/src/Mod/Part/App/FeatureProjectOnSurface.h b/src/Mod/Part/App/FeatureProjectOnSurface.h new file mode 100644 index 0000000000..5d3c5e00ac --- /dev/null +++ b/src/Mod/Part/App/FeatureProjectOnSurface.h @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2019 Manuel Apeltauer, direkt cnc-systeme GmbH * + * Copyright (c) 2024 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef PART_FEATUREPROJECTONSURFACE_H +#define PART_FEATUREPROJECTONSURFACE_H + +#include "PartFeature.h" +#include +#include + + +namespace Part +{ + +class ProjectOnSurface : public Part::Feature +{ + PROPERTY_HEADER_WITH_OVERRIDE(ProjectOnSurface); + +public: + ProjectOnSurface(); + + App::PropertyEnumeration Mode; + App::PropertyLength Height; + App::PropertyDistance Offset; + App::PropertyDirection Direction; + App::PropertyLinkSub SupportFace; + App::PropertyLinkSubList Projection; + + static constexpr const char* AllMode = "All"; + static constexpr const char* FacesMode = "Faces"; + static constexpr const char* EdgesMode = "Edges"; + + /** @name methods override feature */ + //@{ + /// recalculate the feature + App::DocumentObjectExecReturn *execute() override; + const char* getViewProviderName() const override; + //@} + +private: + void tryExecute(); + TopoDS_Face getSupportFace() const; + std::vector getProjectionShapes() const; + std::vector createProjectedWire(const TopoDS_Shape& shape, + const TopoDS_Face& supportFace, + const gp_Dir& dir); + TopoDS_Face createFaceFromWire(const std::vector& wires, + const TopoDS_Face& supportFace) const; + TopoDS_Face createFaceFromParametricWire(const std::vector& wires, + const TopoDS_Face& supportFace) const; + TopoDS_Shape createSolidIfHeight(const TopoDS_Face& face) const; + std::vector createWiresFromWires(const std::vector& wires, + const TopoDS_Face& supportFace) const; + std::vector getWires(const TopoDS_Face& face) const; + std::vector projectFace(const TopoDS_Face& face, + const TopoDS_Face& supportFace, + const gp_Dir& dir); + std::vector projectWire(const TopoDS_Shape& wire, + const TopoDS_Face& supportFace, + const gp_Dir& dir); + TopoDS_Wire fixWire(const TopoDS_Shape& shape, + const TopoDS_Face& supportFace) const; + TopoDS_Wire fixWire(const std::vector& edges, + const TopoDS_Face& supportFace) const; + std::vector filterShapes(const std::vector& shapes) const; + TopoDS_Shape createCompound(const std::vector& shapes); + TopLoc_Location getOffsetPlacement() const; +}; + +} //namespace Part + + +#endif // PART_FEATUREPROJECTONSURFACE_H