Files
create/src/Mod/Part/App/Attacher.cpp

3125 lines
114 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2015 Victor Titov (DeepSOIC) <vv.titov@gmail.com> *
* *
* 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 <BRep_Tool.hxx>
#include <BRepAdaptor_Curve.hxx>
#include <BRepAdaptor_Surface.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include <BRepExtrema_DistShapeShape.hxx>
#include <BRepGProp.hxx>
#include <BRepIntCurveSurface_Inter.hxx>
#include <BRepLProp_SLProps.hxx>
#include <Geom_Line.hxx>
#include <Geom_Plane.hxx>
#include <GeomAdaptor.hxx>
#include <GeomAPI.hxx>
#include <GeomAPI_ProjectPointOnCurve.hxx>
#include <GeomAPI_ProjectPointOnSurf.hxx>
#include <GeomAPI_IntSS.hxx>
#include <GeomLib_IsPlanarSurface.hxx>
#include <gp_Ax1.hxx>
#include <gp_Dir.hxx>
#include <gp_Elips.hxx>
#include <gp_Hypr.hxx>
#include <gp_Parab.hxx>
#include <gp_Pln.hxx>
#include <gp_Pnt.hxx>
#include <gp_Circ.hxx>
#include <gp_Cylinder.hxx>
#include <GProp_GProps.hxx>
#include <GProp_PGProps.hxx>
#include <GProp_PrincipalProps.hxx>
#include <ShapeExtend_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Iterator.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopTools_HSequenceOfShape.hxx>
#include <GeomAbs_CurveType.hxx>
#include <App/Application.h>
#include <App/Document.h>
#include <App/Datums.h>
#include <Base/Console.h>
#include "Attacher.h"
#include "AttachExtension.h"
#include "Tools.h"
#include <Geometry.h>
using namespace Part;
using namespace Attacher;
// These strings are for mode list enum property.
const char* AttachEngine::eMapModeStrings[] = {
"Deactivated",
"Translate",
"ObjectXY",
"ObjectXZ",
"ObjectYZ",
"FlatFace",
"TangentPlane",
"NormalToEdge",
"FrenetNB",
"FrenetTN",
"FrenetTB",
"Concentric",
"SectionOfRevolution",
"ThreePointsPlane",
"ThreePointsNormal",
"Folding",
"ObjectX",
"ObjectY",
"ObjectZ",
"AxisOfCurvature",
"Directrix1",
"Directrix2",
"Asymptote1",
"Asymptote2",
"Tangent",
"Normal",
"Binormal",
"TangentU",
"TangentV",
"TwoPointLine",
"IntersectionLine",
"ProximityLine",
"ObjectOrigin",
"Focus1",
"Focus2",
"OnEdge",
"CenterOfCurvature",
"CenterOfMass",
"IntersectionPoint",
"Vertex",
"ProximityPoint1",
"ProximityPoint2",
"AxisOfInertia1",
"AxisOfInertia2",
"AxisOfInertia3",
"InertialCS",
"FaceNormal",
"OZX",
"OZY",
"OXY",
"OXZ",
"OYZ",
"OYX",
"ParallelPlane",
"MidPoint",
nullptr
};
namespace
{
/**
* @brief Returns a rotation aligned to the given normal vector with minimal twist.
*
* Sets the Z-axis to the given normal. The X-axis is determined by projecting
* the original X-axis (from base) onto the plane perpendicular to the normal.
* If that projection is degenerate, it falls back to projecting the original Y-axis
* or global axes to ensure a valid basis.
*/
Base::Rotation rotationAlignedToNormal(
const Base::Rotation& rotation,
Base::Vector3d normal,
const double eps = Precision::Confusion()
)
{
// If normal is invalid, just return current rotation
if (normal.Length() < eps) {
return rotation;
}
// 1) Normalize desired Z
normal.Normalize();
// 2) Original basis from base rotation
Base::Vector3d xOld = rotation.multVec(Base::Vector3d::UnitX);
Base::Vector3d yOld = rotation.multVec(Base::Vector3d::UnitY);
// 3) Project old X into plane perpendicular to new Z
Base::Vector3d xProj = xOld - normal * (xOld * normal);
if (xProj.Length() < eps) {
xProj = yOld - normal * (yOld * normal);
}
// Still degenerate? Pick any axis not parallel to Z
if (xProj.Length() < eps) {
xProj = Base::Vector3d::UnitX;
if (std::fabs(xProj * normal) > 1.0 - eps) {
xProj = Base::Vector3d::UnitY;
}
}
xProj.Normalize();
// 4) Build Y as Z × X (so it is perpendicular)
Base::Vector3d yNew = normal.Cross(xProj);
yNew.Normalize();
// 5) Build rotation matrix from (X, Y, Z) axes
Base::Matrix4D matrix;
matrix.setCol(0, xProj);
matrix.setCol(1, yNew);
matrix.setCol(2, normal);
return {matrix};
}
} // namespace
// this list must be in sync with eRefType enum.
// These strings are used only by Py interface of Attacher. Strings for use in Gui are in
// Mod/Part/Gui/AttacherTexts.cpp
const char* AttachEngine::eRefTypeStrings[] = {"Any", "Vertex", "Edge", "Face",
"Line", "Curve", "Circle", "Conic",
"Ellipse", "Parabola", "Hyperbola",
"Plane", "Sphere", "Revolve", "Cylinder",
"Torus", "Cone",
"Object", "Solid", "Wire", nullptr};
TYPESYSTEM_SOURCE_ABSTRACT(Attacher::AttachEngine, Base::BaseClass)
AttachEngine::AttachEngine() = default;
void AttachEngine::setReferences(const App::PropertyLinkSubList& references)
{
std::string docname;
std::vector<std::string> names;
for (auto obj : references.getValues()) {
if (!obj->getNameInDocument()) {
throw AttachEngineException("AttachEngine::invalid object");
}
if (docname.empty()) {
docname = obj->getDocument()->getName();
}
else if (docname != obj->getDocument()->getName()) {
throw AttachEngineException("AttachEngine::object from multiple document");
}
names.emplace_back(obj->getNameInDocument());
}
this->docName = docname;
this->objNames = std::move(names);
this->subnames.clear();
this->subnames.reserve(this->objNames.size());
this->shadowSubs.clear();
this->shadowSubs.reserve(this->objNames.size());
for (auto& shadow : references.getShadowSubs()) {
this->shadowSubs.push_back(shadow.newName);
this->subnames.push_back(shadow.oldName);
}
assert(this->objNames.size() == this->subnames.size());
}
void AttachEngine::setReferences(const std::vector<App::SubObjectT>& references)
{
std::string docname;
std::vector<std::string> names;
std::vector<std::string> subnames;
std::vector<std::string> shadowSubs;
for (auto& ref : references) {
if (!ref.getSubObject()) {
FC_THROWM(
AttachEngineException,
"AttachEngine::invalid object " << ref.getSubObjectFullName()
);
}
if (docname.empty()) {
docname = ref.getDocumentName();
}
else if (docname != ref.getDocumentName()) {
throw AttachEngineException("AttachEngine::object from multiple document");
}
names.push_back(ref.getObjectName());
subnames.push_back(ref.getSubNameNoElement() + ref.getOldElementName());
shadowSubs.push_back(ref.getSubNameNoElement() + ref.getNewElementName());
}
this->docName = docname;
this->objNames = std::move(names);
this->subnames = std::move(subnames);
this->shadowSubs = std::move(shadowSubs);
}
void AttachEngine::setUp(
const App::PropertyLinkSubList& references,
eMapMode mapMode,
bool mapReverse,
double attachParameter,
double surfU,
double surfV,
const Base::Placement& attachmentOffset
)
{
setReferences(references);
this->mapMode = mapMode;
this->mapReverse = mapReverse;
this->attachParameter = attachParameter;
this->surfU = surfU;
this->surfV = surfV;
this->attachmentOffset = attachmentOffset;
}
void AttachEngine::setUp(const AttachEngine& another)
{
this->docName = another.docName;
this->objNames = another.objNames;
this->subnames = another.subnames;
this->shadowSubs = another.shadowSubs;
this->mapMode = another.mapMode;
this->mapReverse = another.mapReverse;
this->attachParameter = another.attachParameter;
this->surfU = another.surfU;
this->surfV = another.surfV;
this->attachmentOffset = another.attachmentOffset;
}
void AttachEngine::setOffset(const Base::Placement& offset)
{
this->attachmentOffset = offset;
}
Base::Placement AttachEngine::placementFactory(
const gp_Dir& ZAxis,
gp_Vec XAxis,
gp_Pnt Origin,
gp_Pnt refOrg,
bool useRefOrg_Line,
bool useRefOrg_Plane,
bool makeYVertical,
bool makeLegacyFlatFaceOrientation,
Base::Placement* placeOfRef
) const
{
if (useRefOrg_Line) {
// move Origin to projection of refOrg onto ZAxis
gp_Vec refOrgV = gp_Vec(refOrg.XYZ());
gp_Vec OriginV = gp_Vec(Origin.XYZ());
gp_Vec ZAxisV = gp_Vec(ZAxis);
Origin = gp_Pnt((OriginV + ZAxisV * ZAxisV.Dot(refOrgV - OriginV)).XYZ());
}
if (useRefOrg_Plane) {
// move Origin to projection of refOrg onto plane (ZAxis, Origin)
gp_Vec refOrgV = gp_Vec(refOrg.XYZ());
gp_Vec OriginV = gp_Vec(Origin.XYZ());
gp_Vec ZAxisV = gp_Vec(ZAxis);
Origin = gp_Pnt((refOrgV + ZAxisV * ZAxisV.Dot(OriginV - refOrgV)).XYZ());
}
if (XAxis.Magnitude() < Precision::Confusion()) {
makeYVertical = true;
}
gp_Ax3 ax3; // OCC representation of the final placement
if (!makeYVertical) {
ax3 = gp_Ax3(Origin, ZAxis, XAxis);
}
else if (!makeLegacyFlatFaceOrientation) {
// align Y along Z, if possible
gp_Vec YAxis(0.0, 0.0, 1.0);
XAxis = YAxis.Crossed(gp_Vec(ZAxis));
if (XAxis.Magnitude() < Precision::Confusion()) {
// ZAxis is along true ZAxis
XAxis = (gp_Vec(1, 0, 0) * ZAxis.Z()).Normalized();
}
ax3 = gp_Ax3(Origin, ZAxis, XAxis);
}
else if (makeLegacyFlatFaceOrientation) {
// find out, to which axis of support Normal is closest to.
// The result will be written into pos variable (0..2 = X..Z)
if (!placeOfRef) {
throw AttachEngineException(
"AttachEngine::placementFactory: for Legacy mode, placement of the reference must "
"be supplied. Got null instead!"
);
}
Base::Placement& Place = *placeOfRef;
Base::Vector3d dX, dY, dZ; // internal axes of support object, as they are in global space
Place.getRotation().multVec(Base::Vector3d(1, 0, 0), dX);
Place.getRotation().multVec(Base::Vector3d(0, 1, 0), dY);
Place.getRotation().multVec(Base::Vector3d(0, 0, 1), dZ);
gp_Dir dirX(dX.x, dX.y, dX.z);
gp_Dir dirY(dY.x, dY.y, dY.z);
gp_Dir dirZ(dZ.x, dZ.y, dZ.z);
double cosNX = ZAxis.Dot(dirX);
double cosNY = ZAxis.Dot(dirY);
double cosNZ = ZAxis.Dot(dirZ);
std::vector<double> cosXYZ;
cosXYZ.push_back(fabs(cosNX));
cosXYZ.push_back(fabs(cosNY));
cosXYZ.push_back(fabs(cosNZ));
int pos = std::max_element(cosXYZ.begin(), cosXYZ.end()) - cosXYZ.begin();
// +X/-X
if (pos == 0) {
if (cosNX > 0) {
ax3 = gp_Ax3(Origin, ZAxis, dirY);
}
else {
ax3 = gp_Ax3(Origin, ZAxis, -dirY);
}
}
// +Y/-Y
else if (pos == 1) {
if (cosNY > 0) {
ax3 = gp_Ax3(Origin, ZAxis, -dirX);
}
else {
ax3 = gp_Ax3(Origin, ZAxis, dirX);
}
}
// +Z/-Z
else {
ax3 = gp_Ax3(Origin, ZAxis, dirX);
}
}
if (this->mapReverse) {
ax3.ZReverse();
ax3.XReverse();
}
// convert ax3 into Base::Placement
gp_Trsf Trf;
Trf.SetTransformation(ax3);
Trf.Invert();
Trf.SetScaleFactor(Standard_Real(1.0));
Base::Matrix4D mtrx;
TopoShape::convertToMatrix(Trf, mtrx);
return Base::Placement(mtrx);
}
void AttachEngine::suggestMapModes(SuggestResult& result) const
{
std::vector<eMapMode>& mlist = result.allApplicableModes;
mlist.clear();
mlist.reserve(mmDummy_NumberOfModes);
std::set<eRefType>& hints = result.nextRefTypeHint;
hints.clear();
std::map<eMapMode, refTypeStringList>& mlist_reachable = result.reachableModes;
mlist_reachable.clear();
result.message = SuggestResult::srLinkBroken;
result.bestFitMode = mmDeactivated;
std::vector<const TopoShape*> shapes;
std::vector<TopoShape> shapeStorage;
std::vector<eRefType> typeStr;
try {
readLinks(getRefObjects(), subnames, shapes, shapeStorage, typeStr);
}
catch (Base::Exception& err) {
result.references_Types = typeStr;
result.message = SuggestResult::srLinkBroken;
result.error.Exception::operator=(err);
return;
}
result.references_Types = typeStr;
// search valid modes.
int bestMatchScore = -1;
result.message = SuggestResult::srNoModesFit;
for (std::size_t iMode = 0; iMode < this->modeRefTypes.size(); ++iMode) {
if (!this->modeEnabled[iMode]) {
continue;
}
const refTypeStringList& listStrings = modeRefTypes[iMode];
for (const auto& str : listStrings) {
int score = 1; //-1 = topo incompatible, 0 = topo compatible, geom incompatible; 1+ =
// compatible (the higher - the more specific is the mode for the support)
for (std::size_t iChr = 0; iChr < str.size() && iChr < typeStr.size(); ++iChr) {
int match = AttachEngine::isShapeOfType(typeStr[iChr], str[iChr]);
switch (match) {
case -1:
score = -1;
break;
case 0:
score = 0;
break;
case 1:
// keep score
break;
default: // 2 and above
if (score > 0) {
score += match;
}
break;
}
}
if (score > 0 && str.size() > typeStr.size()) {
// mode does not fit, but adding more references will make this mode fit.
hints.insert(str[typeStr.size()]);
// build string of references to be added to fit this mode
refTypeString extraRefs;
extraRefs.resize(str.size() - typeStr.size());
for (std::size_t iChr = typeStr.size(); iChr < str.size(); iChr++) {
extraRefs[iChr - typeStr.size()] = str[iChr];
}
// add reachable mode
auto it_r = mlist_reachable.find(eMapMode(iMode));
if (it_r == mlist_reachable.end()) {
it_r = mlist_reachable
.insert(
std::pair<eMapMode, refTypeStringList>(
eMapMode(iMode),
refTypeStringList()
)
)
.first;
}
refTypeStringList& list = it_r->second;
list.push_back(extraRefs);
}
// size check is last, because we needed to collect hints
if (str.size() != typeStr.size()) {
score = -1;
}
if (score > -1) { // still output a best match, even if it is not completely compatible
if (score > bestMatchScore) {
bestMatchScore = score;
result.bestFitMode = eMapMode(iMode);
result.message = score > 0 ? SuggestResult::srOK
: SuggestResult::srIncompatibleGeometry;
}
}
if (score > 0) {
if (mlist.empty()) {
mlist.push_back(eMapMode(iMode));
}
else if (mlist.back() != eMapMode(iMode)) {
mlist.push_back(eMapMode(iMode));
}
}
}
}
}
void AttachEngine::EnableAllSupportedModes()
{
this->modeEnabled.resize(mmDummy_NumberOfModes, false);
assert(modeRefTypes.size() > 0);
for (std::size_t i = 0; i < this->modeEnabled.size(); i++) {
modeEnabled[i] = !modeRefTypes[i].empty();
}
}
eRefType AttachEngine::getShapeType(const TopoDS_Shape& sh)
{
if (sh.IsNull()) {
return rtAnything;
}
switch (sh.ShapeType()) {
case TopAbs_SHAPE:
return rtAnything; // note: there's no rtPart detection here - not enough data!
break;
case TopAbs_SOLID:
return rtSolid;
break;
case TopAbs_COMPOUND: {
const TopoDS_Compound& cmpd = TopoDS::Compound(sh);
TopoDS_Iterator it(cmpd, Standard_False, Standard_False); // don't mess with placements,
// to hopefully increase speed
if (!it.More()) { // empty compound
return rtAnything;
}
const TopoDS_Shape& sh1 = it.Value();
it.Next();
if (it.More()) {
// more than one object, a true compound
return rtAnything;
}
else {
// just one object, let's take a look inside
return getShapeType(sh1);
}
} break;
case TopAbs_COMPSOLID:
case TopAbs_SHELL:
return rtAnything;
break;
case TopAbs_FACE: {
const TopoDS_Face& f = TopoDS::Face(sh);
BRepAdaptor_Surface surf(f, /*restriction=*/Standard_False);
switch (surf.GetType()) {
case GeomAbs_Plane:
return rtFlatFace;
case GeomAbs_Cylinder:
return rtCylindricalFace;
case GeomAbs_Cone:
return rtConicalFace;
case GeomAbs_Sphere:
return rtSphericalFace;
case GeomAbs_Torus:
return rtToroidalFace;
case GeomAbs_BezierSurface:
break;
case GeomAbs_BSplineSurface:
break;
case GeomAbs_SurfaceOfRevolution:
return rtSurfaceRev;
case GeomAbs_SurfaceOfExtrusion:
break;
case GeomAbs_OffsetSurface:
break;
case GeomAbs_OtherSurface:
break;
}
return rtFace;
} break;
case TopAbs_EDGE: {
const TopoDS_Edge& e = TopoDS::Edge(sh);
BRepAdaptor_Curve crv(e);
switch (crv.GetType()) {
case GeomAbs_Line:
return rtLine;
case GeomAbs_Circle:
return rtCircle;
case GeomAbs_Ellipse:
return rtEllipse;
case GeomAbs_Hyperbola:
return rtHyperbola;
case GeomAbs_Parabola:
return rtParabola;
case GeomAbs_BezierCurve:
case GeomAbs_BSplineCurve:
case GeomAbs_OtherCurve:
case GeomAbs_OffsetCurve:
return rtCurve;
}
} break;
case TopAbs_WIRE:
return rtWire;
case TopAbs_VERTEX:
return rtVertex;
default:
throw AttachEngineException(
"AttachEngine::getShapeType: unexpected TopoDS_Shape::ShapeType"
);
} // switch shapetype
return rtAnything; // shouldn't happen, it's here to shut up compiler warning
}
eRefType AttachEngine::getShapeType(const App::DocumentObject* obj, const std::string& subshape)
{
App::PropertyLinkSubList tmpLink;
// const_cast is worth here, to keep obj argument const. We are not going to write anything to
// obj through this temporary link.
tmpLink.setValue(const_cast<App::DocumentObject*>(obj), subshape.c_str());
std::vector<const TopoShape*> shapes;
std::vector<TopoShape> copiedShapeStorage;
std::vector<eRefType> types;
readLinks(tmpLink.getValues(), tmpLink.getSubValues(), shapes, copiedShapeStorage, types);
assert(types.size() == 1);
return types[0];
}
eRefType AttachEngine::downgradeType(eRefType type)
{
// get rid of hasplacement flags, to simplify the rest
type = eRefType(type & (rtFlagHasPlacement - 1));
// FIXME: reintroduce the flag when returning a value.
switch (type) {
case rtVertex:
case rtEdge:
case rtFace:
return rtAnything;
case rtAnything:
return rtAnything;
case rtLine:
case rtCurve:
return rtEdge;
case rtConic:
case rtCircle:
return rtCurve;
case rtEllipse:
case rtParabola:
case rtHyperbola:
return rtConic;
case rtFlatFace:
case rtSphericalFace:
case rtSurfaceRev:
return rtFace;
case rtCylindricalFace:
case rtToroidalFace:
case rtConicalFace:
return rtSurfaceRev;
case rtSolid:
case rtWire:
return rtPart;
case rtPart:
return rtAnything;
default:
throw AttachEngineException("AttachEngine::downgradeType: unknown type");
}
}
int AttachEngine::getTypeRank(eRefType type)
{
// get rid of hasplacement flags, to simplify the rest
type = eRefType(type & (rtFlagHasPlacement - 1));
int rank = 0;
while (type != rtAnything) {
type = downgradeType(type);
rank++;
assert(rank < 8); // downgrading never yields rtAnything, something's wrong with downgrader.
}
return rank;
}
int AttachEngine::isShapeOfType(eRefType shapeType, eRefType requirement)
{
// first up, check for hasplacement flag
if (requirement & rtFlagHasPlacement) {
if (!(shapeType & rtFlagHasPlacement)) {
return -1;
}
}
// get rid of hasplacement flags, to simplify the rest
shapeType = eRefType(shapeType & (rtFlagHasPlacement - 1));
requirement = eRefType(requirement & (rtFlagHasPlacement - 1));
if (requirement == rtAnything) {
return 1;
}
int reqRank = getTypeRank(requirement);
// test for valid match
eRefType shDeg = shapeType;
while (shDeg != rtAnything) {
if (shDeg == requirement) {
return reqRank;
}
shDeg = downgradeType(shDeg);
}
// test for slightly invalid match (e.g. requirement==line, shapeType == curve)
requirement = downgradeType(requirement);
if (requirement != rtAnything) {
eRefType shDeg = shapeType;
while (shDeg != rtAnything) {
if (shDeg == requirement) {
return 0;
}
shDeg = downgradeType(shDeg);
}
}
// complete mismatch!
return -1;
}
std::string AttachEngine::getModeName(eMapMode mmode)
{
if (mmode < 0 || mmode >= mmDummy_NumberOfModes) {
throw AttachEngineException("AttachEngine::getModeName: Attachment Mode index is out of range");
}
return {AttachEngine::eMapModeStrings[mmode]};
}
eMapMode AttachEngine::getModeByName(const std::string& modeName)
{
for (int mmode = 0; mmode < mmDummy_NumberOfModes; mmode++) {
if (strcmp(eMapModeStrings[mmode], modeName.c_str()) == 0) {
return eMapMode(mmode);
}
}
std::stringstream errMsg;
errMsg << "AttachEngine::getModeByName: mode with this name doesn't exist: " << modeName;
throw AttachEngineException(errMsg.str());
}
std::string AttachEngine::getRefTypeName(eRefType shapeType)
{
eRefType flagless = eRefType(shapeType & 0xFF);
if (flagless < 0 || flagless >= rtDummy_numberOfShapeTypes) {
throw AttachEngineException("eRefType value is out of range");
}
std::string result = std::string(eRefTypeStrings[flagless]);
if (shapeType & rtFlagHasPlacement) {
result.append("|Placement");
}
return result;
}
eRefType AttachEngine::getRefTypeByName(const std::string& typeName)
{
std::string flagless;
std::string flags;
size_t seppos = typeName.find('|');
flagless = typeName.substr(0, seppos);
if (seppos != std::string::npos) {
flags = typeName.substr(seppos + 1);
}
for (int irt = 0; irt < rtDummy_numberOfShapeTypes; irt++) {
if (strcmp(flagless.c_str(), eRefTypeStrings[irt]) == 0) {
if (strcmp("Placement", flags.c_str()) == 0) {
return eRefType(irt | rtFlagHasPlacement);
}
else if (flags.length() == 0) {
return eRefType(irt);
}
else {
std::stringstream errmsg;
errmsg << "RefType flag not recognized: " << flags;
throw AttachEngineException(errmsg.str());
}
}
}
std::stringstream errmsg;
errmsg << "RefType not recognized: " << typeName;
throw AttachEngineException(errmsg.str());
}
GProp_GProps AttachEngine::getInertialPropsOfShape(const std::vector<const TopoShape*>& shapes)
{
// explode compounds
TopTools_HSequenceOfShape totalSeq;
for (auto tSh : shapes) {
auto pSh = tSh->getShape();
ShapeExtend_Explorer xp;
totalSeq.Append(xp.SeqFromCompound(pSh, /*recursive=*/true));
}
if (totalSeq.Length() == 0) {
throw AttachEngineException("AttachEngine::getInertialPropsOfShape: no geometry provided");
}
const TopoDS_Shape& sh0 = totalSeq.Value(1);
switch (sh0.ShapeType()) {
case TopAbs_VERTEX: {
GProp_PGProps gpr;
for (int i = 0; i < totalSeq.Length(); i++) {
const TopoDS_Shape& sh = totalSeq.Value(i + 1);
if (sh.ShapeType() != TopAbs_VERTEX) {
throw AttachEngineException(
"AttachEngine::getInertialPropsOfShape: provided shapes are incompatible "
"(not only vertices)"
);
}
gpr.AddPoint(BRep_Tool::Pnt(TopoDS::Vertex(sh)));
}
return gpr;
} break;
case TopAbs_EDGE:
case TopAbs_WIRE: {
GProp_GProps gpr_acc;
GProp_GProps gpr;
for (int i = 0; i < totalSeq.Length(); i++) {
const TopoDS_Shape& sh = totalSeq.Value(i + 1);
if (sh.ShapeType() != TopAbs_EDGE && sh.ShapeType() != TopAbs_WIRE) {
throw AttachEngineException(
"AttachEngine::getInertialPropsOfShape: provided shapes are incompatible "
"(not only edges/wires)"
);
}
if (sh.Infinite()) {
throw AttachEngineException(
"AttachEngine::getInertialPropsOfShape: infinite shape provided"
);
}
BRepGProp::LinearProperties(sh, gpr);
gpr_acc.Add(gpr);
}
return gpr_acc;
} break;
case TopAbs_FACE:
case TopAbs_SHELL: {
GProp_GProps gpr_acc;
GProp_GProps gpr;
for (int i = 0; i < totalSeq.Length(); i++) {
const TopoDS_Shape& sh = totalSeq.Value(i + 1);
if (sh.ShapeType() != TopAbs_FACE && sh.ShapeType() != TopAbs_SHELL) {
throw AttachEngineException(
"AttachEngine::getInertialPropsOfShape: provided shapes are incompatible "
"(not only faces/shells)"
);
}
if (sh.Infinite()) {
throw AttachEngineException(
"AttachEngine::getInertialPropsOfShape: infinite shape provided"
);
}
BRepGProp::SurfaceProperties(sh, gpr);
gpr_acc.Add(gpr);
}
return gpr_acc;
} break;
case TopAbs_SOLID:
case TopAbs_COMPSOLID: {
GProp_GProps gpr_acc;
GProp_GProps gpr;
for (int i = 0; i < totalSeq.Length(); i++) {
const TopoDS_Shape& sh = totalSeq.Value(i + 1);
if (sh.ShapeType() != TopAbs_SOLID && sh.ShapeType() != TopAbs_COMPSOLID) {
throw AttachEngineException(
"AttachEngine::getInertialPropsOfShape: provided shapes are incompatible "
"(not only solids/compsolids)"
);
}
if (sh.Infinite()) {
throw AttachEngineException(
"AttachEngine::getInertialPropsOfShape: infinite shape provided"
);
}
BRepGProp::VolumeProperties(sh, gpr);
gpr_acc.Add(gpr);
}
return gpr_acc;
} break;
default:
throw AttachEngineException("AttachEngine::getInertialPropsOfShape: unexpected shape type");
}
}
/*!
* \brief AttachEngine3D::readLinks
* \param shapes
* \param storage is a buffer storing what some of the pointers in shapes point to. It is needed,
* since subshapes are copied in the process (but copying a whole shape of an object can potentially
* be slow).
*/
void AttachEngine::readLinks(
const std::vector<App::DocumentObject*>& objs,
const std::vector<std::string>& subs,
std::vector<const TopoShape*>& shapes,
std::vector<TopoShape>& storage,
std::vector<eRefType>& types
)
{
storage.reserve(objs.size());
shapes.resize(objs.size());
types.resize(objs.size());
for (std::size_t i = 0; i < objs.size(); i++) {
auto geof = extractGeoFeature(objs[i]);
if (!geof) {
FC_THROWM(
AttachEngineException,
"AttachEngine3D: attached to a non App::GeoFeature '"
<< objs[i]->getNameInDocument() << "'"
);
}
auto shape = extractSubShape(objs[i], subs[i]);
if (shape.isNull()) {
if (subs[i].length() == 0) {
storage.emplace_back(TopoShape());
shapes[i] = &storage.back();
types[i] = eRefType(rtPart | rtFlagHasPlacement);
continue;
}
else {
// This case should now be unreachable because extractSubShape would have thrown
// for a missing subname. But it's good defensive programming.
FC_THROWM(
AttachEngineException,
"AttachEngine3D: null subshape " << objs[i]->getNameInDocument() << '.' << subs[i]
);
}
}
storage.emplace_back(shape);
shapes[i] = &(storage.back());
// FIXME: unpack single-child compounds here? Compounds are not used so far, so it should be
// considered later, when the need arises.
types[i] = getShapeType(shapes[i]->getShape());
if (subs[i].length() == 0) {
types[i] = eRefType(types[i] | rtFlagHasPlacement);
}
}
}
App::GeoFeature* AttachEngine::extractGeoFeature(App::DocumentObject* obj)
{
if (auto geof = dynamic_cast<App::GeoFeature*>(obj)) {
return geof;
}
auto linkedObject = obj->getLinkedObject();
if (auto linkedGeof = dynamic_cast<App::GeoFeature*>(linkedObject)) {
return linkedGeof;
}
return nullptr;
}
TopoShape AttachEngine::extractSubShape(App::DocumentObject* obj, const std::string& subname)
{
TopoShape shape;
try {
// getTopoShape support fully qualified subnames and should return shape with correct
// global placement.
shape = Feature::getTopoShape(
obj,
ShapeOption::NeedSubElement | ShapeOption::ResolveLink | ShapeOption::Transform,
subname.c_str()
);
for (;;) {
if (shape.isNull()) {
// Shape is null. Let's see if this is an acceptable null.
// (i.e., an empty object was selected, not a broken link to a sub-element).
if (subname.empty()) {
// The user selected the whole object, and it has no shape.
// This is the empty sketch or empty body case.
// Instead of throwing an error, we return a null TopoShape.
// The caller (readLinks) will then handle this null shape.
return TopoShape(); // Return a default-constructed (null) shape
}
else {
// The user specified a subname (e.g., "Edge1"), but it couldn't be found.
// This is a genuine error.
FC_THROWM(
AttachEngineException,
"AttachEngine3D: subshape not found " << obj->getNameInDocument() << '.'
<< subname
);
}
}
if (shape.shapeType() != TopAbs_COMPOUND || shape.countSubShapes(TopAbs_SHAPE) != 1) {
break;
}
// auto extract the single sub-shape from a compound
shape = shape.getSubTopoShape(TopAbs_SHAPE, 1);
}
}
catch (Standard_Failure& e) {
FC_THROWM(
AttachEngineException,
"AttachEngine3D: subshape not found " << obj->getNameInDocument() << '.' << subname
<< std::endl
<< e.GetMessageString()
);
}
catch (Base::CADKernelError& e) {
FC_THROWM(
AttachEngineException,
"AttachEngine3D: subshape not found " << obj->getNameInDocument() << '.' << subname
<< std::endl
<< e.what()
);
}
return shape;
}
void AttachEngine::throwWrongMode(eMapMode mmode)
{
std::stringstream errmsg;
if (mmode >= 0 && mmode < mmDummy_NumberOfModes) {
if (AttachEngine::eMapModeStrings[mmode]) {
errmsg << "Attachment mode " << AttachEngine::eMapModeStrings[mmode]
<< " is not implemented.";
}
else {
errmsg << "Attachment mode " << int(mmode) << " is undefined.";
}
}
else {
errmsg << "Attachment mode index (" << int(mmode) << ") is out of range.";
}
throw Base::ValueError(errmsg.str().c_str());
}
void AttachEngine::verifyReferencesAreSafe(const App::PropertyLinkSubList& references)
{
const std::vector<App::DocumentObject*> links = references.getValues();
const std::vector<App::Document*> docs = App::GetApplication().getDocuments();
for (App::DocumentObject* lnk : links) {
bool found = false;
for (App::Document* doc : docs) {
if (doc->isIn(lnk)) {
found = true;
}
}
if (!found) {
throw AttachEngineException(
"AttachEngine: verifyReferencesAreSafe: references point to deleted object."
);
}
}
}
std::vector<App::DocumentObject*> AttachEngine::getRefObjects() const
{
std::vector<App::DocumentObject*> objs;
if (objNames.empty()) {
return objs;
}
auto doc = App::GetApplication().getDocument(docName.c_str());
if (!doc) {
FC_THROWM(AttachEngineException, "AttachEngine: document '" << docName << "' not found");
}
objs.reserve(objNames.size());
for (auto& name : objNames) {
objs.push_back(doc->getObject(name.c_str()));
if (!objs.back()) {
FC_THROWM(
AttachEngineException,
"AttachEngine: object '" << docName << "#" << name << "' not found"
);
}
}
return objs;
}
Base::Placement AttachEngine::calculateAttachedPlacement(
const Base::Placement& origPlacement,
bool* subChanged
)
{
std::map<int, std::pair<std::string, std::string>> subChanges;
int i = -1;
auto objs = getRefObjects();
for (auto obj : objs) {
++i;
auto& sub = subnames[i];
obj = obj->getSubObject(sub.c_str());
auto& shadow = shadowSubs[i];
if (shadow.empty() || !Data::hasMissingElement(sub.c_str())) {
continue;
}
auto related = Part::Feature::getRelatedElements(
obj,
shadow.c_str(),
Part::HistoryTraceType::followTypeChange,
false
);
if (!related.empty()) {
auto& res = subChanges[i];
res.first = Data::ComplexGeoData::elementMapPrefix();
related.front().name.appendToBuffer(res.first);
res.second.clear();
related.front().index.appendToStringBuffer(res.second);
}
else {
std::string name = Data::oldElementName(shadow.c_str());
if (!name.empty()) {
auto& res = subChanges[i];
res.first.clear();
res.second = name;
}
else {
subnames[i] = shadow;
}
}
}
if (!subChanges.empty()) {
// In case there is topological name changes, we only auto change the
// subname if the calculated placement stays the same. If not, just
// proceed as normal, which will throw exception and catch user's
// attention.
auto subs = subnames;
for (auto& change : subChanges) {
auto [subkey, namechange] = change;
auto [_oldname, newname] = namechange;
subs[subkey] = newname;
}
auto pla = _calculateAttachedPlacement(objs, subs, origPlacement);
// check equal placement with some tolerance
if (pla.getPosition().IsEqual(origPlacement.getPosition(), Precision::Confusion())
&& pla.getRotation().isSame(origPlacement.getRotation(), Precision::Angular())) {
// Only make changes if the caller supplies 'subChanged', because
// otherwise it means the caller just want to do an immutable test.
// See AttachExtension::isAttacherActive().
if (subChanged) {
*subChanged = true;
subnames = std::move(subs);
for (auto& v : subChanges) {
shadowSubs[v.first] = v.second.first;
}
}
return pla;
}
}
return _calculateAttachedPlacement(objs, subnames, origPlacement);
}
//=================================================================================
TYPESYSTEM_SOURCE(Attacher::AttachEngine3D, Attacher::AttachEngine)
AttachEngine3D::AttachEngine3D()
{
// fill type lists for modes
modeRefTypes.resize(mmDummy_NumberOfModes);
refTypeString s;
refTypeStringList ss;
modeRefTypes[mmTranslate].push_back(cat(rtVertex));
ss.clear();
ss.push_back(cat(eRefType(rtAnything | rtFlagHasPlacement)));
ss.push_back(cat(rtConic));
modeRefTypes[mmObjectXY] = ss;
modeRefTypes[mmObjectXZ] = ss;
modeRefTypes[mmObjectYZ] = ss;
modeRefTypes[mmParallelPlane].push_back(cat(eRefType(rtFlatFace | rtFlagHasPlacement), rtVertex));
modeRefTypes[mmParallelPlane].push_back(cat(eRefType(rtAnything | rtFlagHasPlacement), rtVertex));
modeRefTypes[mmInertialCS].push_back(cat(rtAnything));
modeRefTypes[mmInertialCS].push_back(cat(rtAnything, rtAnything));
modeRefTypes[mmInertialCS].push_back(cat(rtAnything, rtAnything, rtAnything));
modeRefTypes[mmInertialCS].push_back(cat(rtAnything, rtAnything, rtAnything, rtAnything));
modeRefTypes[mmFlatFace].push_back(cat(rtFlatFace));
modeRefTypes[mmTangentPlane].push_back(cat(rtFace, rtVertex));
modeRefTypes[mmTangentPlane].push_back(cat(rtVertex, rtFace));
//---------Edge-driven
s = cat(rtEdge);
modeRefTypes[mmNormalToPath].push_back(s);
s = cat(rtCurve);
modeRefTypes[mmFrenetNB].push_back(s);
modeRefTypes[mmFrenetTN].push_back(s);
modeRefTypes[mmFrenetTB].push_back(s);
modeRefTypes[mmRevolutionSection].push_back(s);
modeRefTypes[mmConcentric].push_back(s);
s = cat(rtCircle);
modeRefTypes[mmRevolutionSection].push_back(s); // for this mode to get best score on circles
modeRefTypes[mmConcentric].push_back(s);
//-----------Edge-driven at vertex
s = cat(rtEdge, rtVertex);
modeRefTypes[mmNormalToPath].push_back(s);
s = cat(rtVertex, rtEdge);
modeRefTypes[mmNormalToPath].push_back(s);
s = cat(rtCurve, rtVertex);
modeRefTypes[mmFrenetNB].push_back(s);
modeRefTypes[mmFrenetTN].push_back(s);
modeRefTypes[mmFrenetTB].push_back(s);
modeRefTypes[mmRevolutionSection].push_back(s);
modeRefTypes[mmConcentric].push_back(s);
s = cat(rtCircle, rtVertex);
modeRefTypes[mmRevolutionSection].push_back(s); // for this mode to get best score on circles
modeRefTypes[mmConcentric].push_back(s);
s = cat(rtVertex, rtCurve);
modeRefTypes[mmFrenetNB].push_back(s);
modeRefTypes[mmFrenetTN].push_back(s);
modeRefTypes[mmFrenetTB].push_back(s);
modeRefTypes[mmRevolutionSection].push_back(s);
modeRefTypes[mmConcentric].push_back(s);
s = cat(rtVertex, rtCircle);
modeRefTypes[mmRevolutionSection].push_back(s); // for this mode to get best score on circles
modeRefTypes[mmConcentric].push_back(s);
//------------ThreePoints
s = cat(rtVertex, rtVertex, rtVertex);
modeRefTypes[mmThreePointsPlane].push_back(s);
modeRefTypes[mmThreePointsNormal].push_back(s);
s = cat(rtLine, rtVertex);
modeRefTypes[mmThreePointsPlane].push_back(s);
modeRefTypes[mmThreePointsNormal].push_back(s);
s = cat(rtVertex, rtLine);
modeRefTypes[mmThreePointsPlane].push_back(s);
modeRefTypes[mmThreePointsNormal].push_back(s);
s = cat(rtLine, rtLine);
modeRefTypes[mmThreePointsPlane].push_back(s);
modeRefTypes[mmThreePointsNormal].push_back(s);
//------------origin-axis-axis modes
for (int mmode = mmOZX; mmode <= mmOYX; ++mmode) {
modeRefTypes[mmode].push_back(cat(rtVertex, rtVertex, rtVertex));
modeRefTypes[mmode].push_back(cat(rtVertex, rtVertex, rtLine));
modeRefTypes[mmode].push_back(cat(rtVertex, rtLine, rtVertex));
modeRefTypes[mmode].push_back(cat(rtVertex, rtLine, rtLine));
modeRefTypes[mmode].push_back(cat(rtVertex, rtVertex));
modeRefTypes[mmode].push_back(cat(rtVertex, rtLine));
}
modeRefTypes[mmFolding].push_back(cat(rtLine, rtLine, rtLine, rtLine));
this->EnableAllSupportedModes();
}
AttachEngine3D* AttachEngine3D::copy() const
{
AttachEngine3D* p = new AttachEngine3D;
p->setUp(*this);
return p;
}
Base::Placement AttachEngine3D::_calculateAttachedPlacement(
const std::vector<App::DocumentObject*>& objs,
const std::vector<std::string>& subs,
const Base::Placement& origPlacement
) const
{
const eMapMode mmode = this->mapMode;
if (mmode == mmDeactivated) {
throw ExceptionCancel(); // to be handled in positionBySupport, to not do anything if
// disabled
}
std::vector<const TopoShape*> shapes;
std::vector<TopoShape> copiedShapeStorage;
std::vector<eRefType> types;
readLinks(objs, subs, shapes, copiedShapeStorage, types);
if (shapes.empty()) {
throw ExceptionCancel();
}
// common stuff for all map modes
App::DocumentObject* subObj = objs[0]->getSubObject(subs[0].c_str());
Base::Placement Place = App::GeoFeature::getGlobalPlacement(subObj, objs[0], subs[0]);
Base::Vector3d vec = Place.getPosition();
gp_Pnt refOrg = gp_Pnt(vec.x, vec.y, vec.z); // origin of linked object
// variables to derive the actual placement.
// They are to be set, depending on the mode:
// to the sketch
gp_Dir SketchNormal; // points at the user
gp_Vec SketchXAxis; // if left zero, a guess will be made
gp_Pnt SketchBasePoint; // where to put the origin of the sketch
switch (mmode) {
case mmDeactivated:
// should have been filtered out already!
break;
case mmTranslate: {
if (shapes.empty()) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: no subobjects "
"specified (need one vertex)."
);
}
const TopoDS_Shape& sh = shapes[0]->getShape();
if (sh.IsNull()) {
throw Base::ValueError("Null shape in AttachEngine3D::calculateAttachedPlacement()!");
}
if (sh.ShapeType() != TopAbs_VERTEX) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: no subobjects "
"specified (need one vertex)."
);
}
gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(sh));
Base::Placement plm = Base::Placement();
plm.setPosition(Base::Vector3d(p.X(), p.Y(), p.Z()));
plm.setPosition(plm.getPosition() + this->attachmentOffset.getPosition());
plm.setRotation(origPlacement.getRotation());
return plm;
} break;
case mmObjectXY:
case mmObjectXZ:
case mmObjectYZ:
case mmParallelPlane: {
// DeepSOIC: could have been done much more efficiently, but I'm lazy...
gp_Dir dirX, dirY, dirZ;
if (types[0] & rtFlagHasPlacement) {
Base::Vector3d dX, dY,
dZ; // internal axes of support object, as they are in global space
Place.getRotation().multVec(Base::Vector3d(1, 0, 0), dX);
Place.getRotation().multVec(Base::Vector3d(0, 1, 0), dY);
Place.getRotation().multVec(Base::Vector3d(0, 0, 1), dZ);
dirX = gp_Dir(dX.x, dX.y, dX.z);
dirY = gp_Dir(dY.x, dY.y, dY.z);
dirZ = gp_Dir(dZ.x, dZ.y, dZ.z);
SketchBasePoint
= gp_Pnt(Place.getPosition().x, Place.getPosition().y, Place.getPosition().z);
}
else if (isShapeOfType(types[0], rtConic) > 0) {
const TopoDS_Edge& e = TopoDS::Edge(shapes[0]->getShape());
BRepAdaptor_Curve adapt(e);
gp_Ax3 pos;
switch (adapt.GetType()) {
case GeomAbs_Ellipse: {
gp_Elips cc = adapt.Ellipse();
pos = gp_Ax3(cc.Position());
} break;
case GeomAbs_Hyperbola: {
gp_Hypr cc = adapt.Hyperbola();
pos = gp_Ax3(cc.Position());
} break;
case GeomAbs_Parabola: {
gp_Parab cc = adapt.Parabola();
pos = gp_Ax3(cc.Position());
} break;
default:
assert(0); // conics should have been filtered out by testing shape type in
// the above if.
}
dirX = pos.XDirection();
dirY = pos.YDirection();
dirZ = pos.Axis().Direction();
SketchBasePoint = pos.Location();
}
else {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: need either a conic section edge, "
"or a whole object for ObjectXY-like modes."
);
}
switch (mmode) {
case mmObjectXY:
SketchNormal = dirZ;
SketchXAxis = gp_Vec(dirX);
break;
case mmObjectXZ:
SketchNormal = dirY.Reversed();
SketchXAxis = gp_Vec(dirX);
break;
case mmObjectYZ:
SketchNormal = dirX;
SketchXAxis = gp_Vec(dirY);
break;
case mmParallelPlane: {
if (shapes.size() < 2) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: not "
"enough subshapes (need one plane and one vertex)."
);
}
TopoDS_Vertex vertex;
try {
vertex = TopoDS::Vertex(shapes[1]->getShape());
}
catch (...) {
}
if (vertex.IsNull()) {
throw Base::ValueError(
"Null vertex in AttachEngine3D::calculateAttachedPlacement()!"
);
}
SketchNormal = dirZ;
SketchXAxis = gp_Vec(dirX);
// The new origin will be the vertex projected onto the normal.
Handle(Geom_Line) hCurve(new Geom_Line(SketchBasePoint, dirZ));
gp_Pnt p = BRep_Tool::Pnt(vertex);
GeomAPI_ProjectPointOnCurve projector(p, hCurve);
SketchBasePoint = projector.NearestPoint();
} break;
default:
break;
}
} break;
case mmInertialCS: {
GProp_GProps gpr = AttachEngine::getInertialPropsOfShape(shapes);
GProp_PrincipalProps pr = gpr.PrincipalProperties();
if (pr.HasSymmetryPoint()) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement:InertialCS: "
"inertia tensor is trivial, principal axes are undefined."
);
}
if (pr.HasSymmetryAxis()) {
Base::Console().warning(
"AttachEngine3D::calculateAttachedPlacement:InertialCS: inertia tensor has "
"axis of symmetry. Second and third axes of inertia are undefined.\n"
);
// find defined axis, and use it as Z axis
// situation: we have two moments that are almost equal, and one
// that is substantially different. The one that is different
// corresponds to a defined axis. We'll identify the different one by
// comparing differences.
Standard_Real I1, I2, I3;
pr.Moments(I1, I2, I3);
Standard_Real d12, d23, d31;
d12 = fabs(I1 - I2);
d23 = fabs(I2 - I3);
d31 = fabs(I3 - I1);
if (d12 < d23 && d12 < d31) {
SketchNormal = pr.ThirdAxisOfInertia();
}
else if (d23 < d31 && d23 < d12) {
SketchNormal = pr.FirstAxisOfInertia();
}
else {
SketchNormal = pr.SecondAxisOfInertia();
}
}
else {
SketchNormal = pr.FirstAxisOfInertia();
SketchXAxis = pr.SecondAxisOfInertia();
}
SketchBasePoint = gpr.CentreOfMass();
} break;
case mmFlatFace: {
if (shapes.empty()) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: no subobjects "
"specified (needed one planar face)."
);
}
TopoDS_Face face;
gp_Pln plane;
bool Reverse = false;
try {
face = TopoDS::Face(shapes[0]->getShape());
}
catch (...) {
}
if (face.IsNull()) {
if (!TopoShape(*shapes[0]).findPlane(plane)) {
throw Base::ValueError(
"No planar face in AttachEngine3D::calculateAttachedPlacement()!"
);
}
}
else {
BRepAdaptor_Surface adapt(face);
if (adapt.GetType() == GeomAbs_Plane) {
plane = adapt.Plane();
}
else {
TopLoc_Location loc;
Handle(Geom_Surface) surf = BRep_Tool::Surface(face, loc);
GeomLib_IsPlanarSurface check(surf, precision);
if (check.IsPlanar()) {
plane = check.Plan();
}
else {
throw Base::ValueError(
"No planar face in AttachEngine3D::calculateAttachedPlacement()!"
);
}
}
if (face.Orientation() == TopAbs_REVERSED) {
Reverse = true;
}
}
Standard_Boolean ok = plane.Direct();
if (!ok) {
// toggle if plane has a left-handed coordinate system
plane.UReverse();
Reverse = !Reverse;
}
gp_Ax1 Normal = plane.Axis();
if (Reverse) {
Normal.Reverse();
}
SketchNormal = Normal.Direction();
Handle(Geom_Plane) gPlane = new Geom_Plane(plane);
GeomAPI_ProjectPointOnSurf projector(refOrg, gPlane);
SketchBasePoint = projector.NearestPoint();
} break;
case mmTangentPlane: {
if (shapes.size() < 2) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: not enough "
"subshapes (need one face and one vertex)."
);
}
bool bThruVertex = false;
if (shapes[0]->shapeType() == TopAbs_VERTEX) {
std::swap(shapes[0], shapes[1]);
bThruVertex = true;
}
TopoDS_Face face;
try {
face = TopoDS::Face(shapes[0]->getShape());
}
catch (...) {
}
if (face.IsNull()) {
throw Base::ValueError("Null face in AttachEngine3D::calculateAttachedPlacement()!");
}
TopoDS_Vertex vertex;
try {
vertex = TopoDS::Vertex(shapes[1]->getShape());
}
catch (...) {
}
if (vertex.IsNull()) {
throw Base::ValueError("Null vertex in AttachEngine3D::calculateAttachedPlacement()!");
}
Handle(Geom_Surface) hSurf = BRep_Tool::Surface(face);
gp_Pnt p = BRep_Tool::Pnt(vertex);
GeomAPI_ProjectPointOnSurf projector(p, hSurf);
double u, v;
if (projector.NbPoints() == 0) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: projecting "
"point onto surface failed."
);
}
projector.LowerDistanceParameters(u, v);
BRepAdaptor_Surface surf(face);
BRepLProp_SLProps prop(surf, u, v, 1, Precision::Confusion());
gp_Dir dirX;
Standard_Boolean done;
Tools::getNormal(face, u, v, Precision::Confusion(), SketchNormal, done);
if (!done) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: finding normal "
"to surface at projected point failed."
);
}
// if getNormal succeeds, at least one of the tangent is defined
if (prop.IsTangentUDefined()) {
prop.TangentU(dirX);
if (face.Orientation() == TopAbs_REVERSED) {
dirX.Reverse();
}
}
// here the orientation of dirX is managed by SketchNormal orientation
else {
gp_Dir dirY;
prop.TangentV(dirY);
dirX = dirY.Crossed(SketchNormal);
}
SketchXAxis = gp_Vec(dirX).Reversed(); // yields upside-down sketches less often.
if (bThruVertex) {
SketchBasePoint = p;
}
else {
SketchBasePoint = projector.NearestPoint();
}
} break;
case mmNormalToPath:
case mmFrenetNB:
case mmFrenetTN:
case mmFrenetTB:
case mmRevolutionSection:
case mmConcentric: { // all alignments to point on curve
if (shapes.empty()) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: no subshapes "
"specified (need one edge, and an optional vertex)."
);
}
bool bThruVertex = false;
if (shapes[0]->shapeType() == TopAbs_VERTEX && shapes.size() >= 2) {
std::swap(shapes[0], shapes[1]);
bThruVertex = true;
}
TopoDS_Edge path;
try {
path = TopoDS::Edge(shapes[0]->getShape());
}
catch (...) {
}
if (path.IsNull()) {
throw Base::ValueError("Null path in AttachEngine3D::calculateAttachedPlacement()!");
}
BRepAdaptor_Curve adapt(path);
double u = 0.0;
double u1 = adapt.FirstParameter();
double u2 = adapt.LastParameter();
if (Precision::IsInfinite(u1) || Precision::IsInfinite(u2)) {
// prevent attachment to infinities in case of infinite shape.
// example of an infinite shape is a datum line.
u1 = 0.0;
u2 = 1.0;
}
// if a point is specified, use the point as a point of mapping, otherwise use parameter
// value from properties
gp_Pnt p_in;
if (shapes.size() >= 2) {
TopoDS_Vertex vertex;
try {
vertex = TopoDS::Vertex(shapes[1]->getShape());
}
catch (...) {
}
if (vertex.IsNull()) {
throw Base::ValueError(
"Null vertex in AttachEngine3D::calculateAttachedPlacement()!"
);
}
p_in = BRep_Tool::Pnt(vertex);
Handle(Geom_Curve) hCurve = BRep_Tool::Curve(path, u1, u2);
GeomAPI_ProjectPointOnCurve projector(p_in, hCurve);
u = projector.LowerDistanceParameter();
}
else {
u = u1 + this->attachParameter * (u2 - u1);
}
gp_Pnt p;
gp_Vec d; // point and derivative
adapt.D1(u, p, d);
if (d.Magnitude() < Precision::Confusion()) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: path curve "
"derivative is below 1e-7, too low, can't align"
);
}
// Set origin. Note that it will be overridden later for mmConcentric and
// mmRevolutionSection
if (bThruVertex) {
SketchBasePoint = p_in;
}
else {
SketchBasePoint = p;
}
if (mmode == mmRevolutionSection || mmode == mmConcentric || mmode == mmFrenetNB
|| mmode == mmFrenetTN || mmode == mmFrenetTB) {
gp_Vec dd; // second derivative
try {
adapt.D2(u, p, d, dd);
}
catch (Standard_Failure& e) {
// ignore. This is probably due to insufficient continuity.
dd = gp_Vec(0., 0., 0.);
Base::Console().warning(
"AttachEngine3D::calculateAttachedPlacement: can't "
"calculate second derivative of curve. OCC error: %s\n",
e.GetMessageString()
);
}
gp_Vec T, N, B; // Frenet?Serret axes: tangent, normal, binormal
T = d.Normalized();
N = dd.Subtracted(T.Multiplied(dd.Dot(T))); // take away the portion of dd that is
// along tangent
if (N.Magnitude() > Precision::SquareConfusion()) {
N.Normalize();
B = T.Crossed(N);
}
else {
Base::Console().warning(
"AttachEngine3D::calculateAttachedPlacement: path curve second derivative "
"is below 1e-14, cannot align X-axis.\n"
);
N = gp_Vec(0., 0., 0.);
B = gp_Vec(0., 0., 0.); // redundant, just for consistency
}
switch (mmode) {
case mmFrenetNB:
case mmRevolutionSection:
SketchNormal = T.Reversed(); // to avoid sketches upside-down for regular
// curves like circles
SketchXAxis = N.Reversed();
break;
case mmFrenetTN:
case mmConcentric:
if (N.Magnitude() == 0.0) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: Frenet-Serret normal "
"is undefined. Can't align to TN plane."
);
}
SketchNormal = B;
SketchXAxis = T;
break;
case mmFrenetTB:
if (N.Magnitude() == 0.0) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: Frenet-Serret normal "
"is undefined. Can't align to TB plane."
);
}
SketchNormal = N.Reversed(); // it is more convenient to sketch on
// something looking at it so it is convex.
SketchXAxis = T;
break;
default:
assert(0); // mode forgotten?
}
if (mmode == mmRevolutionSection || mmode == mmConcentric) {
// make sketch origin be at center of osculating circle
if (N.Magnitude() == 0.0) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: path has infinite radius "
"of curvature at the point. Can't align for revolving."
);
}
double curvature = dd.Dot(N) / pow(d.Magnitude(), 2);
gp_Vec pv(p.XYZ());
pv.Add(N.Multiplied(1 / curvature)); // shift the point along curvature by
// radius of curvature
SketchBasePoint = gp_Pnt(pv.XYZ());
// it would have been cool to have the curve attachment point available inside
// sketch... Leave for future.
}
}
else if (mmode == mmNormalToPath) { // mmNormalToPath
// align sketch origin to the origin of support
SketchNormal = gp_Dir(d.Reversed()); // sketch normal looks at user. It is natural
// to have the curve directed away from user,
// so reversed.
}
} break;
case mmThreePointsPlane:
case mmThreePointsNormal: {
std::vector<gp_Pnt> points;
for (const auto& shape : shapes) {
const TopoDS_Shape& sh = shape->getShape();
if (sh.IsNull()) {
throw Base::ValueError(
"Null shape in AttachEngine3D::calculateAttachedPlacement()!"
);
}
if (sh.ShapeType() == TopAbs_VERTEX) {
const TopoDS_Vertex& v = TopoDS::Vertex(sh);
points.push_back(BRep_Tool::Pnt(v));
}
else if (sh.ShapeType() == TopAbs_EDGE) {
const TopoDS_Edge& e = TopoDS::Edge(sh);
BRepAdaptor_Curve crv(e);
double u1 = crv.FirstParameter();
double u2 = crv.LastParameter();
if (Precision::IsInfinite(u1) || Precision::IsInfinite(u2)) {
u1 = 0.0;
u2 = 1.0;
}
points.push_back(crv.Value(u1));
points.push_back(crv.Value(u2));
}
if (points.size() >= 3) {
break;
}
}
if (points.size() < 3) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: less than 3 "
"points are specified, cannot derive the plane."
);
}
gp_Pnt p0 = points[0];
gp_Pnt p1 = points[1];
gp_Pnt p2 = points[2];
gp_Vec vec01(p0, p1);
gp_Vec vec02(p0, p2);
if (vec01.Magnitude() < Precision::Confusion()
|| vec02.Magnitude() < Precision::Confusion()) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: some of 3 "
"points are coincident. Can't make a plane"
);
}
vec01.Normalize();
vec02.Normalize();
gp_Vec norm;
if (mmode == mmThreePointsPlane) {
norm = vec01.Crossed(vec02);
if (norm.Magnitude() < Precision::Confusion()) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: points are "
"collinear. Can't make a plane"
);
}
// SketchBasePoint = (p0+p1+p2)/3.0
SketchBasePoint = gp_Pnt(
gp_Vec(p0.XYZ()).Added(p1.XYZ()).Added(p2.XYZ()).Multiplied(1.0 / 3.0).XYZ()
);
}
else if (mmode == mmThreePointsNormal) {
norm = vec02.Subtracted(vec01.Multiplied(vec02.Dot(vec01)))
.Reversed(); // norm = vec02 forced perpendicular to vec01.
if (norm.Magnitude() < Precision::Confusion()) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: points are "
"collinear. Can't make a plane"
);
}
// SketchBasePoint = (p0+p1)/2.0
Handle(Geom_Plane) gPlane = new Geom_Plane(p0, gp_Dir(norm));
GeomAPI_ProjectPointOnSurf projector(p2, gPlane);
SketchBasePoint = projector.NearestPoint();
}
norm.Normalize();
SketchNormal = gp_Dir(norm);
} break;
case mmFolding: {
// Expected selection: four edges in order: edgeA, fold axis A,
// fold axis B, edgeB. The sketch will be placed angled so as to join
// edgeA to edgeB by folding the sheet along axes. All edges are
// expected to be in one plane.
if (shapes.size() < 4) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: not enough "
"shapes (need 4 lines: edgeA, axisA, axisB, edgeB)."
);
}
// extract the four lines
const TopoDS_Edge* edges[4];
BRepAdaptor_Curve adapts[4];
gp_Lin lines[4];
for (int i = 0; i < 4; i++) {
try {
edges[i] = &TopoDS::Edge(shapes[i]->getShape());
}
catch (...) {
}
if (edges[i]->IsNull()) {
throw Base::ValueError(
"Null edge in AttachEngine3D::calculateAttachedPlacement()!"
);
}
adapts[i] = BRepAdaptor_Curve(*(edges[i]));
if (adapts[i].GetType() != GeomAbs_Line) {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: Folding - non-straight edge."
);
}
lines[i] = adapts[i].Line();
}
// figure out the common starting point (variable p)
gp_Pnt p, p1, p2, p3, p4;
double signs[4] = {0, 0, 0, 0}; // flags whether to reverse line directions, for all
// directions to point away from the common vertex
p1 = adapts[0].Value(adapts[0].FirstParameter());
p2 = adapts[0].Value(adapts[0].LastParameter());
p3 = adapts[1].Value(adapts[1].FirstParameter());
p4 = adapts[1].Value(adapts[1].LastParameter());
p = p1;
if (p1.Distance(p3) < Precision::Confusion()) {
p = p3;
signs[0] = +1.0;
signs[1] = +1.0;
}
else if (p1.Distance(p4) < Precision::Confusion()) {
p = p4;
signs[0] = +1.0;
signs[1] = -1.0;
}
else if (p2.Distance(p3) < Precision::Confusion()) {
p = p3;
signs[0] = -1.0;
signs[1] = +1.0;
}
else if (p2.Distance(p4) < Precision::Confusion()) {
p = p4;
signs[0] = -1.0;
signs[1] = -1.0;
}
else {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: Folding - "
"edges to not share a vertex."
);
}
for (int i = 2; i < 4; i++) {
p1 = adapts[i].Value(adapts[i].FirstParameter());
p2 = adapts[i].Value(adapts[i].LastParameter());
if (p.Distance(p1) < Precision::Confusion()) {
signs[i] = +1.0;
}
else if (p.Distance(p2) < Precision::Confusion()) {
signs[i] = -1.0;
}
else {
throw Base::ValueError(
"AttachEngine3D::calculateAttachedPlacement: Folding - "
"edges to not share a vertex."
);
}
}
gp_Vec dirs[4];
for (int i = 0; i < 4; i++) {
assert(fabs(signs[i]) == 1.0);
dirs[i] = gp_Vec(lines[i].Direction()).Multiplied(signs[i]);
}
double ang = this->calculateFoldAngle(dirs[1], dirs[2], dirs[0], dirs[3]);
gp_Vec norm = dirs[1].Crossed(dirs[2]);
// rotation direction: when angle is positive, rotation is CCW when observing the vector
// so that the axis is pointing at you. Hence angle is negated here.
norm.Rotate(gp_Ax1(gp_Pnt(), gp_Dir(dirs[1])), -ang);
SketchNormal = norm.Reversed();
SketchXAxis = dirs[1];
SketchBasePoint = p;
} break;
case mmOZX:
case mmOZY:
case mmOXY:
case mmOXZ:
case mmOYZ:
case mmOYX: {
const char orderStrings[6][4] = {
"ZXY",
"ZYX",
"XYZ",
"XZY",
"YZX",
"YXZ",
};
const char* orderString = orderStrings[mmode - mmOZX];
enum dirIndex
{
X,
Y,
Z
};
int order[3];
for (int i = 0; i < 3; ++i) {
order[i] = orderString[i] - 'X';
}
if (shapes.size() < 2) {
THROWM(
Base::ValueError,
"AttachEngine3D::calculateAttachedPlacement: not enough shapes linked (at "
"least two are required)."
);
}
gp_Vec dirs[3];
// read out origin
if (shapes[0]->isNull()) {
THROWM(Base::TypeError, "AttachEngine3D::calculateAttachedPlacement: null shape!")
}
if (shapes[0]->shapeType() != TopAbs_VERTEX) {
THROWM(
Base::TypeError,
"AttachEngine3D::calculateAttachedPlacement: first reference must be a "
"vertex, it's not"
)
}
SketchBasePoint = BRep_Tool::Pnt(TopoDS::Vertex(shapes[0]->getShape()));
// read out axes directions
for (size_t i = 1; i < 3 && i < shapes.size(); ++i) {
if (shapes[i]->isNull()) {
THROWM(Base::TypeError, "AttachEngine3D::calculateAttachedPlacement: null shape!")
}
if (shapes[i]->shapeType() == TopAbs_VERTEX) {
gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(shapes[i]->getShape()));
dirs[order[i - 1]] = gp_Vec(SketchBasePoint, p);
}
else if (shapes[i]->shapeType() == TopAbs_EDGE) {
const TopoDS_Edge& e = TopoDS::Edge(shapes[i]->getShape());
BRepAdaptor_Curve crv(e);
double u1 = crv.FirstParameter();
double u2 = crv.LastParameter();
if (Precision::IsInfinite(u1) || Precision::IsInfinite(u2)) {
u1 = 0.0;
u2 = 1.0;
}
gp_Pnt p1 = crv.Value(u1);
gp_Pnt p2 = crv.Value(u2);
dirs[order[i - 1]] = gp_Vec(p1, p2);
}
}
// make the placement
Base::Rotation rot = Base::Rotation::makeRotationByAxes(
Base::Vector3d(dirs[0].X(), dirs[0].Y(), dirs[0].Z()),
Base::Vector3d(dirs[1].X(), dirs[1].Y(), dirs[1].Z()),
Base::Vector3d(dirs[2].X(), dirs[2].Y(), dirs[2].Z()),
orderString
);
if (this->mapReverse) {
rot = rot * Base::Rotation(Base::Vector3d(0, 1, 0), std::numbers::pi);
}
Base::Placement plm = Base::Placement(
Base::Vector3d(SketchBasePoint.X(), SketchBasePoint.Y(), SketchBasePoint.Z()),
rot
);
plm *= this->attachmentOffset;
return plm;
} break;
case mmMidpoint: {
Base::Placement placement;
// special case for planes
if (auto plane = dynamic_cast<App::Plane*>(objs[0])) {
return plane->Placement.getValue() * attachmentOffset;
}
auto shape = shapes.front();
auto geom = Geometry::fromShape(shape->getShape());
switch (shape->shapeType()) {
case TopAbs_VERTEX: {
if (auto point = freecad_cast<GeomPoint*>(geom.get())) {
placement.setPosition(point->getPoint());
}
} break;
case TopAbs_EDGE: {
if (auto conic = freecad_cast<GeomConic*>(geom.get())) {
placement.setPosition(conic->getLocation());
placement.setRotation(conic->getRotation().value_or(Base::Rotation {}));
}
else if (auto line = freecad_cast<GeomCurve*>(geom.get())) {
auto u1 = line->getFirstParameter();
auto u2 = line->getLastParameter();
auto middle = (u1 + u2) / 2;
placement.setPosition(line->pointAtParameter(middle));
Base::Vector3d direction;
if (!line->normalAt(middle, direction)) {
line->tangent(middle, direction);
}
placement.setRotation(Base::Rotation::fromNormalVector(direction));
}
// Midpoint for circular edges
const TopoDS_Shape& sh = shape->getShape();
if (!sh.IsNull() && sh.ShapeType() == TopAbs_EDGE) {
TopoDS_Edge ed = TopoDS::Edge(sh);
BRepAdaptor_Curve adapt(ed);
if (adapt.GetType() == GeomAbs_Circle) {
// Center of the circle / arc
const gp_Circ circ = adapt.Circle();
const gp_Pnt center = circ.Location();
const gp_Dir axisDir = circ.Axis().Direction(); // normal to circle plane
placement.setPosition(Base::convertTo<Base::Vector3d>(center));
placement.setRotation(
Base::Rotation::fromNormalVector(
Base::convertTo<Base::Vector3d>(axisDir)
)
);
break;
}
}
} break;
case TopAbs_FACE: {
auto surface = freecad_cast<GeomSurface*>(geom.get());
auto face = TopoDS::Face(shape->getShape());
auto adaptorSurface = BRepAdaptor_Surface(face, true);
auto u1 = adaptorSurface.FirstUParameter();
auto u2 = adaptorSurface.LastUParameter();
auto v1 = adaptorSurface.FirstVParameter();
auto v2 = adaptorSurface.LastVParameter();
auto midU = (u1 + u2) / 2;
auto midV = (v1 + v2) / 2;
// Axis for circular faces
if (adaptorSurface.GetType() == GeomAbs_Cylinder) {
const gp_Cylinder cyl = adaptorSurface.Cylinder();
const gp_Ax1 axis = cyl.Axis();
const gp_Pnt origin = axis.Location();
const gp_Dir axisDir = axis.Direction();
const gp_Pnt midPnt = adaptorSurface.Value(midU, midV);
// Project midPnt onto the cylinder axis to get an axis-center point near face
const gp_Vec v(origin, midPnt);
const Standard_Real t = v.Dot(gp_Vec(axisDir)); // scalar projection onto axis
const gp_Pnt axisCenter = origin.Translated(gp_Vec(axisDir) * t);
placement.setPosition(Base::convertTo<Base::Vector3d>(axisCenter));
placement.setRotation(
Base::Rotation::fromNormalVector(Base::convertTo<Base::Vector3d>(axisDir))
);
break;
}
if (auto sphere = freecad_cast<GeomSphere*>(geom.get())) {
placement.setPosition(sphere->getLocation());
}
else if (auto cone = freecad_cast<GeomCone*>(geom.get())) {
placement.setPosition(cone->getApex());
}
else if (auto point = surface->point(midU, midV)) {
placement.setPosition(*point);
}
else if (auto com = shape->centerOfGravity()) {
placement.setPosition(*com);
}
else {
placement.setPosition(shape->getBoundBox().GetCenter());
}
// calculate the normal at midpoint of the surface and use it as Z axis
gp_Dir dir;
surface->normal(midU, midV, dir);
if (face.Orientation() == TopAbs_REVERSED) {
dir = -dir;
}
placement.setRotation(rotationAlignedToNormal(
placement.getRotation(),
Base::convertTo<Base::Vector3d>(dir)
));
} break;
default:
THROWM(
Base::TypeError,
"AttachEngine3D::calculateAttachedPlacement: Unsupported shape type, "
"must be one of: Vertex, Edge, Face"
);
break;
}
return placement * attachmentOffset;
break;
}
default:
throwWrongMode(mmode);
} // switch (MapMode)
//----------calculate placement, based on point and vector
Base::Placement plm = this->placementFactory(
SketchNormal,
SketchXAxis,
SketchBasePoint,
gp_Pnt(),
/*useRefOrg_Line = */ false,
/*useRefOrg_Plane = */ false,
/*makeYVertical = */ false,
/*makeLegacyFlatFaceOrientation = */ mmode == mmFlatFace,
&Place
);
plm *= this->attachmentOffset;
return plm;
}
double AttachEngine3D::calculateFoldAngle(gp_Vec axA, gp_Vec axB, gp_Vec edA, gp_Vec edB) const
{
// DeepSOIC: this hardcore math can probably be replaced with a couple of
// clever OCC calls... See forum thread "Sketch mapping enhancement" for a
// picture on how this math was derived.
// https://forum.freecad.org/viewtopic.php?f=8&t=10511&sid=007946a934530ff2a6c9259fb32624ec&start=40#p87584
axA.Normalize();
axB.Normalize();
edA.Normalize();
edB.Normalize();
gp_Vec norm = axA.Crossed(axB);
if (norm.Magnitude() < Precision::Confusion()) {
throw AttachEngineException(
"calculateFoldAngle: Folding axes are parallel, folding angle cannot be computed."
);
}
norm.Normalize();
double a = edA.Dot(axA);
double ra = edA.Crossed(axA).Magnitude();
if (fabs(ra) < Precision::Confusion()) {
throw AttachEngineException(
"calculateFoldAngle: axisA and edgeA are parallel, folding can't be computed."
);
}
double b = edB.Dot(axB);
double costheta = axB.Dot(axA);
double sintheta = axA.Crossed(axB).Dot(norm);
double singama = -costheta;
double cosgama = sintheta;
double k = b * cosgama;
double l = a + b * singama;
double xa = k + l * singama / cosgama;
double cos_unfold = -xa / ra;
if (fabs(cos_unfold) > 0.999) {
throw AttachEngineException(
"calculateFoldAngle: cosine of folding angle is too close to or above 1."
);
}
return acos(cos_unfold);
}
//=================================================================================
TYPESYSTEM_SOURCE(Attacher::AttachEnginePlane, Attacher::AttachEngine)
AttachEnginePlane::AttachEnginePlane()
{
// reused 3d modes: all of Attacher3d
AttachEngine3D attacher3D;
this->modeRefTypes = attacher3D.modeRefTypes;
this->EnableAllSupportedModes();
}
AttachEnginePlane* AttachEnginePlane::copy() const
{
AttachEnginePlane* p = new AttachEnginePlane;
p->setUp(*this);
return p;
}
Base::Placement AttachEnginePlane::_calculateAttachedPlacement(
const std::vector<App::DocumentObject*>& objs,
const std::vector<std::string>& subs,
const Base::Placement& origPlacement
) const
{
// reuse Attacher3d
Base::Placement plm;
AttachEngine3D attacher3D;
attacher3D.precision = precision;
attacher3D.setUp(*this);
plm = attacher3D._calculateAttachedPlacement(objs, subs, origPlacement);
return plm;
}
double AttachEnginePlane::planarPrecision()
{
return 2.0e-7; // NOLINT
}
//=================================================================================
TYPESYSTEM_SOURCE(Attacher::AttachEngineLine, Attacher::AttachEngine)
AttachEngineLine::AttachEngineLine()
{
// fill type lists for modes
modeRefTypes.resize(mmDummy_NumberOfModes);
refTypeString s;
// reused 3d modes
AttachEngine3D attacher3D;
modeRefTypes[mm1AxisX] = attacher3D.modeRefTypes[mmObjectYZ];
modeRefTypes[mm1AxisY] = attacher3D.modeRefTypes[mmObjectXZ];
modeRefTypes[mm1AxisZ] = attacher3D.modeRefTypes[mmObjectXY];
modeRefTypes[mm1AxisCurv] = attacher3D.modeRefTypes[mmRevolutionSection];
modeRefTypes[mm1Binormal] = attacher3D.modeRefTypes[mmFrenetTN];
modeRefTypes[mm1Normal] = attacher3D.modeRefTypes[mmFrenetTB];
modeRefTypes[mm1Tangent] = attacher3D.modeRefTypes[mmNormalToPath];
modeRefTypes[mm1TwoPoints].push_back(cat(rtVertex, rtVertex));
modeRefTypes[mm1TwoPoints].push_back(cat(rtLine));
modeRefTypes[mm1Asymptote1].push_back(cat(rtHyperbola));
modeRefTypes[mm1Asymptote2].push_back(cat(rtHyperbola));
modeRefTypes[mm1Directrix1].push_back(cat(rtConic));
modeRefTypes[mm1Directrix2].push_back(cat(rtEllipse));
modeRefTypes[mm1Directrix2].push_back(cat(rtHyperbola));
modeRefTypes[mm1Proximity].push_back(cat(rtAnything, rtAnything));
modeRefTypes[mm1AxisInertia1].push_back(cat(rtAnything));
modeRefTypes[mm1AxisInertia1].push_back(cat(rtAnything, rtAnything));
modeRefTypes[mm1AxisInertia1].push_back(cat(rtAnything, rtAnything, rtAnything));
modeRefTypes[mm1AxisInertia1].push_back(cat(rtAnything, rtAnything, rtAnything, rtAnything));
modeRefTypes[mm1AxisInertia2] = modeRefTypes[mm1AxisInertia1];
modeRefTypes[mm1AxisInertia3] = modeRefTypes[mm1AxisInertia1];
modeRefTypes[mm1FaceNormal] = attacher3D.modeRefTypes[mmTangentPlane];
modeRefTypes[mm1Intersection].push_back(cat(rtFace, rtFace));
this->EnableAllSupportedModes();
}
AttachEngineLine* AttachEngineLine::copy() const
{
AttachEngineLine* p = new AttachEngineLine;
p->setUp(*this);
return p;
}
Base::Placement AttachEngineLine::_calculateAttachedPlacement(
const std::vector<App::DocumentObject*>& objs,
const std::vector<std::string>& subs,
const Base::Placement& origPlacement
) const
{
eMapMode mmode = this->mapMode;
// modes that are mirrors of attacher3D:
bool bReUsed = true;
Base::Placement presuperPlacement;
switch (mmode) {
case mmDeactivated:
throw ExceptionCancel(); // to be handled in positionBySupport, to not do anything if
// disabled
case mm1AxisX:
mmode = mmObjectYZ;
break;
case mm1AxisY:
mmode = mmObjectXZ;
break;
case mm1AxisZ:
mmode = mmObjectXY;
break;
case mm1AxisCurv:
mmode = mmRevolutionSection;
// the line should go along Y, not Z
presuperPlacement.setRotation(
Base::Rotation(Base::Vector3d(0.0, 0.0, 1.0), Base::Vector3d(0.0, 1.0, 0.0))
);
break;
case mm1Binormal:
mmode = mmFrenetTN;
break;
case mm1Normal:
mmode = mmFrenetTB;
break;
case mm1Tangent:
mmode = mmNormalToPath;
break;
case mm1FaceNormal:
mmode = mmTangentPlane;
break;
default:
bReUsed = false;
break;
}
Base::Placement plm;
if (!bReUsed) {
std::vector<const TopoShape*> shapes;
std::vector<TopoShape> copiedShapeStorage;
std::vector<eRefType> types;
readLinks(objs, subs, shapes, copiedShapeStorage, types);
if (shapes.empty()) {
throw ExceptionCancel();
}
// common stuff for all map modes
App::DocumentObject* subObj = objs[0]->getSubObject(subs[0].c_str());
Base::Placement Place = App::GeoFeature::getGlobalPlacement(subObj, objs[0], subs[0]);
Base::Vector3d vec = Place.getPosition();
gp_Pnt refOrg = gp_Pnt(vec.x, vec.y, vec.z); // origin of linked object
// variables to derive the actual placement.
// They are to be set, depending on the mode:
gp_Dir LineDir;
gp_Pnt LineBasePoint; // the point the line goes through
switch (mmode) {
case mm1AxisInertia1:
case mm1AxisInertia2:
case mm1AxisInertia3: {
GProp_GProps gpr = AttachEngine::getInertialPropsOfShape(shapes);
LineBasePoint = gpr.CentreOfMass();
GProp_PrincipalProps pr = gpr.PrincipalProperties();
if (pr.HasSymmetryPoint()) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement:AxisOfInertia: inertia "
"tensor is trivial, principal axes are undefined."
);
}
// query moments, to use them to check if axis is defined
// See AttachEngine3D::calculateAttachedPlacement:case mmInertial for comment
// explaining these comparisons
Standard_Real I1, I2, I3;
pr.Moments(I1, I2, I3);
Standard_Real d12, d23, d31;
d12 = fabs(I1 - I2);
d23 = fabs(I2 - I3);
d31 = fabs(I3 - I1);
if (mmode == mm1AxisInertia1) {
LineDir = pr.FirstAxisOfInertia();
if (pr.HasSymmetryAxis() && !(d23 < d31 && d23 < d12)) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement:AxisOfInertia: inertia "
"tensor has axis of symmetry; first axis of inertia is undefined."
);
}
}
else if (mmode == mm1AxisInertia2) {
LineDir = pr.SecondAxisOfInertia();
if (pr.HasSymmetryAxis() && !(d31 < d12 && d31 < d23)) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement:AxisOfInertia: inertia "
"tensor has axis of symmetry; second axis of inertia is undefined."
);
}
}
else if (mmode == mm1AxisInertia3) {
LineDir = pr.ThirdAxisOfInertia();
if (pr.HasSymmetryAxis() && !(d12 < d23 && d12 < d31)) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement:AxisOfInertia: inertia "
"tensor has axis of symmetry; third axis of inertia is undefined."
);
}
}
} break;
case mm1TwoPoints: {
std::vector<gp_Pnt> points;
for (const auto& shape : shapes) {
const TopoDS_Shape& sh = shape->getShape();
if (sh.IsNull()) {
throw Base::ValueError(
"Null shape in AttachEngineLine::calculateAttachedPlacement()!"
);
}
if (sh.ShapeType() == TopAbs_VERTEX) {
const TopoDS_Vertex& v = TopoDS::Vertex(sh);
points.push_back(BRep_Tool::Pnt(v));
}
else if (sh.ShapeType() == TopAbs_EDGE) {
const TopoDS_Edge& e = TopoDS::Edge(sh);
BRepAdaptor_Curve crv(e);
double u1 = crv.FirstParameter();
double u2 = crv.LastParameter();
if (Precision::IsInfinite(u1) || Precision::IsInfinite(u2)) {
u1 = 0.0;
u2 = 1.0;
}
points.push_back(crv.Value(u1));
points.push_back(crv.Value(u2));
}
if (points.size() >= 2) {
break;
}
}
if (points.size() < 2) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement: less "
"than 2 points are specified, cannot derive the line."
);
}
gp_Pnt p0 = points[0];
gp_Pnt p1 = points[1];
LineDir = gp_Dir(gp_Vec(p0, p1));
LineBasePoint = p0;
} break;
case mm1Asymptote1:
case mm1Asymptote2: {
if (shapes[0]->isNull()) {
throw Base::ValueError(
"Null shape in AttachEngineLine::calculateAttachedPlacement()!"
);
}
TopoDS_Edge e;
try {
e = TopoDS::Edge(shapes[0]->getShape());
}
catch (...) {
}
if (e.IsNull()) {
throw Base::ValueError(
"Null edge in AttachEngineLine::calculateAttachedPlacement()!"
);
}
BRepAdaptor_Curve adapt(e);
if (adapt.GetType() != GeomAbs_Hyperbola) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement: Asymptotes are available "
"only for hyperbola-shaped edges, the one supplied is not."
);
}
gp_Hypr hyp = adapt.Hyperbola();
if (mmode == mm1Asymptote1) {
LineDir = hyp.Asymptote1().Direction();
}
else {
LineDir = hyp.Asymptote2().Direction();
}
LineBasePoint = hyp.Location();
} break;
case mm1Directrix1:
case mm1Directrix2: {
if (shapes[0]->isNull()) {
throw Base::ValueError(
"Null shape in AttachEngineLine::calculateAttachedPlacement()!"
);
}
TopoDS_Edge e;
try {
e = TopoDS::Edge(shapes[0]->getShape());
}
catch (...) {
}
if (e.IsNull()) {
throw Base::ValueError(
"Null edge in AttachEngineLine::calculateAttachedPlacement()!"
);
}
BRepAdaptor_Curve adapt(e);
gp_Ax1 dx1, dx2; // vars to receive directrices
switch (adapt.GetType()) {
case GeomAbs_Ellipse: {
gp_Elips cc = adapt.Ellipse();
dx1 = cc.Directrix1();
dx2 = cc.Directrix2();
} break;
case GeomAbs_Hyperbola: {
gp_Hypr cc = adapt.Hyperbola();
dx1 = cc.Directrix1();
dx2 = cc.Directrix2();
} break;
case GeomAbs_Parabola: {
gp_Parab cc = adapt.Parabola();
dx1 = cc.Directrix();
if (mmode == mm1Directrix2) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement: "
"Parabola has no second directrix"
);
}
} break;
default:
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement: referenced edge is not "
"a conic section with a directrix"
);
}
if (mmode == mm1Directrix1) {
LineDir = dx1.Direction();
LineBasePoint = dx1.Location();
}
else {
LineDir = dx2.Direction();
LineBasePoint = dx2.Location();
}
} break;
case mm1Intersection: {
if (shapes.size() < 2) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement: Intersection mode requires "
"two shapes; only one is supplied"
);
}
if (shapes[0]->isNull() || shapes[1]->isNull()) {
throw Base::ValueError(
"Null shape in AttachEngineLine::calculateAttachedPlacement()!"
);
}
const TopoDS_Face& face1 = TopoDS::Face(shapes[0]->getShape());
const TopoDS_Face& face2 = TopoDS::Face(shapes[1]->getShape());
Handle(Geom_Surface) hSurf1 = BRep_Tool::Surface(face1);
Handle(Geom_Surface) hSurf2 = BRep_Tool::Surface(face2);
GeomAPI_IntSS intersector(hSurf1, hSurf2, Precision::Confusion());
if (!intersector.IsDone()) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement: Intersection failed"
);
}
const Standard_Integer intLines = intersector.NbLines();
if (intLines == 0) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement: The two "
"shapes don't intersect"
);
}
if (intLines != 1) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement: "
"Intersection is not a single curve"
);
}
GeomAdaptor_Curve adapt(intersector.Line(1));
if (adapt.GetType() != GeomAbs_Line) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement: "
"Intersection is not a straight line"
);
}
LineBasePoint = adapt.Line().Location();
LineDir = adapt.Line().Direction();
} break;
case mm1Proximity: {
if (shapes.size() < 2) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement: Proximity mode requires two "
"shapes; only one is supplied"
);
}
if (shapes[0]->isNull()) {
throw Base::ValueError(
"Null shape in AttachEngineLine::calculateAttachedPlacement()!"
);
}
if (shapes[1]->isNull()) {
throw Base::ValueError(
"Null shape in AttachEngineLine::calculateAttachedPlacement()!"
);
}
BRepExtrema_DistShapeShape distancer(shapes[0]->getShape(), shapes[1]->getShape());
if (!distancer.IsDone()) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement: "
"proximity calculation failed."
);
}
if (distancer.NbSolution() > 1) {
Base::Console().warning(
"AttachEngineLine::calculateAttachedPlacement: "
"proximity calculation gave %i solutions, ambiguous.\n",
int(distancer.NbSolution())
);
}
gp_Pnt p1 = distancer.PointOnShape1(1);
gp_Pnt p2 = distancer.PointOnShape2(1);
LineBasePoint = p1;
gp_Vec dist = gp_Vec(p1, p2);
if (dist.Magnitude() < Precision::Confusion()) {
throw Base::ValueError(
"AttachEngineLine::calculateAttachedPlacement: can't make proximity line, "
"because shapes touch or intersect"
);
}
LineDir = gp_Dir(dist);
} break;
default:
throwWrongMode(mmode);
}
plm = this->placementFactory(
LineDir,
gp_Vec(),
LineBasePoint,
refOrg,
/*useRefOrg_Line = */ true
);
}
else { // reuse 3d mode
AttachEngine3D attacher3D;
attacher3D.setUp(*this);
attacher3D.mapMode = mmode;
attacher3D.attachmentOffset
= Base::Placement(); // AttachmentOffset is applied separately here, afterwards. So we
// are resetting it in sub-attacher to avoid applying it twice!
plm = attacher3D._calculateAttachedPlacement(objs, subs, origPlacement);
plm *= presuperPlacement;
}
plm *= this->attachmentOffset;
return plm;
}
//=================================================================================
TYPESYSTEM_SOURCE(Attacher::AttachEnginePoint, Attacher::AttachEngine)
AttachEnginePoint::AttachEnginePoint()
{
// fill type lists for modes
modeRefTypes.resize(mmDummy_NumberOfModes);
refTypeString s;
// reused 3d modes
AttachEngine3D attacher3D;
modeRefTypes[mm0Origin] = attacher3D.modeRefTypes[mmObjectXY];
modeRefTypes[mm0CenterOfCurvature] = attacher3D.modeRefTypes[mmRevolutionSection];
modeRefTypes[mm0OnEdge] = attacher3D.modeRefTypes[mmNormalToPath];
modeRefTypes[mm0Vertex].push_back(cat(rtVertex));
modeRefTypes[mm0Vertex].push_back(cat(rtLine));
modeRefTypes[mm0Focus1].push_back(cat(rtConic));
modeRefTypes[mm0Focus2].push_back(cat(rtEllipse));
modeRefTypes[mm0Focus2].push_back(cat(rtHyperbola));
s = cat(rtAnything, rtAnything);
modeRefTypes[mm0ProximityPoint1].push_back(s);
modeRefTypes[mm0ProximityPoint2].push_back(s);
modeRefTypes[mm0CenterOfMass].push_back(cat(rtAnything));
modeRefTypes[mm0CenterOfMass].push_back(cat(rtAnything, rtAnything));
modeRefTypes[mm0CenterOfMass].push_back(cat(rtAnything, rtAnything, rtAnything));
modeRefTypes[mm0CenterOfMass].push_back(cat(rtAnything, rtAnything, rtAnything, rtAnything));
this->EnableAllSupportedModes();
}
AttachEnginePoint* AttachEnginePoint::copy() const
{
AttachEnginePoint* p = new AttachEnginePoint;
p->setUp(*this);
return p;
}
Base::Placement AttachEnginePoint::_calculateAttachedPlacement(
const std::vector<App::DocumentObject*>& objs,
const std::vector<std::string>& subs,
const Base::Placement& origPlacement
) const
{
eMapMode mmode = this->mapMode;
// modes that are mirrors of attacher3D:
bool bReUsed = true;
switch (mmode) {
case mmDeactivated:
throw ExceptionCancel(); // to be handled in positionBySupport, to not do anything if
// disabled
case mm0Origin:
mmode = mmObjectXY;
break;
case mm0CenterOfCurvature:
mmode = mmRevolutionSection;
break;
case mm0OnEdge:
// todo: prevent thruPoint
mmode = mmNormalToPath;
break;
default:
bReUsed = false;
}
Base::Placement plm;
if (!bReUsed) {
std::vector<const TopoShape*> shapes;
std::vector<TopoShape> copiedShapeStorage;
std::vector<eRefType> types;
readLinks(objs, subs, shapes, copiedShapeStorage, types);
if (shapes.empty()) {
throw ExceptionCancel();
}
// variables to derive the actual placement.
// They are to be set, depending on the mode:
gp_Pnt BasePoint; // where to put the point
switch (mmode) {
case mm0Vertex: {
std::vector<gp_Pnt> points;
assert(!shapes.empty());
const TopoDS_Shape& sh = shapes[0]->getShape();
if (sh.IsNull()) {
throw Base::ValueError(
"Null shape in AttachEnginePoint::calculateAttachedPlacement()!"
);
}
if (sh.ShapeType() == TopAbs_VERTEX) {
const TopoDS_Vertex& v = TopoDS::Vertex(sh);
BasePoint = BRep_Tool::Pnt(v);
}
else if (sh.ShapeType() == TopAbs_EDGE) {
const TopoDS_Edge& e = TopoDS::Edge(sh);
BRepAdaptor_Curve crv(e);
double u = crv.FirstParameter();
if (Precision::IsInfinite(u)) {
throw Base::ValueError("Edge is infinite");
}
BasePoint = crv.Value(u);
}
} break;
case mm0Focus1:
case mm0Focus2: {
if (shapes[0]->isNull()) {
throw Base::ValueError(
"Null shape in AttachEnginePoint::calculateAttachedPlacement()!"
);
}
TopoDS_Edge e;
try {
e = TopoDS::Edge(shapes[0]->getShape());
}
catch (...) {
}
if (e.IsNull()) {
throw Base::ValueError(
"Null edge in AttachEnginePoint::calculateAttachedPlacement()!"
);
}
BRepAdaptor_Curve adapt(e);
gp_Pnt f1, f2;
switch (adapt.GetType()) {
case GeomAbs_Ellipse: {
gp_Elips cc = adapt.Ellipse();
f1 = cc.Focus1();
f2 = cc.Focus2();
} break;
case GeomAbs_Hyperbola: {
gp_Hypr cc = adapt.Hyperbola();
f1 = cc.Focus1();
f2 = cc.Focus2();
} break;
case GeomAbs_Parabola: {
gp_Parab cc = adapt.Parabola();
f1 = cc.Focus();
if (mmode == mm0Focus2) {
throw Base::ValueError(
"AttachEnginePoint::calculateAttachedPlacement: "
"Parabola has no second focus"
);
}
} break;
default:
throw Base::ValueError(
"AttachEnginePoint::calculateAttachedPlacement: referenced edge is not "
"a conic section with a directrix"
);
}
if (mmode == mm0Focus1) {
BasePoint = f1;
}
else {
BasePoint = f2;
}
} break;
case mm0ProximityPoint1:
case mm0ProximityPoint2: {
if (shapes.size() < 2) {
throw Base::ValueError(
"AttachEnginePoint::calculateAttachedPlacement: Proximity mode requires "
"two shapes; only one is supplied"
);
}
if (shapes[0]->isNull()) {
throw Base::ValueError(
"Null shape in AttachEnginePoint::calculateAttachedPlacement()!"
);
}
if (shapes[1]->isNull()) {
throw Base::ValueError(
"Null shape in AttachEnginePoint::calculateAttachedPlacement()!"
);
}
BasePoint = getProximityPoint(mmode, shapes[0]->getShape(), shapes[1]->getShape());
} break;
case mm0CenterOfMass: {
GProp_GProps gpr = AttachEngine::getInertialPropsOfShape(shapes);
BasePoint = gpr.CentreOfMass();
} break;
default:
throwWrongMode(mmode);
}
plm = this->placementFactory(gp_Vec(0.0, 0.0, 1.0), gp_Vec(1.0, 0.0, 0.0), BasePoint, gp_Pnt());
}
else { // reuse 3d mode
AttachEngine3D attacher3D;
attacher3D.setUp(*this);
attacher3D.mapMode = mmode;
attacher3D.attachmentOffset
= Base::Placement(); // AttachmentOffset is applied separately here, afterwards. So we
// are resetting it in sub-attacher to avoid applying it twice!
plm = attacher3D._calculateAttachedPlacement(objs, subs, origPlacement);
}
plm *= this->attachmentOffset;
return plm;
}
gp_Pnt AttachEnginePoint::getProximityPoint(
eMapMode mmode,
const TopoDS_Shape& s1,
const TopoDS_Shape& s2
) const
{
// #0003921: Crash when opening document with datum point intersecting line and plane
//
// BRepExtrema_DistanceSS is used inside BRepExtrema_DistShapeShape and can cause
// a crash if the input shape is an unlimited face.
// So, when the input is a face and an edge then before checking for minimum distances
// try to determine intersection points.
try {
TopoDS_Shape face, edge;
if (s1.ShapeType() == TopAbs_FACE && s2.ShapeType() == TopAbs_EDGE) {
face = s1;
edge = s2;
}
else if (s1.ShapeType() == TopAbs_EDGE && s2.ShapeType() == TopAbs_FACE) {
edge = s1;
face = s2;
}
// edge and face
if (!edge.IsNull() && !face.IsNull()) {
BRepAdaptor_Curve crv(TopoDS::Edge(edge));
GeomAdaptor_Curve typedcrv;
try {
// Important note about BRepIntCurveSurface_Inter and GeomAdaptor_Curve
//
// A GeomAdaptor_Curve obtained directly from BRepAdaptor_Curve will lose the
// information about Location/orientation of the edge.
//
// That's why GeomAdaptor::MakeCurve() is used to create a new geometry with the
// transformation applied.
typedcrv.Load(GeomAdaptor::MakeCurve(crv));
}
catch (const Standard_DomainError&) {
Handle(Geom_Curve) curve = crv.Curve().Curve();
if (curve.IsNull()) {
// Can this ever happen?
typedcrv = crv.Curve();
}
else {
curve = Handle(Geom_Curve)::DownCast(curve->Copy());
curve->Transform(crv.Trsf());
typedcrv.Load(curve);
}
}
BRepIntCurveSurface_Inter intCS;
intCS.Init(face, typedcrv, Precision::Confusion());
std::vector<gp_Pnt> points;
for (; intCS.More(); intCS.Next()) {
gp_Pnt pnt = intCS.Pnt();
points.push_back(pnt);
}
if (points.size() > 1) {
Base::Console().warning(
"AttachEnginePoint::calculateAttachedPlacement: proximity calculation gave %d "
"solutions, ambiguous.\n",
int(points.size())
);
}
// if an intersection is found return the first hit
// otherwise continue with BRepExtrema_DistShapeShape
if (!points.empty()) {
return points.front();
}
}
}
catch (const Standard_Failure&) {
// ignore
}
BRepExtrema_DistShapeShape distancer(s1, s2);
if (!distancer.IsDone()) {
throw Base::ValueError(
"AttachEnginePoint::calculateAttachedPlacement: proximity calculation failed."
);
}
if (distancer.NbSolution() > 1) {
Base::Console().warning(
"AttachEnginePoint::calculateAttachedPlacement: proximity calculation gave %i "
"solutions, ambiguous.\n",
int(distancer.NbSolution())
);
}
gp_Pnt p1 = distancer.PointOnShape1(1);
gp_Pnt p2 = distancer.PointOnShape2(1);
if (mmode == mm0ProximityPoint1) {
return p1;
}
else {
return p2;
}
}