Download src/Mod/Part/App/Attacher.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 116 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Part/App/Attacher.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Part/App/Attacher.cpp
-
curl -L -o Attacher.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Part/App/Attacher.cpp
116 kB
| // 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 * | |
| * * | |
| ***************************************************************************/ | |
| 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; | |
| } | |
| } | |