diff --git a/src/Mod/Part/App/AppPart.cpp b/src/Mod/Part/App/AppPart.cpp index 5973dd4023..00333c23c9 100644 --- a/src/Mod/Part/App/AppPart.cpp +++ b/src/Mod/Part/App/AppPart.cpp @@ -51,6 +51,7 @@ #include "PartFeatures.h" #include "BodyBase.h" #include "PrimitiveFeature.h" +#include "Attacher.h" #include "Part2DObject.h" #include "CustomFeature.h" #include "TopoShapePy.h" @@ -220,6 +221,7 @@ PyMODINIT_FUNC initPart() Part::Feature ::init(); Part::FeatureExt ::init(); + Part::AttachableObject ::init(); Part::BodyBase ::init(); Part::FeaturePython ::init(); Part::FeatureGeometrySet ::init(); diff --git a/src/Mod/Part/App/AttachableObject.cpp b/src/Mod/Part/App/AttachableObject.cpp new file mode 100644 index 0000000000..2bec3775d9 --- /dev/null +++ b/src/Mod/Part/App/AttachableObject.cpp @@ -0,0 +1,131 @@ +/*************************************************************************** + * Copyright (c) Victor Titov (DeepSOIC) * + * (vv.titov@gmail.com) 2015 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#endif + +#include "AttachableObject.h" + +#include + + + +using namespace Part; +using namespace Attacher; + +PROPERTY_SOURCE(Part::AttachableObject, Part::Feature); + +AttachableObject::AttachableObject() + : _attacher(0) +{ + ADD_PROPERTY_TYPE(Support, (0,0), "Attachment",(App::PropertyType)(App::Prop_None),"Support of the 2D geometry"); + + //It is necessary to default to mmToFlatFace, in order to load old files + ADD_PROPERTY_TYPE(MapMode, (mmFlatFace), "Attachment", App::Prop_None, "Mode of attachment to other object"); + MapMode.setEnums(AttachEngine::eMapModeStrings); + + ADD_PROPERTY_TYPE(MapReversed, (false), "Attachment", App::Prop_None, "Reverse Z direction (flip sketch upside down)"); + + ADD_PROPERTY_TYPE(MapPathParameter, (0.0), "Attachment", App::Prop_None, "Sets point of curve to map the sketch to. 0..1 = start..end"); + + ADD_PROPERTY_TYPE(superPlacement, (Base::Placement()), "Attachment", App::Prop_None, "Extra placement to apply in addition to attachment (in local coordinates)"); + + setAttacher(new AttachEngine3D);//default attacher +} + +AttachableObject::~AttachableObject() +{ + if(_attacher) + delete _attacher; +} + +void AttachableObject::setAttacher(AttachEngine* attacher) +{ + if (_attacher) + delete _attacher; + _attacher = attacher; + updateAttacherVals(); +} + +void AttachableObject::positionBySupport() +{ + if (!_attacher) + return; + updateAttacherVals(); + try{ + this->Placement.setValue(_attacher->calculateAttachedPlacement(this->Placement.getValue())); + } catch (ExceptionCancel) { + //disabled, don't do anything + }; +} + +App::DocumentObjectExecReturn *AttachableObject::execute() +{ + if(this->isTouched_Mapping()) { + try{ + positionBySupport(); + } catch (Base::Exception &e) { + return new App::DocumentObjectExecReturn(e.what()); + } catch (Standard_Failure &e){ + return new App::DocumentObjectExecReturn(e.GetMessageString()); + } + } + return Part::Feature::execute(); +} + +void AttachableObject::onChanged(const App::Property* prop) +{ + if(! this->isRestoring()){ + try{ + if ((prop == &Support + || prop == &MapMode + || prop == &MapPathParameter + || prop == &MapReversed + || prop == &superPlacement)) + positionBySupport(); + } catch (Base::Exception &e) { + this->setError(); + Base::Console().Error("PositionBySupport: &s",e.what()); + //set error message - how? + } catch (Standard_Failure &e){ + this->setError(); + Base::Console().Error("PositionBySupport: &s",e.GetMessageString()); + } + } + Part::Feature::onChanged(prop); +} + +void AttachableObject::updateAttacherVals() +{ + if (!_attacher) + return; + _attacher->setUp(this->Support, + eMapMode(this->MapMode.getValue()), + this->MapReversed.getValue(), + this->MapPathParameter.getValue(), + 0.0,0.0, + this->superPlacement.getValue()); +} + + diff --git a/src/Mod/Part/App/AttachableObject.h b/src/Mod/Part/App/AttachableObject.h new file mode 100644 index 0000000000..485149d28b --- /dev/null +++ b/src/Mod/Part/App/AttachableObject.h @@ -0,0 +1,110 @@ +/*************************************************************************** + * Copyright (c) Victor Titov (DeepSOIC) * + * (vv.titov@gmail.com) 2015 * + * * + * 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 * + * * + ***************************************************************************/ +/** + * AttachableObject.h, .cpp contain a class to derive other features from, to make + * them attachable. + */ + +#ifndef PARTATTACHABLEOBJECT_H +#define PARTATTACHABLEOBJECT_H + +#include +#include +#include +#include +#include +#include + +#include "PartFeature.h" +#include "Attacher.h" + +#include + +#include + +using namespace Attacher; + +namespace Part +{ + +/** + * @brief The AttachableObject class is the thing to be inherited by an object + * that should be attachable. It includes the required properties, and + * shortcuts for accessing the attachment math class. + * + * Todos to make it work: + * - call Attacher::execute() when executing derived object. Make sure to deal + * with its return value, otherwise it will leak memory upon fails. + */ +class PartExport AttachableObject : public Part::Feature +{ + PROPERTY_HEADER(Part::AttachableObject); +public: + AttachableObject(); + ~AttachableObject(); + + /** + * @brief setAttacher sets the AttachEngine object. The class takes the + * ownership of the pointer, it will be deleted when the class is + * destroyed, or when a new attacher is set. The default attacher is AttachEngine3D. + * @param attacher. AttachableObject takes ownership and will delete it eventually. + */ + virtual void setAttacher(AttachEngine* attacher); + AttachEngine* attacher(void) const {return _attacher;} + + /// if the 2DObject lies on the Face of an other object this links to it + App::PropertyLinkSubList Support; + App::PropertyEnumeration MapMode; //see AttachEngine::eMapMode + App::PropertyBool MapReversed; //inverts Z and X internal axes + App::PropertyPlacement superPlacement; + + /** + * @brief MapPathParameter is a parameter value for mmNormalToPath (the + * sketch will be mapped normal to a curve at point specified by parameter + * (from 0.0 to 1.0, from start to end) ) + */ + App::PropertyFloat MapPathParameter; + + /** calculate and update the Placement property based on the Support, and + * mode. Can throw FreeCAD and OCC exceptions. + */ + virtual void positionBySupport(void); + + virtual bool isTouched_Mapping() + {return true; /*support.isTouched isn't true when linked objects are changed... why?..*/}; + + App::DocumentObjectExecReturn *execute(void); +protected: + virtual void onChanged(const App::Property* /*prop*/); + +public: + void updateAttacherVals(); + +private: + AttachEngine* _attacher; +}; + + +} // namespace Part + +#endif // PARTATTACHABLEOBJECT_H diff --git a/src/Mod/Part/App/Attacher.cpp b/src/Mod/Part/App/Attacher.cpp new file mode 100644 index 0000000000..0af157ff1c --- /dev/null +++ b/src/Mod/Part/App/Attacher.cpp @@ -0,0 +1,1045 @@ +/*************************************************************************** + * Copyright (c) Victor Titov (DeepSOIC) * + * (vv.titov@gmail.com) 2015 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif +#include +#include + +#include "Attacher.h" +#include +#include + +using namespace Part; +using namespace Attacher; + +const char* AttachEngine::eMapModeStrings[]= { + "Deactivated", + "Translate", + "ObjectXY", + "ObjectXZ", + "ObjectYZ", + "FlatFace", + "TangentPlane", + "NormalToEdge", + "FrenetNB", + "FrenetTN", + "FrenetTB", + "Concentric", + "SectionOfRevolution", + "ThreePointsPlane", + "ThreePointsNormal", + "Folding", + NULL}; + + +TYPESYSTEM_SOURCE_ABSTRACT(AttachEngine, Base::BaseClass); + +AttachEngine::AttachEngine() +{ + //by default, enable hinting of all modes. + this->modeEnabled.resize(mmDummy_NumberOfModes,true); + modeEnabled[mmDeactivated] = false; + + //fill type lists for modes + modeRefTypes.resize(mmDummy_NumberOfModes); + refTypeString s; + + modeRefTypes[mmTranslate].push_back(cat(rtVertex)); + + s = cat(rtPart); + modeRefTypes[mmObjectXY].push_back(s); + modeRefTypes[mmObjectXZ].push_back(s); + modeRefTypes[mmObjectYZ].push_back(s); + + 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); + + modeRefTypes[mmFolding].push_back(cat(rtLine, rtLine, rtLine, rtLine)); +} + +void AttachEngine::setUp(const App::PropertyLinkSubList &references, + eMapMode mapMode, bool mapReverse, + double attachParameter, + double surfU, double surfV, Base::Placement superPlacement) +{ + this->references.Paste(references); + this->mapMode = mapMode; + this->mapReverse = mapReverse; + this->attachParameter = attachParameter; + this->surfU = surfU; + this->surfV = surfV; + this->superPlacement = superPlacement; +} + +eMapMode AttachEngine::listMapModes(eSuggestResult& msg, + std::vector* allApplicableModes, + std::set* nextRefTypeHint) const +{ + //replace a pointer with a valid reference, to avoid checks for zero pointer everywhere + std::vector buf; + if (allApplicableModes == 0) + allApplicableModes = &buf; + std::vector &mlist = *allApplicableModes; + mlist.clear(); + mlist.reserve(mmDummy_NumberOfModes); + + std::set buf2; + if (nextRefTypeHint == 0) + nextRefTypeHint = &buf2; + std::set &hints = *nextRefTypeHint; + hints.clear(); + + + std::vector parts; + std::vector shapes; + std::vector shapeStorage; + try{ + readLinks(parts, shapes, shapeStorage); + } catch (Base::Exception) { + msg = srLinkBroken; + return mmDeactivated; + } + + //assemble a typelist string representing a set of shapes + std::vector typeStr; + typeStr.resize(shapes.size()); + for( int i = 0 ; i < shapes.size() ; i++){ + typeStr[i] = AttachEngine::getShapeType(*(shapes[i])); + if ( references.getSubValues()[i].length() == 0 + && + !( isShapeOfType(typeStr[i],rtPart) > 0 ) ) { + //if part ( == a whole object, not a subshape) happened to be a + //circular edge, for example, force it to be a part. + typeStr[i] = rtPart; + } + } + + //search valid modes. + eMapMode bestMatchType = mmDeactivated; + int bestMatchScore = -1; + msg = srNoModesFit; + for(int iMode = 0 ; iMode < this->modeRefTypes.size() ; ++iMode){ + if (! this->modeEnabled[iMode]) + continue; + const refTypeStringList &listStrings = modeRefTypes[iMode]; + for( int iStr = 0 ; iStr < listStrings.size() ; ++iStr ){ + int score = 1; //-1 = topo incompatible, 0 = topo compatible, geom incompatible; 1+ = compatible (the higher - the more specific is the mode for the support) + const refTypeString &str = listStrings[iStr]; + for (int 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()) + hints.insert(str[typeStr.size()]); + + //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; + bestMatchType = eMapMode(iMode); + msg = score > 0 ? srOK : srIncompatibleGeometry; + } + } + if (score > 0){ + if(mlist.size() == 0) + mlist.push_back(eMapMode(iMode)); + else if (mlist.back() != eMapMode(iMode)) + mlist.push_back(eMapMode(iMode)); + } + } + } + + return bestMatchType; + +} + +const std::set AttachEngine::getHint(bool forCurrentModeOnly) const +{ + eSuggestResult msg; + std::set ret; + this->listMapModes(msg, 0, &ret); + return ret; +} + +eRefType AttachEngine::getShapeType(const TopoDS_Shape& sh) +{ + 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:{ + TopoDS_Compound cmpd = TopoDS::Compound(cmpd); + TopoDS_Iterator it (cmpd, Standard_False, Standard_False);//don't mess with placements, to hopefully increase speed + if (! it.More()) return rtAnything;//empty compound + 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; + break; + case GeomAbs_Cylinder: + return rtCylindricalFace; + break; + case GeomAbs_Cone: + break; + case GeomAbs_Sphere: + return rtSphericalFace; + break; + case GeomAbs_Torus: + break; + case GeomAbs_BezierSurface: + break; + case GeomAbs_BSplineSurface: + break; + case GeomAbs_SurfaceOfRevolution: + break; + 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; + break; + case GeomAbs_Circle: + return rtCircle; + break; + case GeomAbs_Ellipse: + case GeomAbs_Hyperbola: + case GeomAbs_Parabola: + case GeomAbs_BezierCurve: + case GeomAbs_BSplineCurve: + case GeomAbs_OtherCurve: + return rtCurve; + break; + } + }break; + case TopAbs_WIRE: + return rtWire; + break; + case TopAbs_VERTEX: + return rtVertex; + break; + default: + throw Base::Exception("AttachEngine::getShapeType: unexpected TopoDS_Shape::ShapeType"); + }//switch shapetype + return rtAnything;//shouldn't happen, it's here to shut up compiler warning +} + +eRefType AttachEngine::downgradeType(eRefType type) +{ + switch(type){ + case rtVertex: + case rtEdge: + case rtFace: + return rtAnything; + break; + case rtAnything: + return rtAnything; + break; + case rtLine: + case rtCurve: + return rtEdge; + break; + case rtCircle: + return rtCurve; + break; + case rtFlatFace: + case rtCylindricalFace: + case rtSphericalFace: + return rtFace; + break; + case rtSolid: + case rtWire: + return rtPart; + break; + case rtPart: + return rtAnything; + break; + default: + throw Base::Exception("AttachEngine::downgradeType: unknown type"); + } +} + +int AttachEngine::getTypeRank(eRefType type) +{ + int rank = 0; + while (type != rtAnything) { + type = downgradeType(type); + rank++; + assert(rank<8);//downgrading never yeilds rtAnything, something's wrong with downgrader. + } + return rank; +} + +int AttachEngine::isShapeOfType(eRefType shapeType, eRefType requirement) +{ + 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; +} + +/*! + * \brief AttachEngine3D::readLinks + * \param parts + * \param shapes + * \param storage is a buffer storing what some of the pointers in shapes point to. It is needed, since subshapes are copied in the process (but copying a whole shape of an object can potentially be slow). + */ +void AttachEngine::readLinks(std::vector &geofs, + std::vector &shapes, + std::vector &storage) const +{ + const std::vector &objs = references.getValues(); + const std::vector &sub = references.getSubValues(); + geofs.resize(objs.size()); + storage.reserve(objs.size()); + shapes.resize(objs.size()); + for( int i = 0 ; i < objs.size() ; i++){ + if (!objs[i]->getTypeId().isDerivedFrom(App::GeoFeature::getClassTypeId())){ + throw Base::Exception("AttachEngine3D: link points to something that is not App::GeoFeature"); + } + App::GeoFeature* geof = static_cast(objs[i]); + geofs[i] = geof; + const Part::TopoShape* shape; + if (geof->isDerivedFrom(Part::Feature::getClassTypeId())){ + shape = &(static_cast(geof)->Shape.getShape()); + if (shape->isNull()){ + throw Base::Exception("AttachEngine3D: Part has null shape"); + } + if (sub[i].length()>0){ + try{ + storage.push_back(shape->getSubShape(sub[i].c_str())); + } catch (Standard_Failure){ + throw Base::Exception("AttachEngine3D: subshape not found"); + } + if(storage[storage.size()-1].IsNull()) + throw Base::Exception("AttachEngine3D: null subshape"); + shapes[i] = &(storage[storage.size()-1]); + } else { + shapes[i] = &(shape->_Shape); + } + } else if (geof->isDerivedFrom(App::Plane::getClassTypeId())) { + Base::Vector3d norm; + geof->Placement.getValue().getRotation().multVec(Base::Vector3d(0.0,0.0,1.0),norm); + if (sub[0] == "back") + norm = norm*(-1.0); + Base::Vector3d org; + geof->Placement.getValue().multVec(Base::Vector3d(),org); + gp_Pln pl = gp_Pln(gp_Pnt(org.x, org.y, org.z), gp_Dir(norm.x, norm.y, norm.z)); + BRepBuilderAPI_MakeFace builder(pl); + storage.push_back( builder.Shape() ); + shapes[i] = &(storage[storage.size()-1]); + } + + + } +} + + +//================================================================================= + +TYPESYSTEM_SOURCE(AttachEngine3D, AttachEngine); + +AttachEngine3D::AttachEngine3D() +{ +} + +AttachEngine3D* AttachEngine3D::copy() const +{ + AttachEngine3D* p = new AttachEngine3D; + p->setUp(this->references, + this->mapMode, + this->mapReverse, + this->attachParameter, + this->surfU, this->surfV, + this->superPlacement); + return p; +} + +Base::Placement AttachEngine3D::calculateAttachedPlacement(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 parts; + std::vector shapes; + std::vector copiedShapeStorage; + readLinks(parts, shapes, copiedShapeStorage); + + if (parts.size() == 0) + throw ExceptionCancel(); + + + //common stuff for all map modes + Base::Placement Place = parts[0]->Placement.getValue(); + + //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.size() < 1) + throw Base::Exception("Part2DObject::positionBySupport: no subobjects specified (need one vertex)."); + const TopoDS_Shape &sh = *shapes[0]; + if (sh.IsNull()) + throw Base::Exception("Null face in Part2DObject::positionBySupport()!"); + if (sh.ShapeType() != TopAbs_VERTEX) + throw Base::Exception("Part2DObject::positionBySupport: 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->superPlacement.getPosition()); + plm.setRotation(origPlacement.getRotation()); + return plm; + } break; + case mmObjectXY: + case mmObjectXZ: + case mmObjectYZ:{ + //DeepSOIC: could have been done much more efficiently, but I'm lazy... + 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); + + 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; + } + SketchBasePoint = gp_Pnt(Place.getPosition().x,Place.getPosition().y,Place.getPosition().z); + + } break; + case mmFlatFace:{ + if (shapes.size() < 1) + throw Base::Exception("Part2DObject::positionBySupport: no subobjects specified (needed one planar face)."); + + const TopoDS_Face &face = TopoDS::Face(*(shapes[0])); + if (face.IsNull()) + throw Base::Exception("Null face in Part2DObject::positionBySupport()!"); + + BRepAdaptor_Surface adapt(face); + if (adapt.GetType() != GeomAbs_Plane) + throw Base::Exception("No planar face in Part2DObject::positionBySupport()!"); + + bool Reverse = false; + if (face.Orientation() == TopAbs_REVERSED) + Reverse = true; + + gp_Pln plane = adapt.Plane(); + 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(); + + gp_Pnt ObjOrg(Place.getPosition().x,Place.getPosition().y,Place.getPosition().z); + + Handle (Geom_Plane) gPlane = new Geom_Plane(plane); + GeomAPI_ProjectPointOnSurf projector(ObjOrg,gPlane); + SketchBasePoint = projector.NearestPoint(); + + } break; + case mmTangentPlane: { + if (shapes.size() < 2) + throw Base::Exception("Part2DObject::positionBySupport: not enough subshapes (need one false and one vertex)."); + + bool bThruVertex = false; + if (shapes[0]->ShapeType() == TopAbs_VERTEX && shapes.size()>=2) { + std::swap(shapes[0],shapes[1]); + bThruVertex = true; + } + + const TopoDS_Face &face = TopoDS::Face(*(shapes[0])); + if (face.IsNull()) + throw Base::Exception("Null face in Part2DObject::positionBySupport()!"); + + const TopoDS_Vertex &vertex = TopoDS::Vertex(*(shapes[1])); + if (vertex.IsNull()) + throw Base::Exception("Null vertex in Part2DObject::positionBySupport()!"); + + BRepAdaptor_Surface surf (face); + 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::Exception("Part2DObject::positionBySupport: projecting point onto surface failed."); + projector.LowerDistanceParameters(u, v); + + BRepLProp_SLProps prop(surf,u,v,1, Precision::Confusion()); + SketchNormal = prop.Normal(); + + gp_Dir dirX; + prop.TangentU(dirX); //if normal is defined, this should be defined too + SketchXAxis = gp_Vec(dirX); + + if (face.Orientation() == TopAbs_REVERSED) { + SketchNormal.Reverse(); + SketchXAxis.Reverse(); + } + if (bThruVertex) { + SketchBasePoint = p; + } else { + SketchBasePoint = projector.NearestPoint(); + } + } break; + case mmNormalToPath: + case mmFrenetNB: + case mmFrenetTN: + case mmFrenetTB: + case mmRevolutionSection: + case mmConcentric: {//all alignments to poing on curve + if (shapes.size() < 1) + throw Base::Exception("Part2DObject::positionBySupport: 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; + } + + const TopoDS_Edge &path = TopoDS::Edge(*(shapes[0])); + if (path.IsNull()) + throw Base::Exception("Null path in Part2DObject::positionBySupport()!"); + + BRepAdaptor_Curve adapt(path); + + double u = 0.0; + double u1 = adapt.FirstParameter(); + double u2 = adapt.LastParameter(); + + //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 = TopoDS::Vertex(*(shapes[1])); + if (vertex.IsNull()) + throw Base::Exception("Null vertex in Part2DObject::positionBySupport()!"); + p_in = BRep_Tool::Pnt(vertex); + + Handle (Geom_Curve) hCurve = BRep_Tool::Curve(path, u1, u2); + + GeomAPI_ProjectPointOnCurve projector = GeomAPI_ProjectPointOnCurve (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::SquareConfusion()) { + N.Normalize(); + B = T.Crossed(N); + } else { + Base::Console().Warning("Part2DObject::positionBySupport: path curve second derivative is below 1e-14, can't align x axis.\n"); + N = gp_Vec(0.,0.,0.); + B = gp_Vec(0.,0.,0.);//redundant, just for consistency + } + + //Set origin. Note that it will be overridden later for mmConcentric and mmRevolutionSection + if (bThruVertex) { + SketchBasePoint = p_in; + } else { + SketchBasePoint = p; + } + + 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::Exception("Part2DObject::positionBySupport: 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::Exception("Part2DObject::positionBySupport: Frenet-Serret normal is undefined. Can't align to TB plane."); + SketchNormal = N.Reversed();//it is more convenient to sketch on something looking it 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::Exception("Part2DObject::positionBySupport: 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. + SketchBasePoint = p; + } + + } break; + case mmThreePointsPlane: + case mmThreePointsNormal: { + + std::vector points; + + for( int i = 0 ; i < shapes.size() ; i++){ + const TopoDS_Shape &sh = *shapes[i]; + if (sh.IsNull()) + throw Base::Exception("Null shape in Part2DObject::positionBySupport()!"); + 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); + points.push_back(crv.Value(crv.FirstParameter())); + points.push_back(crv.Value(crv.LastParameter())); + } + if (points.size() >= 3) + break; + } + + if(points.size()<3) + throw Base::Exception("Part2DObject::positionBySupport: 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::Exception("Part2DObject::positionBySupport: 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::Exception("Part2DObject::positionBySupport: 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::Exception("Part2DObject::positionBySupport: 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::Exception("Part2DObject::positionBySupport: 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++){ + edges[i] = &TopoDS::Edge(*(shapes[i])); + if (edges[i]->IsNull()) + throw Base::Exception("Null edge in Part2DObject::positionBySupport()!"); + + adapts[i] = BRepAdaptor_Curve(*(edges[i])); + if (adapts[i].GetType() != GeomAbs_Line) + throw Base::Exception("Part2DObject::positionBySupport: 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::Exception("Part2DObject::positionBySupport: 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::Exception("Part2DObject::positionBySupport: Folding - edges to not share a vertex."); + } + + gp_Vec dirs[4]; + for(int i=0 ; i<4 ; i++){ + assert(abs(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]); + norm.Rotate(gp_Ax1(gp_Pnt(),gp_Dir(dirs[1])),-ang);//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. + SketchNormal = norm.Reversed(); + + SketchXAxis = dirs[1]; + + SketchBasePoint = p; + + } break; + default: + assert(0/*Attachment mode is not implemented?*/); + Base::Console().Error("Attachment mode %i is not implemented.\n", int(mmode)); + throw ExceptionCancel(); + }//switch (MapMode) + + //----------calculate placement, based on point and vector + + gp_Ax3 SketchPos; + if (SketchXAxis.Magnitude() > Precision::Confusion()) { + SketchPos = gp_Ax3(SketchBasePoint, SketchNormal, SketchXAxis); + } else { + //find out, to which axis of support Normal is closest to. + //The result will be written into pos variable (0..2 = X..Z) + 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 = SketchNormal.Dot(dirX); + double cosNY = SketchNormal.Dot(dirY); + double cosNZ = SketchNormal.Dot(dirZ); + std::vector 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) + SketchPos = gp_Ax3(SketchBasePoint, SketchNormal, dirY); + else + SketchPos = gp_Ax3(SketchBasePoint, SketchNormal, -dirY); + } + // +Y/-Y + else if (pos == 1) { + if (cosNY > 0) + SketchPos = gp_Ax3(SketchBasePoint, SketchNormal, -dirX); + else + SketchPos = gp_Ax3(SketchBasePoint, SketchNormal, dirX); + } + // +Z/-Z + else { + SketchPos = gp_Ax3(SketchBasePoint, SketchNormal, dirX); + } + } // if SketchXAxis.Magnitude() > Precision::Confusion + + if(this->mapReverse){ + SketchPos.ZReverse(); + SketchPos.XReverse(); + } + + gp_Trsf Trf; + Trf.SetTransformation(SketchPos); + Trf.Invert(); + Trf.SetScaleFactor(Standard_Real(1.0)); + + Base::Matrix4D mtrx; + TopoShape::convertToMatrix(Trf,mtrx); + + auto plm = Base::Placement(mtrx); + plm *= this->superPlacement; + 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. + //http://forum.freecadweb.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 Base::Exception("calculateFoldAngle: Folding axes are parallel, folding angle cannot be computed."); + norm.Normalize(); + double a = edA.Dot(axA); + double ra = edA.Crossed(axA).Magnitude(); + if (abs(ra) < Precision::Confusion()) + throw Base::Exception("calculateFoldAngle: axisA and edgeA are parallel, folding can't be computed."); + double b = edB.Dot(axB); + double rb = edB.Crossed(axB).Magnitude(); + 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 (abs(cos_unfold)>0.999) + throw Base::Exception("calculateFoldAngle: cosine of folding angle is too close to or above 1."); + return acos(cos_unfold); +} diff --git a/src/Mod/Part/App/Attacher.h b/src/Mod/Part/App/Attacher.h new file mode 100644 index 0000000000..6cdcf852f4 --- /dev/null +++ b/src/Mod/Part/App/Attacher.h @@ -0,0 +1,265 @@ +/*************************************************************************** + * Copyright (c) Victor Titov (DeepSOIC) * + * (vv.titov@gmail.com) 2015 * + * * + * 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 * + * * + ***************************************************************************/ +/** + *Attacher.h, Attacher.cpp contain the functionality of deriving placement + *from a set of geometric subelements. Examples are: sketch attachment, datum + *plane placement. + */ + +#ifndef PARTATTACHER_H +#define PARTATTACHER_H + +#include +#include +#include +#include +#include +#include + +#include "PartFeature.h" + +#include + +#include + +using namespace Part; + +namespace Attacher +{ + +class AttachEngine; + +enum eMapMode { + mmDeactivated, + mmTranslate, + mmObjectXY, + mmObjectXZ, + mmObjectYZ, + mmFlatFace, + mmTangentPlane, + mmNormalToPath, + mmFrenetNB, + mmFrenetTN, + mmFrenetTB, + mmConcentric, + mmRevolutionSection, + mmThreePointsPlane, + mmThreePointsNormal, + mmFolding, + mmDummy_NumberOfModes//a value useful to check the validity of mode value +};//see also eMapModeStrings[] definition in .cpp + +enum eSuggestResult{ + srOK, + srLinkBroken, + srUnexpectedError, + srNoModesFit,//none of the avaliable mapping modes accepts the set of topological type + srIncompatibleGeometry,//there is a mode that could fit, but geometry is wrong (e.g. a line is required, but a curve was passed). +}; + +/** + * @brief The eRefType enum lists the types of references. If adding one, see + * also AttachEngine::getShapeType() and AttachEngine::downgradeType() + */ +enum eRefType { + //topo //ranks: (number of times the type is downgradable) + rtAnything, //0 + rtVertex, //1 + rtEdge, //1 + rtFace, //1 + //edges: + rtLine, //2 + rtCurve, //2 + rtCircle, //3 + //faces: + rtFlatFace, //2 + rtCylindricalFace, //2 + rtSphericalFace, //2 + //shapes: + rtPart, //1 + rtSolid, //2 + rtWire, //2 + rtDummy_numberOfShapeTypes//a value useful to check the validity of value +}; + + +/** + * @brief The AttachEngine class is the placement calculation routine, modes, + * hints and so on. It can be used separately, without deriving from + * AttachableObject. + */ +class PartExport AttachEngine : public Base::BaseClass +{ + TYPESYSTEM_HEADER(); +public: //methods + AttachEngine(); + virtual void setUp(const App::PropertyLinkSubList &references, + eMapMode mapMode = mmDeactivated, + bool mapReverse = false, + double attachParameter = 0.0, + double surfU = 0.0, double surfV = 0.0, + Base::Placement superPlacement = Base::Placement()); + virtual AttachEngine* copy() const = 0; + virtual Base::Placement calculateAttachedPlacement(Base::Placement origPlacement) const = 0; + + /** + * @brief listMapModes is the procedure that knows everything about + * mapping modes. It returns the most appropriate mapping mode, as well as + * list of all modes that will accept the set of references. In case no modes apply, + * extra information regarding reasons is returned in msg. + * + * @param msg (output). Returns a message from the decision logic: OK if + * the mode was chosen, a reason if not. + * + * @param allApplicableModes (output). Pointer to a vector array that will recieve the + * list of all modes that are applicable to the support. It doesn't + * guarantee that all modes will work, it only checks that subelemnts are of + * right type. + * + * @param nextRefTypeHint (output). A hint of what can be added to references. + */ + virtual eMapMode listMapModes(eSuggestResult &msg, + std::vector* allApplicableModes = 0, + std::set* nextRefTypeHint = 0) const; + + /** + * @brief getHint function returns a set of types that user can add to + * references to arrive to combinations valid for some modes. This function + * is a shoutcut to listMapModes. + * + * @return a set of selection types that can be appended to the support. + * + * Subclassing: This function works out of the box via a call to + * listMapModes, so there is no need to reimplement it. + */ + virtual const std::set getHint(bool forCurrentModeOnly) const; + + virtual ~AttachEngine(){}; + +public://helper functions that may be useful outside of the class + static eRefType getShapeType(const TopoDS_Shape &sh); + + /** + * @brief downgradeType converts a more-specific type into a less-specific + * type (e.g. rtCircle->rtCurve, rtCurve->rtEdge, rtEdge->rtAnything) + * @param type + * @return the downgraded type. + */ + static eRefType downgradeType(eRefType type); + + /** + * @brief getTypeRank determines, how specific is the supplied shape type. + * The ranks are outlined in definition of eRefType. The ranks are defined + * by implementation of downgradeType(). + * @param type + * @return number of times the type can be downgradeType() before it + * becomes rtAnything + */ + static int getTypeRank(eRefType type); + + /** + * @brief isShapeOfType tests if a shape fulfills the requirement of a mode, and returns a score of how spot on was the requirement. + * @param shapeType (use return value of AttachEngine::getShapeType) + * @param requirement + * @return : -1 - doesn't fulfill, + * 0 - compatible topology, but incompatible specific (e.g. rtLine, rtCircle); + * 1 - valid by generic type (e.g. rtCircle is rtEdge), + * 2 and up - more and more specific match (according to rank of requirement) + */ + static int isShapeOfType(eRefType shapeType, eRefType requirement); + + +public: //enums + static const char* eMapModeStrings[]; + + +public: //members + App::PropertyLinkSubList references; + + eMapMode mapMode; + bool mapReverse; + double attachParameter; + double surfU, surfV; + Base::Placement superPlacement; + + /** + * @brief modeEnabled is an indicator, whether some mode is ever suggested + * or not. Set to false to suppress suggesting some mode, like so: + * modeEnabled[mmModeIDontLike] = false; + */ + std::vector modeEnabled; + + typedef std::vector refTypeString; //a sequence of ref types, according to Support contents for example + typedef std::vector refTypeStringList; //a set of type strings, defines which selection sets are supported by a certain mode + std::vector modeRefTypes; //a complete data structure, containing info on which modes support what selection + +protected: + refTypeString cat(eRefType rt1){refTypeString ret; ret.push_back(rt1); return ret;} + refTypeString cat(eRefType rt1, eRefType rt2){refTypeString ret; ret.push_back(rt1); ret.push_back(rt2); return ret;} + refTypeString cat(eRefType rt1, eRefType rt2, eRefType rt3){refTypeString ret; ret.push_back(rt1); ret.push_back(rt2); ret.push_back(rt3); return ret;} + refTypeString cat(eRefType rt1, eRefType rt2, eRefType rt3, eRefType rt4){refTypeString ret; ret.push_back(rt1); ret.push_back(rt2); ret.push_back(rt3); ret.push_back(rt4); return ret;} + void readLinks(std::vector &geofs, std::vector& shapes, std::vector &storage) const; + +}; + + +class PartExport AttachEngine3D : public AttachEngine +{ + TYPESYSTEM_HEADER(); +public: + AttachEngine3D(); + virtual AttachEngine3D* copy() const; + virtual Base::Placement calculateAttachedPlacement(Base::Placement origPlacement) const; +private: + double calculateFoldAngle(gp_Vec axA, gp_Vec axB, gp_Vec edA, gp_Vec edB) const; +}; + +typedef AttachEngine3D AttachEnginePlane ;//no separate class for planes, for now. Can be added later, if required. +/* +class AttachEnginePlane : public AttachEngine +{ + AttachEnginePlane(); + virtual AttachEnginePlane* copy() const {return new AttachEnginePlane(*this);} + virtual Base::Placement calculateAttachedPlacement(void) const; + virtual eMapMode listMapModes(eSuggestResult &msg, std::vector* allmodes = 0, std::vector* nextRefTypeHint = 0) const; + ~AttachEnginePlane(){}; +}; +*/ + +//class AttachEnginePoint : public AttachEngine +//{ +// +//}; + + +class ExceptionCancel : public Base::Exception +{ +public: + ExceptionCancel(){} + ExceptionCancel(char* msg){this->setMessage(msg);} + ~ExceptionCancel(){} +}; + +} // namespace Attacher + +#endif // PARTATTACHER_H diff --git a/src/Mod/Part/App/CMakeLists.txt b/src/Mod/Part/App/CMakeLists.txt index 7c499acf11..a223b4d2d6 100644 --- a/src/Mod/Part/App/CMakeLists.txt +++ b/src/Mod/Part/App/CMakeLists.txt @@ -140,6 +140,8 @@ SET(Features_SRCS BodyBase.cpp DatumFeature.cpp DatumFeature.h + AttachableObject.h + AttachableObject.cpp ) SOURCE_GROUP("Features" FILES ${Features_SRCS}) @@ -243,6 +245,8 @@ SET(Part_SRCS ${Features_SRCS} ${Properties_SRCS} ${Python_SRCS} + Attacher.cpp + Attacher.h AppPart.cpp AppPartPy.cpp BSplineCurveBiArcs.cpp diff --git a/src/Mod/Part/App/Part2DObject.cpp b/src/Mod/Part/App/Part2DObject.cpp index 21fb48ccb6..2d27f75648 100644 --- a/src/Mod/Part/App/Part2DObject.cpp +++ b/src/Mod/Part/App/Part2DObject.cpp @@ -59,7 +59,7 @@ const int Part2DObject::H_Axis = -1; const int Part2DObject::V_Axis = -2; const int Part2DObject::N_Axis = -3; -PROPERTY_SOURCE(Part::Part2DObject, Part::Feature) +PROPERTY_SOURCE(Part::Part2DObject, Part::AttachableObject) Part2DObject::Part2DObject() @@ -69,170 +69,7 @@ Part2DObject::Part2DObject() App::DocumentObjectExecReturn *Part2DObject::execute(void) { - return App::DocumentObject::StdReturn; -} - -void Part2DObject::positionBySupport(void) -{ - AttachableObject::positionBySupport(); - - return; - /* - // recalculate support: - Base::Placement Place; - TopoDS_Shape sh; - bool Reverse = false; - gp_Pln plane; - App::DocumentObject* support = Support.getValues(); - if (support == NULL) - return; - - if (support->getTypeId().isDerivedFrom(App::Plane::getClassTypeId())) { - // Find the name of the Baseplane without having to access PartDesignGui::BaseplaneNames[] - Place = static_cast(support)->Placement.getValue(); - Base::Vector3d dir; - Place.getRotation().multVec(Base::Vector3d(0,0,1),dir); - const std::vector &sub = Support.getSubValues(); - if (!sub.empty() && (sub[0] == "back")) - Reverse = true; - - // Set placement identical to the way it used to be done in the Sketcher::SketchOrientationDialog - if (dir == Base::Vector3d(0,0,1)) { - if (Reverse) - Place = Base::Placement(Base::Vector3d(0,0,0),Base::Rotation(-1.0, 0.0,0.0,0.0)); - else - Place = Base::Placement(Base::Vector3d(0,0,0),Base::Rotation()); - } else if (dir == Base::Vector3d(0,1,0)) { - if (Reverse) - Place = Base::Placement(Base::Vector3d(0,0,0),Base::Rotation(Base::Vector3d(0,sqrt(2.0)/2.0,sqrt(2.0)/2.0),M_PI)); - else - Place = Base::Placement(Base::Vector3d(0,0,0),Base::Rotation(Base::Vector3d(-1,0,0),1.5*M_PI)); - } else if (dir == Base::Vector3d(1,0,0)) { - Place = Base::Placement(Base::Vector3d(0,0,0),Base::Rotation(Reverse ? -0.5 : 0.5,0.5,0.5, Reverse ? -0.5 : 0.5)); - } - - if (Reverse) { - dir *= -1.0; - Reverse = false; // We already reversed... - } - - Place.getRotation().multVec(Base::Vector3d(0,0,1),dir); - Base::Vector3d pos = Place.getPosition(); - plane = gp_Pln(gp_Pnt(pos.x, pos.y, pos.z), gp_Dir(dir.x, dir.y, dir.z)); - } else if (support->getTypeId().isDerivedFrom(Part::Datum::getClassTypeId())) { - const std::vector &sub = Support.getSubValues(); - assert(sub.size()==1); - - Part::Datum* pcDatum = static_cast(support); - Place = pcDatum->Placement.getValue(); - Base::Vector3d dir; - Place.getRotation().multVec(Base::Vector3d(0,0,1),dir); - if (!sub.empty() && (sub[0] == "back")) - dir *= -1.0; - Base::Vector3d pos = Place.getPosition(); - plane = gp_Pln(gp_Pnt(pos.x, pos.y, pos.z), gp_Dir(dir.x, dir.y, dir.z)); - } else { - Part::Feature *part = static_cast(support); - if (!part || !part->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) - return; - - Place = part->Placement.getValue(); - const std::vector &sub = Support.getSubValues(); - assert(sub.size()==1); - // get the selected sub shape (a Face) - const Part::TopoShape &shape = part->Shape.getShape(); - if (shape._Shape.IsNull()) - throw Base::Exception("Support shape is empty!"); - - try { - sh = shape.getSubShape(sub[0].c_str()); - } - catch (Standard_Failure) { - throw Base::Exception("Face in support shape doesn't exist!"); - } - - const TopoDS_Face &face = TopoDS::Face(sh); - if (face.IsNull()) - throw Base::Exception("Null face in Part2DObject::positionBySupport()!"); - - BRepAdaptor_Surface adapt(face); - if (adapt.GetType() != GeomAbs_Plane) - throw Base::Exception("No planar face in Part2DObject::positionBySupport()!"); - - if (face.Orientation() == TopAbs_REVERSED) - Reverse = true; - - plane = adapt.Plane(); - 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(); - - gp_Pnt ObjOrg(Place.getPosition().x,Place.getPosition().y,Place.getPosition().z); - - Handle (Geom_Plane) gPlane = new Geom_Plane(plane); - GeomAPI_ProjectPointOnSurf projector(ObjOrg,gPlane); - gp_Pnt SketchBasePoint = projector.NearestPoint(); - - gp_Dir dir = Normal.Direction(); - gp_Ax3 SketchPos; - - Base::Vector3d dX,dY,dZ; - 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 = dir.Dot(dirX); - double cosNY = dir.Dot(dirY); - double cosNZ = dir.Dot(dirZ); - std::vector 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) - SketchPos = gp_Ax3(SketchBasePoint, dir, dirY); - else - SketchPos = gp_Ax3(SketchBasePoint, dir, -dirY); - } - // +Y/-Y - else if (pos == 1) { - if (cosNY > 0) - SketchPos = gp_Ax3(SketchBasePoint, dir, -dirX); - else - SketchPos = gp_Ax3(SketchBasePoint, dir, dirX); - } - // +Z/-Z - else { - SketchPos = gp_Ax3(SketchBasePoint, dir, dirX); - } - - gp_Trsf Trf; - Trf.SetTransformation(SketchPos); - Trf.Invert(); - Trf.SetScaleFactor(Standard_Real(1.0)); - - Base::Matrix4D mtrx; - TopoShape::convertToMatrix(Trf,mtrx); - - // check the angle against the Z Axis - //Standard_Real a = Normal.Angle(gp_Ax1(gp_Pnt(0,0,0),gp_Dir(0,0,1))); - - Placement.setValue(Base::Placement(mtrx)); - */ + return AttachableObject::execute(); } void Part2DObject::transformPlacement(const Base::Placement &transform) @@ -240,8 +77,9 @@ void Part2DObject::transformPlacement(const Base::Placement &transform) if (Support.getValues().size() > 0) { //part->transformPlacement(transform); positionBySupport(); - } else + } else { GeoFeature::transformPlacement(transform); + } } int Part2DObject::getAxisCount(void) const @@ -370,14 +208,6 @@ void Part2DObject::acceptGeometry() // implemented in sub-classes } -void Part2DObject::onChanged(const App::Property* prop) -{ - // Update the Placement if the Support changes - if ((prop == &Support) && (Support.getValues().size() > 0)) - positionBySupport(); - Part::Feature::onChanged(prop); -} - // Python Drawing feature --------------------------------------------------------- namespace App { diff --git a/src/Mod/Part/App/Part2DObject.h b/src/Mod/Part/App/Part2DObject.h index 8fb85dd81b..3c7a62b372 100644 --- a/src/Mod/Part/App/Part2DObject.h +++ b/src/Mod/Part/App/Part2DObject.h @@ -57,8 +57,6 @@ class PartExport Part2DObject : public Part::AttachableObject public: Part2DObject(); - void positionBySupport(); - virtual void transformPlacement(const Base::Placement &transform); /// returns the number of construction lines (to be used as axes) @@ -95,9 +93,6 @@ public: } //@} -protected: - /// get called by the container when a property has changed - virtual void onChanged(const App::Property* /*prop*/); }; typedef App::FeaturePythonT Part2DObjectPython; diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index be79a6144f..f55bf567d3 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -128,8 +128,10 @@ SketchObject::~SketchObject() App::DocumentObjectExecReturn *SketchObject::execute(void) { try { - if (Support.getSize() > 0) - this->positionBySupport(); + App::DocumentObjectExecReturn* rtn = Part2DObject::execute();//to positionBySupport + if(rtn!=App::DocumentObject::StdReturn) + //error + return rtn; } catch (const Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what());