3125 lines
114 KiB
C++
3125 lines
114 KiB
C++
// 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;
|
||
}
|
||
}
|