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

2710 lines
103 KiB
C++

/***************************************************************************
* 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 "PreCompiled.h"
#ifndef _PreComp_
# 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 <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>
#endif
#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"
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",
nullptr};
//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<App::GeoFeature*> parts;
std::vector<const TopoDS_Shape*> shapes;
std::vector<TopoDS_Shape> shapeStorage;
std::vector<eRefType> typeStr;
try{
readLinks(getRefObjects(),subnames, parts, 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<App::GeoFeature*> parts;
std::vector<const TopoDS_Shape*> shapes;
std::vector<TopoDS_Shape> copiedShapeStorage;
std::vector<eRefType> types;
readLinks(tmpLink.getValues(),tmpLink.getSubValues(), parts, 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 TopoDS_Shape*> &shapes)
{
//explode compounds
TopTools_HSequenceOfShape totalSeq;
for (const TopoDS_Shape* pSh : shapes) {
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 parts
* \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<App::GeoFeature*> &geofs,
std::vector<const TopoDS_Shape*> &shapes,
std::vector<TopoDS_Shape> &storage,
std::vector<eRefType> &types)
{
geofs.resize(objs.size());
storage.reserve(objs.size());
shapes.resize(objs.size());
types.resize(objs.size());
for (std::size_t i = 0; i < objs.size(); i++) {
std::string fullSub = subs[i];
const char* element = Data::findElementName(fullSub.c_str());
App::DocumentObject* obj = objs[i]->getSubObject(subs[i].c_str());
auto* geof = dynamic_cast<App::GeoFeature*>(obj);
if (!geof) {
FC_THROWM(AttachEngineException,
"AttachEngine3D: attached to a non App::GeoFeature '"
<< obj->getNameInDocument() << "'");
}
geofs[i] = geof;
TopoDS_Shape myShape;
Base::Placement plc = App::GeoFeature::getGlobalPlacement(obj, objs[i], fullSub);
if (geof->isDerivedFrom(App::Plane::getClassTypeId())) {
// obtain Z axis and origin of placement
Base::Vector3d norm;
plc.getRotation().multVec(Base::Vector3d(0.0, 0.0, 1.0), norm);
Base::Vector3d org;
plc.multVec(Base::Vector3d(), org);
// make shape - an local-XY plane infinite face
gp_Pln plane = gp_Pln(gp_Pnt(org.x, org.y, org.z), gp_Dir(norm.x, norm.y, norm.z));
myShape = BRepBuilderAPI_MakeFace(plane).Shape();
myShape.Infinite(true);
}
else if (geof->isDerivedFrom(App::Line::getClassTypeId())) {
// obtain X axis and origin of placement
Base::Vector3d dir;
plc.getRotation().multVec(Base::Vector3d(0.0, 0.0, 1.0), dir);
Base::Vector3d org;
plc.multVec(Base::Vector3d(), org);
// make shape - an infinite line along local X axis
gp_Lin line = gp_Lin(gp_Pnt(org.x, org.y, org.z), gp_Dir(dir.x, dir.y, dir.z));
myShape = BRepBuilderAPI_MakeEdge(line).Shape();
myShape.Infinite(true);
}
else if (geof->isDerivedFrom(App::Point::getClassTypeId())) {
Base::Vector3d org;
plc.multVec(Base::Vector3d(), org);
gp_Pnt pnt = gp_Pnt(org.x, org.y, org.z);
myShape = BRepBuilderAPI_MakeVertex(pnt).Shape();
}
else {
try {
Part::TopoShape shape = Part::Feature::getTopoShape(geof, element, true);
for (;;) {
if (shape.isNull()) {
FC_THROWM(AttachEngineException,
"AttachEngine3D: subshape not found "
<< objs[i]->getNameInDocument() << '.' << subs[i]);
}
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);
}
shape.setPlacement(plc);
myShape = shape.getShape();
}
catch (Standard_Failure& e) {
FC_THROWM(AttachEngineException,
"AttachEngine3D: subshape not found " << objs[i]->getNameInDocument()
<< '.' << subs[i] << std::endl
<< e.GetMessageString());
}
catch (Base::CADKernelError& e) {
FC_THROWM(AttachEngineException,
"AttachEngine3D: subshape not found " << objs[i]->getNameInDocument()
<< '.' << subs[i] << std::endl
<< e.what());
}
if (myShape.IsNull()) {
FC_THROWM(AttachEngineException,
"AttachEngine3D: null subshape " << objs[i]->getNameInDocument() << '.'
<< subs[i]);
}
}
storage.emplace_back(myShape);
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]));
if (subs[i].length() == 0) {
types[i] = eRefType(types[i] | rtFlagHasPlacement);
}
}
}
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<App::GeoFeature*> parts;
std::vector<const TopoDS_Shape*> shapes;
std::vector<TopoDS_Shape> copiedShapeStorage;
std::vector<eRefType> types;
readLinks(objs, subs, parts, shapes, copiedShapeStorage, types);
if (parts.empty()) {
throw ExceptionCancel();
}
// common stuff for all map modes
Base::Placement Place = App::GeoFeature::getGlobalPlacement(parts[0], objs[0], subs[0]);
Base::Console().Warning("parts[0] = %s\n", parts[0]->getNameInDocument());
Base::Console().Warning("objs[0] = %s\n", objs[0]->getNameInDocument());
Base::Console().Warning("subs[0] = %s\n", subs[0]);
Base::Console().Warning("Place = (%f, %f, %f)\n", Place.getPosition().x, Place.getPosition().y, Place.getPosition().z);
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];
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]);
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]));
}
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]));
}
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);
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]));
}
catch (...) {
}
if (face.IsNull()) {
throw Base::ValueError(
"Null face in AttachEngine3D::calculateAttachedPlacement()!");
}
TopoDS_Vertex vertex;
try {
vertex = TopoDS::Vertex(*(shapes[1]));
}
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]));
}
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]));
}
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, can't 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;
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]));
}
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])));
// 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])));
dirs[order[i - 1]] = gp_Vec(SketchBasePoint, p);
}
else if (shapes[i]->ShapeType() == TopAbs_EDGE) {
const TopoDS_Edge& e = TopoDS::Edge(*(shapes[i]));
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), D_PI);
}
Base::Placement plm = Base::Placement(
Base::Vector3d(SketchBasePoint.X(), SketchBasePoint.Y(), SketchBasePoint.Z()),
rot);
plm *= this->attachmentOffset;
return plm;
} 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.setUp(*this);
plm = attacher3D._calculateAttachedPlacement(objs,subs,origPlacement);
return plm;
}
//=================================================================================
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<App::GeoFeature*> parts;
std::vector<const TopoDS_Shape*> shapes;
std::vector<TopoDS_Shape> copiedShapeStorage;
std::vector<eRefType> types;
readLinks(objs, subs, parts, shapes, copiedShapeStorage, types);
if (parts.empty()) {
throw ExceptionCancel();
}
// common stuff for all map modes
Base::Placement Place = App::GeoFeature::getGlobalPlacement(parts[0], 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;
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]));
}
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]));
}
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]));
const TopoDS_Face& face2 = TopoDS::Face(*(shapes[1]));
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]), *(shapes[1]));
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<App::GeoFeature*> parts;
std::vector<const TopoDS_Shape*> shapes;
std::vector<TopoDS_Shape> copiedShapeStorage;
std::vector<eRefType> types;
readLinks(objs, subs, parts, shapes, copiedShapeStorage, types);
if (parts.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];
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]));
}
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]), *(shapes[1]));
} 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;
}