diff --git a/data/examples/CMakeLists.txt b/data/examples/CMakeLists.txt index 44d9e6908e..2da83cf4d4 100644 --- a/data/examples/CMakeLists.txt +++ b/data/examples/CMakeLists.txt @@ -1,7 +1,7 @@ SET(Examples_Files Schenkel.stp - DrawingExample.FCStd + draft_test_objects.FCStd EngineBlock.FCStd PartDesignExample.FCStd RobotExample.FCStd diff --git a/data/examples/draft_test_objects.FCStd b/data/examples/draft_test_objects.FCStd new file mode 100644 index 0000000000..4293fa4c90 Binary files /dev/null and b/data/examples/draft_test_objects.FCStd differ diff --git a/src/App/DocumentObject.h b/src/App/DocumentObject.h index ca86a0719e..f4202f6eb8 100644 --- a/src/App/DocumentObject.h +++ b/src/App/DocumentObject.h @@ -78,7 +78,7 @@ public: DocumentObjectExecReturn(const char* sWhy, DocumentObject* WhichObject=0) : Which(WhichObject) { - if(sWhy) + if (sWhy) Why = sWhy; } diff --git a/src/App/Extension.cpp b/src/App/Extension.cpp index f0fb3d59f2..fe55f97ca9 100644 --- a/src/App/Extension.cpp +++ b/src/App/Extension.cpp @@ -74,13 +74,13 @@ Extension::~Extension() void Extension::initExtensionType(Base::Type type) { m_extensionType = type; - if(m_extensionType.isBad()) + if (m_extensionType.isBad()) throw Base::RuntimeError("Extension: Extension type not set"); } void Extension::initExtension(ExtensionContainer* obj) { - if(m_extensionType.isBad()) + if (m_extensionType.isBad()) throw Base::RuntimeError("Extension: Extension type not set"); //all properties are initialised without PropertyContainer father. Now that we know it we can @@ -107,13 +107,13 @@ PyObject* Extension::getExtensionPyObject(void) { std::string Extension::name() const { - if(m_extensionType.isBad()) + if (m_extensionType.isBad()) throw Base::RuntimeError("Extension::name: Extension type not set"); std::string temp(m_extensionType.getName()); std::string::size_type pos = temp.find_last_of(':'); - if(pos != std::string::npos) + if (pos != std::string::npos) return temp.substr(pos+1); else return std::string(); diff --git a/src/App/Part.h b/src/App/Part.h index 028f8efe45..c4d7bc5ad6 100644 --- a/src/App/Part.h +++ b/src/App/Part.h @@ -70,7 +70,7 @@ public: //@{ /** Base color of the Item If the transparency value is 1.0 - the color or the next hirachy is used + the color or the next hierarchy is used */ App::PropertyColor Color; //@} diff --git a/src/App/Property.h b/src/App/Property.h index 07df953ffd..be321d346c 100644 --- a/src/App/Property.h +++ b/src/App/Property.h @@ -329,7 +329,7 @@ public: * before, and only then will it call the property's aboutToSetValue(). */ void aboutToChange() { - if(!mProp.hasChanged) { + if (!mProp.hasChanged) { mProp.hasChanged = true; mProp.aboutToSetValue(); } @@ -350,12 +350,12 @@ public: // Must make sure to not throw in a destructor try { mProp.hasSetValue(); - }catch(Base::Exception &e) { + } catch(Base::Exception &e) { e.ReportException(); - }catch(...) {} + } catch(...) {} mProp.hasChanged = false; } - if(mProp.signalCounter>0) + if (mProp.signalCounter>0) mProp.signalCounter--; } @@ -367,9 +367,9 @@ public: // Destructor cannot throw. So we provide this function to allow error // propagation. void tryInvoke() { - if(mProp.signalCounter==1 && mProp.hasChanged) { + if (mProp.signalCounter==1 && mProp.hasChanged) { mProp.hasSetValue(); - if(mProp.signalCounter>0) + if (mProp.signalCounter>0) --mProp.signalCounter; mProp.hasChanged = false; } @@ -500,14 +500,14 @@ public: virtual void set1Value(int index, const_reference value) { int size = getSize(); - if(index<-1 || index>size) + if (index<-1 || index>size) throw Base::RuntimeError("index out of bound"); atomic_change guard(*this); - if(index==-1 || index == size) { + if (index==-1 || index == size) { index = size; setSize(index+1,value); - }else + } else _lValueList[index] = value; this->_touchList.insert(index); guard.tryInvoke(); @@ -517,17 +517,17 @@ protected: void setPyValues(const std::vector &vals, const std::vector &indices) override { - if(indices.empty()) { + if (indices.empty()) { ListT values; values.resize(vals.size()); - for(std::size_t i=0,count=vals.size();i(value); else if (value.type() == typeid(unsigned short)) avalue = boost::any_cast(value); + else if (value.type() == typeid(long)) + avalue = boost::any_cast(value); + else if (value.type() == typeid(unsigned long)) + avalue = boost::any_cast(value); else throw std::bad_cast(); diff --git a/src/App/PropertyStandard.cpp b/src/App/PropertyStandard.cpp index 8eefa6f249..12ed96fbbc 100644 --- a/src/App/PropertyStandard.cpp +++ b/src/App/PropertyStandard.cpp @@ -2119,8 +2119,12 @@ void PropertyBool::setPathValue(const ObjectIdentifier &path, const boost::any & setValue(boost::any_cast(value)); else if (value.type() == typeid(int)) setValue(boost::any_cast(value) != 0); + else if (value.type() == typeid(long)) + setValue(boost::any_cast(value) != 0); else if (value.type() == typeid(double)) setValue(boost::math::round(boost::any_cast(value))); + else if (value.type() == typeid(float)) + setValue(boost::math::round(boost::any_cast(value))); else if (value.type() == typeid(Quantity)) setValue(boost::any_cast(value).getValue() != 0); else diff --git a/src/Base/Builder3D.cpp b/src/Base/Builder3D.cpp index 427e707ddc..58c7403180 100644 --- a/src/Base/Builder3D.cpp +++ b/src/Base/Builder3D.cpp @@ -856,6 +856,13 @@ void InventorBuilder::addCylinder(float radius, float height) << Base::blanks(indent) << "}\n"; } +void InventorBuilder::addSphere(float radius) +{ + result << Base::blanks(indent) << "Sphere {\n" + << Base::blanks(indent) << " radius " << radius << "\n" + << Base::blanks(indent) << "}\n"; +} + void InventorBuilder::addBoundingBox(const Vector3f& pt1, const Vector3f& pt2, short lineWidth, float color_r,float color_g,float color_b) { diff --git a/src/Base/Builder3D.h b/src/Base/Builder3D.h index 8b6800d441..39f936db08 100644 --- a/src/Base/Builder3D.h +++ b/src/Base/Builder3D.h @@ -301,6 +301,7 @@ public: int numUControlPoints, int numVControlPoints, const std::vector& uKnots, const std::vector& vKnots); void addCylinder(float radius, float height); + void addSphere(float radius); //@} /** @name Bounding Box handling */ diff --git a/src/Base/Console.cpp b/src/Base/Console.cpp index b7506d6768..31a1b90d81 100644 --- a/src/Base/Console.cpp +++ b/src/Base/Console.cpp @@ -133,7 +133,7 @@ ConsoleSingleton::ConsoleSingleton(void) ConsoleSingleton::~ConsoleSingleton() { ConsoleOutput::destruct(); - for(std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) + for (std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) delete (*Iter); } @@ -383,32 +383,32 @@ void ConsoleSingleton::DetachObserver(ILogger *pcObserver) void ConsoleSingleton::NotifyMessage(const char *sMsg) { - for(std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { - if((*Iter)->bMsg) + for (std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { + if ((*Iter)->bMsg) (*Iter)->SendLog(sMsg, LogStyle::Message); // send string to the listener } } void ConsoleSingleton::NotifyWarning(const char *sMsg) { - for(std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { - if((*Iter)->bWrn) + for (std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { + if ((*Iter)->bWrn) (*Iter)->SendLog(sMsg, LogStyle::Warning); // send string to the listener } } void ConsoleSingleton::NotifyError(const char *sMsg) { - for(std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { - if((*Iter)->bErr) + for (std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { + if ((*Iter)->bErr) (*Iter)->SendLog(sMsg, LogStyle::Error); // send string to the listener } } void ConsoleSingleton::NotifyLog(const char *sMsg) { - for(std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { - if((*Iter)->bLog) + for (std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { + if ((*Iter)->bLog) (*Iter)->SendLog(sMsg, LogStyle::Log); // send string to the listener } } @@ -416,26 +416,26 @@ void ConsoleSingleton::NotifyLog(const char *sMsg) ILogger *ConsoleSingleton::Get(const char *Name) const { const char* OName; - for(std::set::const_iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { + for (std::set::const_iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { OName = (*Iter)->Name(); // get the name - if(OName && strcmp(OName,Name) == 0) + if (OName && strcmp(OName,Name) == 0) return *Iter; } return 0; } int *ConsoleSingleton::GetLogLevel(const char *tag, bool create) { - if(!tag) tag = ""; - if(_logLevels.find(tag) != _logLevels.end()) + if (!tag) tag = ""; + if (_logLevels.find(tag) != _logLevels.end()) return &_logLevels[tag]; - if(!create) return 0; + if (!create) return 0; int &ret = _logLevels[tag]; ret = -1; return &ret; } void ConsoleSingleton::Refresh() { - if(_bCanRefresh) + if (_bCanRefresh) qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } @@ -459,7 +459,7 @@ void ConsoleSingleton::Destruct(void) ConsoleSingleton & ConsoleSingleton::Instance(void) { // not initialized? - if(!_pcSingleton) + if (!_pcSingleton) { _pcSingleton = new ConsoleSingleton(); } @@ -681,19 +681,19 @@ PyObject *ConsoleSingleton::sPyGetStatus(PyObject * /*self*/, PyObject *args) PY_TRY{ bool b=false; ILogger *pObs = Instance().Get(pstr1); - if(!pObs) + if (!pObs) { Py_INCREF(Py_None); return Py_None; } - if(strcmp(pstr2,"Log") == 0) + if (strcmp(pstr2,"Log") == 0) b = pObs->bLog; - else if(strcmp(pstr2,"Wrn") == 0) + else if (strcmp(pstr2,"Wrn") == 0) b = pObs->bWrn; - else if(strcmp(pstr2,"Msg") == 0) + else if (strcmp(pstr2,"Msg") == 0) b = pObs->bMsg; - else if(strcmp(pstr2,"Err") == 0) + else if (strcmp(pstr2,"Err") == 0) b = pObs->bErr; return Py_BuildValue("i",b?1:0); @@ -710,22 +710,23 @@ PyObject *ConsoleSingleton::sPySetStatus(PyObject * /*self*/, PyObject *args) PY_TRY{ ILogger *pObs = Instance().Get(pstr1); - if(pObs) + if (pObs) { - if(strcmp(pstr2,"Log") == 0) + if (strcmp(pstr2,"Log") == 0) pObs->bLog = (Bool==0)?false:true; - else if(strcmp(pstr2,"Wrn") == 0) + else if (strcmp(pstr2,"Wrn") == 0) pObs->bWrn = (Bool==0)?false:true; - else if(strcmp(pstr2,"Msg") == 0) + else if (strcmp(pstr2,"Msg") == 0) pObs->bMsg = (Bool==0)?false:true; - else if(strcmp(pstr2,"Err") == 0) + else if (strcmp(pstr2,"Err") == 0) pObs->bErr = (Bool==0)?false:true; else Py_Error(Base::BaseExceptionFreeCADError,"Unknown Message Type (use Log, Err, Msg or Wrn)"); Py_INCREF(Py_None); return Py_None; - } else { + } + else { Py_Error(Base::BaseExceptionFreeCADError,"Unknown Console Type"); } @@ -948,8 +949,8 @@ std::stringstream &LogLevel::prefix(std::stringstream &str, const char *src, int { static FC_TIME_POINT s_tstart; static bool s_timing = false; - if(print_time) { - if(!s_timing) { + if (print_time) { + if (!s_timing) { s_timing = true; _FC_TIME_INIT(s_tstart); } @@ -957,10 +958,10 @@ std::stringstream &LogLevel::prefix(std::stringstream &str, const char *src, int auto d = std::chrono::duration_cast(tnow-s_tstart); str << d.count() << ' '; } - if(print_tag) str << '<' << tag << "> "; - if(print_src==2) { + if (print_tag) str << '<' << tag << "> "; + if (print_src==2) { PyFrameObject* frame = PyEval_GetFrame(); - if(frame) { + if (frame) { line = PyFrame_GetLineNumber(frame); #if PY_MAJOR_VERSION >= 3 src = PyUnicode_AsUTF8(frame->f_code->co_filename); @@ -969,7 +970,7 @@ std::stringstream &LogLevel::prefix(std::stringstream &str, const char *src, int #endif } } - if(print_src && src && src[0]) { + if (print_src && src && src[0]) { #ifdef FC_OS_WIN32 const char *_f = std::strrchr(src, '\\'); #else diff --git a/src/Base/Exception.cpp b/src/Base/Exception.cpp index 048b0baf46..522429de6d 100644 --- a/src/Base/Exception.cpp +++ b/src/Base/Exception.cpp @@ -91,14 +91,14 @@ void Exception::ReportException (void) const { if (!_isReported) { const char *msg; - if(_sErrMsg.empty()) + if (_sErrMsg.empty()) msg = typeid(*this).name(); else msg = _sErrMsg.c_str(); #ifdef FC_DEBUG - if(_function.size()) { + if (_function.size()) { _FC_ERR(_file.c_str(),_line, _function << " -- " << msg); - }else + } else #endif _FC_ERR(_file.c_str(),_line,msg); _isReported = true; @@ -319,14 +319,14 @@ void FileException::ReportException (void) const { if (!_isReported) { const char *msg; - if(_sErrMsgAndFileName.empty()) + if (_sErrMsgAndFileName.empty()) msg = typeid(*this).name(); else msg = _sErrMsgAndFileName.c_str(); #ifdef FC_DEBUG - if(_function.size()) { + if (_function.size()) { _FC_ERR(_file.c_str(),_line, _function << " -- " << msg); - }else + } else #endif _FC_ERR(_file.c_str(),_line,msg); _isReported = true; diff --git a/src/Base/Interpreter.cpp b/src/Base/Interpreter.cpp index ad7175014e..03b74aee61 100644 --- a/src/Base/Interpreter.cpp +++ b/src/Base/Interpreter.cpp @@ -82,7 +82,7 @@ PyException::PyException(void) _exceptionType = PP_last_exception_type; - if(PP_last_exception_type) { + if (PP_last_exception_type) { // WARNING: we are assuming that python type object will never be // destroyed, so we don't keep reference here to save book-keeping in // our copy constructor and destructor @@ -123,7 +123,7 @@ void PyException::raiseException() { if (_exceptionType == Base::BaseExceptionFreeCADAbort) edict.setItem("sclassname", Py::String(typeid(Base::AbortException).name())); - if(_isReported) + if (_isReported) edict.setItem("breported", Py::True()); Base::ExceptionFactory::Instance().raiseException(edict.ptr()); } diff --git a/src/Base/MatrixPyImp.cpp b/src/Base/MatrixPyImp.cpp index bb3501d3b2..6c18dfe0ca 100644 --- a/src/Base/MatrixPyImp.cpp +++ b/src/Base/MatrixPyImp.cpp @@ -178,10 +178,10 @@ PyObject * MatrixPy::number_power_handler (PyObject* self, PyObject* other, PyOb Base::Matrix4D a = static_cast(self)->value(); long b = Py::Int(other); - if(!b) + if (!b) return new MatrixPy(Matrix4D()); - if(b < 0) { + if (b < 0) { if (fabs(a.determinant()) > DBL_EPSILON) a.inverseGauss(); else { @@ -192,7 +192,7 @@ PyObject * MatrixPy::number_power_handler (PyObject* self, PyObject* other, PyOb } auto res = a; - for(--b;b;--b) + for (--b;b;--b) res *= a; return new MatrixPy(res); } @@ -303,7 +303,7 @@ PyObject* MatrixPy::scale(PyObject * args) PyObject* MatrixPy::hasScale(PyObject * args) { double tol=0; - if(!PyArg_ParseTuple(args, "|d", &tol)) + if (!PyArg_ParseTuple(args, "|d", &tol)) return 0; return Py::new_reference_to(Py::Int(getMatrixPtr()->hasScale(tol))); } diff --git a/src/Base/Quantity.cpp b/src/Base/Quantity.cpp index 4218fc1957..5fab996453 100644 --- a/src/Base/Quantity.cpp +++ b/src/Base/Quantity.cpp @@ -396,11 +396,11 @@ double num_change(char* yytext,char dez_delim,char grp_delim) double ret_val; char temp[40]; int i = 0; - for(char* c=yytext;*c!='\0';c++){ + for (char* c=yytext;*c!='\0';c++){ // skip group delimiter - if(*c==grp_delim) continue; + if (*c==grp_delim) continue; // check for a dez delimiter other then dot - if(*c==dez_delim && dez_delim !='.') + if (*c==dez_delim && dez_delim !='.') temp[i++] = '.'; else temp[i++] = *c; diff --git a/src/Base/Rotation.cpp b/src/Base/Rotation.cpp index 24bb6840b7..4d4971a6c3 100644 --- a/src/Base/Rotation.cpp +++ b/src/Base/Rotation.cpp @@ -121,7 +121,7 @@ void Rotation::evaluateVector() // Taken from // // Note: -1 < w < +1 (|w| == 1 not allowed, with w:=quat[3]) - if((this->quat[3] > -1.0) && (this->quat[3] < 1.0)) { + if ((this->quat[3] > -1.0) && (this->quat[3] < 1.0)) { double rfAngle = acos(this->quat[3]) * 2.0; double scale = sin(rfAngle / 2.0); // Get a normalized vector @@ -287,7 +287,7 @@ void Rotation::setValue(const Vector3d & rotateFrom, const Vector3d & rotateTo) else { // We can use any axis perpendicular to u (and v) Vector3d t = u % Vector3d(1.0, 0.0, 0.0); - if(t.Length() < Base::Vector3d::epsilon()) + if (t.Length() < Base::Vector3d::epsilon()) t = u % Vector3d(0.0, 1.0, 0.0); this->setValue(t.x, t.y, t.z, 0.0); } @@ -455,7 +455,7 @@ Rotation Rotation::slerp(const Rotation & q0, const Rotation & q1, double t) double scale1 = t; double dot = q0.quat[0]*q1.quat[0]+q0.quat[1]*q1.quat[1]+q0.quat[2]*q1.quat[2]+q0.quat[3]*q1.quat[3]; bool neg=false; - if(dot < 0.0) { + if (dot < 0.0) { dot = -dot; neg = true; } @@ -495,10 +495,10 @@ Rotation Rotation::makeRotationByAxes(Vector3d xdir, Vector3d ydir, Vector3d zdi }; //convert priorityOrder string into a sequence of ints. - if(strlen(priorityOrder)!=3) + if (strlen(priorityOrder)!=3) THROWM(ValueError, "makeRotationByAxes: length of priorityOrder is not 3"); int order[3]; - for(int i = 0; i < 3; ++i){ + for (int i = 0; i < 3; ++i){ order[i] = priorityOrder[i] - 'X'; if (order[i] < 0 || order[i] > 2) THROWM(ValueError, "makeRotationByAxes: characters in priorityOrder must be uppercase X, Y, or Z. Some other character encountered.") @@ -531,7 +531,7 @@ Rotation Rotation::makeRotationByAxes(Vector3d xdir, Vector3d ydir, Vector3d zdi //pick up the strict direction Vector3d mainDir; - for(int i = 0; i < 3; ++i){ + for (int i = 0; i < 3; ++i){ mainDir = *(dirs[order[0]]); if (mainDir.Length() > tol) break; @@ -544,7 +544,7 @@ Rotation Rotation::makeRotationByAxes(Vector3d xdir, Vector3d ydir, Vector3d zdi //pick up the 2nd priority direction, "hint" direction. Vector3d hintDir; - for(int i = 0; i < 2; ++i){ + for (int i = 0; i < 2; ++i){ hintDir = *(dirs[order[1]]); if ((hintDir.Cross(mainDir)).Length() > tol) break; @@ -619,7 +619,7 @@ Rotation Rotation::makeRotationByAxes(Vector3d xdir, Vector3d ydir, Vector3d zdi //build the rotation, by constructing a matrix first. Matrix4D m; m.setToUnity(); - for(int i = 0; i < 3; ++i){ + for (int i = 0; i < 3; ++i){ //matrix indexing: [row][col] m[0][i] = finaldirs[i].x; m[1][i] = finaldirs[i].y; diff --git a/src/Base/Tools.h b/src/Base/Tools.h index 7b0826bcc8..1decb4517d 100644 --- a/src/Base/Tools.h +++ b/src/Base/Tools.h @@ -202,12 +202,12 @@ struct FlagToggler { FlagToggler(Flag &_flag, Flag check) :flag(_flag),toggled(check==_flag) { - if(toggled) + if (toggled) flag = !flag; } ~FlagToggler() { - if(toggled) + if (toggled) flag = !flag; } }; diff --git a/src/Base/Tools2D.cpp b/src/Base/Tools2D.cpp index 973469d36f..b62aeb4a18 100644 --- a/src/Base/Tools2D.cpp +++ b/src/Base/Tools2D.cpp @@ -425,29 +425,29 @@ void Polygon2d::Intersect (const Polygon2d &rclPolygon, std::list &rc } bool Polygon2d::Intersect (const Polygon2d &other) const { - if(other.GetCtVectors()<2 || GetCtVectors() < 2) + if (other.GetCtVectors()<2 || GetCtVectors() < 2) return false; - for(auto &v : _aclVct) { - if(other.Contains(v)) + for (auto &v : _aclVct) { + if (other.Contains(v)) return true; } - if(Contains(other[0])) + if (Contains(other[0])) return true; - for(size_t j=1; j::const_iterator pos; pos = typemap.find(name); - if(pos != typemap.end()) + if (pos != typemap.end()) return typedata[pos->second]->type; else return Type::badType(); @@ -186,7 +186,7 @@ Type Type::fromName(const char *name) Type Type::fromKey(unsigned int key) { - if(key < typedata.size()) + if (key < typedata.size()) return typedata[key]->type; else return Type::badType(); @@ -207,7 +207,7 @@ bool Type::isDerivedFrom(const Type type) const Type temp(*this); do { - if(temp == type) + if (temp == type) return true; temp = temp.getParent(); } while (temp != badType()); @@ -221,7 +221,7 @@ int Type::getAllDerivedFrom(const Type type, std::vector & List) for(std::vector::const_iterator it = typedata.begin();it!= typedata.end();++it) { - if((*it)->type.isDerivedFrom(type)) + if ((*it)->type.isDerivedFrom(type)) { List.push_back((*it)->type); cnt++; diff --git a/src/Base/Unit.cpp b/src/Base/Unit.cpp index 933118700e..f3c63b763f 100644 --- a/src/Base/Unit.cpp +++ b/src/Base/Unit.cpp @@ -355,7 +355,7 @@ QString Unit::getString(void) const } if (Sig.Mass < 0) { - if(mult) + if (mult) ret<<'*'; mult = true; ret << "kg"; @@ -364,7 +364,7 @@ QString Unit::getString(void) const } if (Sig.Time < 0) { - if(mult) + if (mult) ret<<'*'; mult = true; ret << "s"; @@ -373,7 +373,7 @@ QString Unit::getString(void) const } if (Sig.ElectricCurrent < 0) { - if(mult) + if (mult) ret<<'*'; mult = true; ret << "A"; diff --git a/src/Base/UnitPyImp.cpp b/src/Base/UnitPyImp.cpp index 768abd2b30..07ebb12056 100644 --- a/src/Base/UnitPyImp.cpp +++ b/src/Base/UnitPyImp.cpp @@ -26,7 +26,7 @@ std::string UnitPy::representation(void) const ret << Sig.LuminousIntensity << ","; ret << Sig.Angle << ")"; std::string type = getUnitPtr()->getTypeString().toUtf8().constData(); - if(! type.empty()) + if (! type.empty()) ret << " [" << type << "]"; return ret.str(); diff --git a/src/Base/Vector3D.cpp b/src/Base/Vector3D.cpp index 0d0729013f..a0c67d690d 100644 --- a/src/Base/Vector3D.cpp +++ b/src/Base/Vector3D.cpp @@ -192,6 +192,26 @@ Vector3<_Precision> Vector3<_Precision>::Cross(const Vector3<_Precision>& rcVct) return cVctRes; } +template +bool Vector3<_Precision>::IsOnLineSegment (const Vector3<_Precision>& startVct, const Vector3<_Precision>& endVct) const +{ + Vector3<_Precision> vectorAB = endVct - startVct; + Vector3<_Precision> vectorAC = *this - startVct; + Vector3<_Precision> crossproduct = vectorAB.Cross(vectorAC); + _Precision dotproduct = vectorAB.Dot(vectorAC); + + if (crossproduct.Length() > traits_type::epsilon()) + return false; + + if (dotproduct < 0) + return false; + + if (dotproduct > vectorAB.Sqr()) + return false; + + return true; +} + template bool Vector3<_Precision>::operator != (const Vector3<_Precision>& rcVct) const { diff --git a/src/Base/Vector3D.h b/src/Base/Vector3D.h index 4b0f4a414d..13651fc49a 100644 --- a/src/Base/Vector3D.h +++ b/src/Base/Vector3D.h @@ -133,6 +133,9 @@ public: bool operator == (const Vector3<_Precision>& rcVct) const; //@} + /// Check if Vector is on a line segment + bool IsOnLineSegment (const Vector3<_Precision>& startVct, const Vector3<_Precision>& endVct) const; + /** @name Modification */ //@{ void ScaleX (_Precision f); diff --git a/src/Base/VectorPy.xml b/src/Base/VectorPy.xml index 517eff03fd..cd1ce5b710 100644 --- a/src/Base/VectorPy.xml +++ b/src/Base/VectorPy.xml @@ -74,6 +74,13 @@ + + + isOnLineSegment(Vector, Vector) + checks if Vector is on a line segment + + + getAngle(Vector) diff --git a/src/Base/VectorPyImp.cpp b/src/Base/VectorPyImp.cpp index 712315dcb1..778e9907f4 100644 --- a/src/Base/VectorPyImp.cpp +++ b/src/Base/VectorPyImp.cpp @@ -435,6 +435,32 @@ PyObject* VectorPy::cross(PyObject *args) return new VectorPy(v); } +PyObject* VectorPy::isOnLineSegment(PyObject *args) +{ + PyObject *start, *end; + if (!PyArg_ParseTuple(args, "OO",&start, &end)) + return 0; + if (!PyObject_TypeCheck(start, &(VectorPy::Type))) { + PyErr_SetString(PyExc_TypeError, "First arg must be Vector"); + return 0; + } + if (!PyObject_TypeCheck(end, &(VectorPy::Type))) { + PyErr_SetString(PyExc_TypeError, "Second arg must be Vector"); + return 0; + } + + VectorPy* start_vec = static_cast(start); + VectorPy* end_vec = static_cast(end); + + VectorPy::PointerType this_ptr = reinterpret_cast(_pcTwinPointer); + VectorPy::PointerType start_ptr = reinterpret_cast(start_vec->_pcTwinPointer); + VectorPy::PointerType end_ptr = reinterpret_cast(end_vec->_pcTwinPointer); + + Py::Boolean result = this_ptr->IsOnLineSegment(*start_ptr, *end_ptr); + + return Py::new_reference_to(result); +} + PyObject* VectorPy::getAngle(PyObject *args) { PyObject *obj; diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index eddfa41b0c..6f20a208e6 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -523,21 +523,23 @@ void Application::open(const char* FileName, const char* Module) if (Module != 0) { try { - if(File.hasExtension("FCStd")) { + if (File.hasExtension("FCStd")) { bool handled = false; std::string filepath = File.filePath(); - for(auto &v : d->documents) { + for (auto &v : d->documents) { auto doc = v.second->getDocument(); std::string fi = Base::FileInfo(doc->FileName.getValue()).filePath(); - if(filepath == fi) { + if (filepath == fi) { handled = true; Command::doCommand(Command::App, "FreeCADGui.reload('%s')", doc->getName()); break; } } - if(!handled) - Command::doCommand(Command::App, "FreeCAD.openDocument('%s')", FileName); - } else { + + if (!handled) + Command::doCommand(Command::App, "FreeCAD.openDocument('%s')", unicodepath.c_str()); + } + else { // issue module loading Command::doCommand(Command::App, "import %s", Module); diff --git a/src/Gui/Application.h b/src/Gui/Application.h index df5df231c8..8925f29ee6 100644 --- a/src/Gui/Application.h +++ b/src/Gui/Application.h @@ -258,7 +258,9 @@ public: static PyObject* sRunCommand (PyObject *self,PyObject *args); static PyObject* sAddCommand (PyObject *self,PyObject *args); + static PyObject* sGetCommandInfo (PyObject *self,PyObject *args); static PyObject* sListCommands (PyObject *self,PyObject *args); + static PyObject* sGetCommandShortcut (PyObject *self,PyObject *args); static PyObject* sIsCommandActive (PyObject *self,PyObject *args); static PyObject* sUpdateCommands (PyObject *self,PyObject *args); diff --git a/src/Gui/ApplicationPy.cpp b/src/Gui/ApplicationPy.cpp index cf059d2906..894f76ff58 100644 --- a/src/Gui/ApplicationPy.cpp +++ b/src/Gui/ApplicationPy.cpp @@ -144,6 +144,12 @@ PyMethodDef Application::Methods[] = { {"listCommands", (PyCFunction) Application::sListCommands, METH_VARARGS, "listCommands() -> list of strings\n\n" "Returns a list of all commands known to FreeCAD."}, + {"getCommandInfo", (PyCFunction) Application::sGetCommandInfo, METH_VARARGS, + "getCommandInfo(string) -> list of strings\n\n" + "Usage: menuText,tooltipText,whatsThisText,statustipText,pixmapText,shortcutText = getCommandInfo(string)"}, + {"getCommandShortcut", (PyCFunction) Application::sGetCommandShortcut, METH_VARARGS, + "getCommandShortcut(string) -> string\n\n" + "Returns string representing shortcut key accelerator for command."}, {"updateCommands", (PyCFunction) Application::sUpdateCommands, METH_VARARGS, "updateCommands\n\n" "Update all command active status"}, @@ -1273,6 +1279,73 @@ PyObject* Application::sUpdateCommands(PyObject * /*self*/, PyObject *args) Py_Return; } +PyObject* Application::sGetCommandShortcut(PyObject * /*self*/, PyObject *args) +{ + char* pName; + if (!PyArg_ParseTuple(args, "s", &pName)) + return NULL; + + Command* cmd = Application::Instance->commandManager().getCommandByName(pName); + if (cmd) { + +#if PY_MAJOR_VERSION >= 3 + PyObject* str = PyUnicode_FromString(cmd->getAccel() ? cmd->getAccel() : ""); +#else + PyObject* str = PyString_FromString(cmd->getAccel() ? cmd->getAccel() : ""); +#endif + return str; + } + else { + PyErr_Format(Base::BaseExceptionFreeCADError, "No such command '%s'", pName); + return 0; + } +} + +PyObject* Application::sGetCommandInfo(PyObject * /*self*/, PyObject *args) +{ + char* pName; + if (!PyArg_ParseTuple(args, "s", &pName)) + return NULL; + + Command* cmd = Application::Instance->commandManager().getCommandByName(pName); + if (cmd) { + PyObject* pyList = PyList_New(6); + const char* menuTxt = cmd->getMenuText(); + const char* tooltipTxt = cmd->getToolTipText(); + const char* whatsThisTxt = cmd->getWhatsThis(); + const char* statustipTxt = cmd->getStatusTip(); + const char* pixMapTxt = cmd->getPixmap(); + const char* shortcutTxt = cmd->getAccel(); + +#if PY_MAJOR_VERSION >= 3 + PyObject* strMenuTxt = PyUnicode_FromString(menuTxt ? menuTxt : ""); + PyObject* strTooltipTxt = PyUnicode_FromString(tooltipTxt ? tooltipTxt : ""); + PyObject* strWhatsThisTxt = PyUnicode_FromString(whatsThisTxt ? whatsThisTxt : ""); + PyObject* strStatustipTxt = PyUnicode_FromString(statustipTxt ? statustipTxt : ""); + PyObject* strPixMapTxt = PyUnicode_FromString(pixMapTxt ? pixMapTxt : ""); + PyObject* strShortcutTxt = PyUnicode_FromString(shortcutTxt ? shortcutTxt : ""); +#else + PyObject* strMenuTxt = PyString_FromString(menuTxt ? menuTxt : ""); + PyObject* strTooltipTxt = PyString_FromString(tooltipTxt ? tooltipTxt : ""); + PyObject* strWhatsThisTxt = PyString_FromString(whatsThisTxt ? whatsThisTxt : ""); + PyObject* strStatustipTxt = PyString_FromString(statustipTxt ? statustipTxt : ""); + PyObject* strPixMapTxt = PyString_FromString(pixMapTxt ? pixMapTxt : ""); + PyObject* strShortcutTxt = PyString_FromString(shortcutTxt ? shortcutTxt : ""); +#endif + PyList_SetItem(pyList, 0, strMenuTxt); + PyList_SetItem(pyList, 1, strTooltipTxt); + PyList_SetItem(pyList, 2, strWhatsThisTxt); + PyList_SetItem(pyList, 3, strStatustipTxt); + PyList_SetItem(pyList, 4, strPixMapTxt); + PyList_SetItem(pyList, 5, strShortcutTxt); + return pyList; + } + else { + PyErr_Format(Base::BaseExceptionFreeCADError, "No such command '%s'", pName); + return 0; + } +} + PyObject* Application::sListCommands(PyObject * /*self*/, PyObject *args) { if (!PyArg_ParseTuple(args, "")) diff --git a/src/Gui/CommandDoc.cpp b/src/Gui/CommandDoc.cpp index c1beaad45a..a79ad6a289 100644 --- a/src/Gui/CommandDoc.cpp +++ b/src/Gui/CommandDoc.cpp @@ -1503,7 +1503,7 @@ StdCmdEdit::StdCmdEdit() sToolTipText = QT_TR_NOOP("Toggles the selected object's edit mode"); sWhatsThis = "Std_Edit"; sStatusTip = QT_TR_NOOP("Activates or Deactivates the selected object's edit mode"); - sAccel = "Shift+E"; + sAccel = ""; #if QT_VERSION >= 0x040200 sPixmap = "edit-edit"; #endif diff --git a/src/Gui/DlgSettings3DView.ui b/src/Gui/DlgSettings3DView.ui index 07024daf4e..25c45f3814 100644 --- a/src/Gui/DlgSettings3DView.ui +++ b/src/Gui/DlgSettings3DView.ui @@ -214,6 +214,45 @@ but slower response to any scene changes. + + + + + + Transparent objects: + + + + + + + + 120 + 0 + + + + Render types of transparent objects + + + TransparentObjectRenderType + + + View + + + + One pass + + + + + Backface pass + + + + + @@ -223,7 +262,7 @@ but slower response to any scene changes. - + @@ -236,14 +275,14 @@ but slower response to any scene changes. - + Eye to eye distance for stereo modes - + @@ -279,7 +318,7 @@ bounding box size of the 3D object that is currently displayed. - + Backlight is enabled with the defined color @@ -295,7 +334,7 @@ bounding box size of the 3D object that is currently displayed. - + false @@ -318,7 +357,7 @@ bounding box size of the 3D object that is currently displayed. - + Qt::Horizontal @@ -331,7 +370,7 @@ bounding box size of the 3D object that is currently displayed. - + false @@ -347,7 +386,7 @@ bounding box size of the 3D object that is currently displayed. - + false diff --git a/src/Gui/DlgSettings3DViewImp.cpp b/src/Gui/DlgSettings3DViewImp.cpp index 6365e0e547..90db0ba8ce 100644 --- a/src/Gui/DlgSettings3DViewImp.cpp +++ b/src/Gui/DlgSettings3DViewImp.cpp @@ -83,6 +83,8 @@ void DlgSettings3DViewImp::saveSettings() index = ui->renderCache->currentIndex(); hGrp->SetInt("RenderCache", index); + ui->comboTransparentRender->onSave(); + QVariant const &vBoxMarkerSize = ui->boxMarkerSize->itemData(ui->boxMarkerSize->currentIndex()); hGrp->SetInt("MarkerSize", vBoxMarkerSize.toInt()); @@ -124,6 +126,8 @@ void DlgSettings3DViewImp::loadSettings() index = hGrp->GetInt("RenderCache", 0); ui->renderCache->setCurrentIndex(index); + ui->comboTransparentRender->onRestore(); + int const current = hGrp->GetInt("MarkerSize", 9L); ui->boxMarkerSize->addItem(tr("5px"), QVariant(5)); ui->boxMarkerSize->addItem(tr("7px"), QVariant(7)); diff --git a/src/Gui/FreeCADGuiInit.py b/src/Gui/FreeCADGuiInit.py index 73a5922d16..8c12f88085 100644 --- a/src/Gui/FreeCADGuiInit.py +++ b/src/Gui/FreeCADGuiInit.py @@ -37,125 +37,131 @@ Gui = FreeCADGui # Important definitions class Workbench: - """The workbench base class.""" - MenuText = "" - ToolTip = "" - - def Initialize(self): - """Initializes this workbench.""" - App.PrintWarning(str(self) + ": Workbench.Initialize() not implemented in subclass!") - def ContextMenu(self, recipient): - pass - def appendToolbar(self,name,cmds): - self.__Workbench__.appendToolbar(name, cmds) - def removeToolbar(self,name): - self.__Workbench__.removeToolbar(name) - def appendCommandbar(self,name,cmds): - self.__Workbench__.appendCommandbar(name, cmds) - def removeCommandbar(self,name): - self.__Workbench__.removeCommandbar(name) - def appendMenu(self,name,cmds): - self.__Workbench__.appendMenu(name, cmds) - def removeMenu(self,name): - self.__Workbench__.removeMenu(name) - def listMenus(self): - return self.__Workbench__.listMenus() - def appendContextMenu(self,name,cmds): - self.__Workbench__.appendContextMenu(name, cmds) - def removeContextMenu(self,name): - self.__Workbench__.removeContextMenu(name) - def name(self): - return self.__Workbench__.name() - def GetClassName(self): - """Return the name of the associated C++ class.""" - # as default use this to simplify writing workbenches in Python - return "Gui::PythonWorkbench" + """The workbench base class.""" + MenuText = "" + ToolTip = "" + + def Initialize(self): + """Initializes this workbench.""" + App.PrintWarning(str(self) + ": Workbench.Initialize() not implemented in subclass!") + def ContextMenu(self, recipient): + pass + def appendToolbar(self,name,cmds): + self.__Workbench__.appendToolbar(name, cmds) + def removeToolbar(self,name): + self.__Workbench__.removeToolbar(name) + def listToolbars(self): + return self.__Workbench__.listToolbars() + def getToolbarItems(self): + return self.__Workbench__.getToolbarItems() + def appendCommandbar(self,name,cmds): + self.__Workbench__.appendCommandbar(name, cmds) + def removeCommandbar(self,name): + self.__Workbench__.removeCommandbar(name) + def listCommandbars(self): + return self.__Workbench__.listCommandbars() + def appendMenu(self,name,cmds): + self.__Workbench__.appendMenu(name, cmds) + def removeMenu(self,name): + self.__Workbench__.removeMenu(name) + def listMenus(self): + return self.__Workbench__.listMenus() + def appendContextMenu(self,name,cmds): + self.__Workbench__.appendContextMenu(name, cmds) + def removeContextMenu(self,name): + self.__Workbench__.removeContextMenu(name) + def name(self): + return self.__Workbench__.name() + def GetClassName(self): + """Return the name of the associated C++ class.""" + # as default use this to simplify writing workbenches in Python + return "Gui::PythonWorkbench" class StandardWorkbench ( Workbench ): - """A workbench defines the tool bars, command bars, menus, + """A workbench defines the tool bars, command bars, menus, context menu and dockable windows of the main window. - """ - def Initialize(self): - """Initialize this workbench.""" - # load the module - Log ('Init: Loading FreeCAD GUI\n') - def GetClassName(self): - """Return the name of the associated C++ class.""" - return "Gui::StdWorkbench" + """ + def Initialize(self): + """Initialize this workbench.""" + # load the module + Log ('Init: Loading FreeCAD GUI\n') + def GetClassName(self): + """Return the name of the associated C++ class.""" + return "Gui::StdWorkbench" class NoneWorkbench ( Workbench ): - """An empty workbench.""" - MenuText = "" - ToolTip = "The default empty workbench" - def Initialize(self): - """Initialize this workbench.""" - # load the module - Log ('Init: Loading FreeCAD GUI\n') - def GetClassName(self): - """Return the name of the associated C++ class.""" - return "Gui::NoneWorkbench" + """An empty workbench.""" + MenuText = "" + ToolTip = "The default empty workbench" + def Initialize(self): + """Initialize this workbench.""" + # load the module + Log ('Init: Loading FreeCAD GUI\n') + def GetClassName(self): + """Return the name of the associated C++ class.""" + return "Gui::NoneWorkbench" def InitApplications(): - import sys,os,traceback - try: - # Python3 - import io as cStringIO - except ImportError: - # Python2 - import cStringIO - # Searching modules dirs +++++++++++++++++++++++++++++++++++++++++++++++++++ - # (additional module paths are already cached) - ModDirs = FreeCAD.__ModDirs__ - #print ModDirs - Log('Init: Searching modules...\n') - for Dir in ModDirs: - if ((Dir != '') & (Dir != 'CVS') & (Dir != '__init__.py')): - InstallFile = os.path.join(Dir,"InitGui.py") - if (os.path.exists(InstallFile)): - try: - # XXX: This looks scary securitywise... - with open(InstallFile) as f: - exec(f.read()) - except Exception as inst: - Log('Init: Initializing ' + Dir + '... failed\n') - Log('-'*100+'\n') - Log(traceback.format_exc()) - Log('-'*100+'\n') - Err('During initialization the error "' + str(inst) + '" occurred in ' + InstallFile + '\n') - Err('Please look into the log file for further information\n') - else: - Log('Init: Initializing ' + Dir + '... done\n') - else: - Log('Init: Initializing ' + Dir + '(InitGui.py not found)... ignore\n') + import sys,os,traceback + try: + # Python3 + import io as cStringIO + except ImportError: + # Python2 + import cStringIO + # Searching modules dirs +++++++++++++++++++++++++++++++++++++++++++++++++++ + # (additional module paths are already cached) + ModDirs = FreeCAD.__ModDirs__ + #print ModDirs + Log('Init: Searching modules...\n') + for Dir in ModDirs: + if ((Dir != '') & (Dir != 'CVS') & (Dir != '__init__.py')): + InstallFile = os.path.join(Dir,"InitGui.py") + if (os.path.exists(InstallFile)): + try: + # XXX: This looks scary securitywise... + with open(InstallFile) as f: + exec(f.read()) + except Exception as inst: + Log('Init: Initializing ' + Dir + '... failed\n') + Log('-'*100+'\n') + Log(traceback.format_exc()) + Log('-'*100+'\n') + Err('During initialization the error "' + str(inst) + '" occurred in ' + InstallFile + '\n') + Err('Please look into the log file for further information\n') + else: + Log('Init: Initializing ' + Dir + '... done\n') + else: + Log('Init: Initializing ' + Dir + '(InitGui.py not found)... ignore\n') - try: - import pkgutil - import importlib - import freecad - freecad.gui = FreeCADGui - for _, freecad_module_name, freecad_module_ispkg in pkgutil.iter_modules(freecad.__path__, "freecad."): - if freecad_module_ispkg: - Log('Init: Initializing ' + freecad_module_name + '\n') - try: - freecad_module = importlib.import_module(freecad_module_name) - if any (module_name == 'init_gui' for _, module_name, ispkg in pkgutil.iter_modules(freecad_module.__path__)): - importlib.import_module(freecad_module_name + '.init_gui') - Log('Init: Initializing ' + freecad_module_name + '... done\n') - else: - Log('Init: No init_gui module found in ' + freecad_module_name + ', skipping\n') - except Exception as inst: - Err('During initialization the error "' + str(inst) + '" occurred in ' + freecad_module_name + '\n') - Err('-'*80+'\n') - Err(traceback.format_exc()) - Err('-'*80+'\n') - Log('Init: Initializing ' + freecad_module_name + '... failed\n') - Log('-'*80+'\n') - Log(traceback.format_exc()) - Log('-'*80+'\n') - except ImportError as inst: - Err('During initialization the error "' + str(inst) + '" occurred\n') + try: + import pkgutil + import importlib + import freecad + freecad.gui = FreeCADGui + for _, freecad_module_name, freecad_module_ispkg in pkgutil.iter_modules(freecad.__path__, "freecad."): + if freecad_module_ispkg: + Log('Init: Initializing ' + freecad_module_name + '\n') + try: + freecad_module = importlib.import_module(freecad_module_name) + if any (module_name == 'init_gui' for _, module_name, ispkg in pkgutil.iter_modules(freecad_module.__path__)): + importlib.import_module(freecad_module_name + '.init_gui') + Log('Init: Initializing ' + freecad_module_name + '... done\n') + else: + Log('Init: No init_gui module found in ' + freecad_module_name + ', skipping\n') + except Exception as inst: + Err('During initialization the error "' + str(inst) + '" occurred in ' + freecad_module_name + '\n') + Err('-'*80+'\n') + Err(traceback.format_exc()) + Err('-'*80+'\n') + Log('Init: Initializing ' + freecad_module_name + '... failed\n') + Log('-'*80+'\n') + Log(traceback.format_exc()) + Log('-'*80+'\n') + except ImportError as inst: + Err('During initialization the error "' + str(inst) + '" occurred\n') Log ('Init: Running FreeCADGuiInit.py start script...\n') diff --git a/src/Gui/PythonWorkbenchPy.xml b/src/Gui/PythonWorkbenchPy.xml index 393e4226b8..9232ff9e86 100644 --- a/src/Gui/PythonWorkbenchPy.xml +++ b/src/Gui/PythonWorkbenchPy.xml @@ -23,11 +23,6 @@ Remove a menu - - - Show a list of all menus - - Append a new context menu item @@ -48,11 +43,6 @@ Remove a toolbar - - - Show a list of all toolbars - - Append a new command bar @@ -63,66 +53,61 @@ Remove a command bar - - - Show a list of all command bars - + + + deprecated -- use appendMenu + - - - deprecated -- use appendMenu - - - - - deprecated -- use removeMenu - - - - - deprecated -- use listMenus - - - - - deprecated -- use appendContextMenu - - - - - deprecated -- use removeContextMenu - - - - - deprecated -- use appendToolbar - - - - - deprecated -- use removeToolbar - - - - - deprecated -- use listToolbars - - - - - deprecated -- use appendCommandBar - - - - - deprecated -- use removeCommandBar - - - - - deprecated -- use listCommandBars - - - + + + deprecated -- use removeMenu + + + + + deprecated -- use listMenus + + + + + deprecated -- use appendContextMenu + + + + + deprecated -- use removeContextMenu + + + + + deprecated -- use appendToolbar + + + + + deprecated -- use removeToolbar + + + + + deprecated -- use listToolbars + + + + + deprecated -- use appendCommandBar + + + + + deprecated -- use removeCommandBar + + + + + deprecated -- use listCommandBars + + + - + diff --git a/src/Gui/PythonWorkbenchPyImp.cpp b/src/Gui/PythonWorkbenchPyImp.cpp index ee7222c5af..54c5a7e7a4 100644 --- a/src/Gui/PythonWorkbenchPyImp.cpp +++ b/src/Gui/PythonWorkbenchPyImp.cpp @@ -54,7 +54,7 @@ PyObject* PythonWorkbenchPy::appendMenu(PyObject *args) PyObject* pPath; PyObject* pItems; if ( !PyArg_ParseTuple(args, "OO", &pPath, &pItems) ) - return NULL; // NULL triggers exception + return nullptr; // menu path std::list path; @@ -95,7 +95,7 @@ PyObject* PythonWorkbenchPy::appendMenu(PyObject *args) #endif } else { PyErr_SetString(PyExc_AssertionError, "Expected either a string or a stringlist as first argument"); - return NULL; // NULL triggers exception + return nullptr; } // menu items @@ -137,7 +137,7 @@ PyObject* PythonWorkbenchPy::appendMenu(PyObject *args) #endif } else { PyErr_SetString(PyExc_AssertionError, "Expected either a string or a stringlist as first argument"); - return NULL; // NULL triggers exception + return nullptr; } getPythonBaseWorkbenchPtr()->appendMenu( path, items ); @@ -151,37 +151,14 @@ PyObject* PythonWorkbenchPy::removeMenu(PyObject *args) { PY_TRY { char *psMenu; - if (!PyArg_ParseTuple(args, "s", &psMenu)) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "s", &psMenu)) + return nullptr; getPythonBaseWorkbenchPtr()->removeMenu( psMenu ); Py_Return; } PY_CATCH; } -/** Shows a list of all menus */ -PyObject* PythonWorkbenchPy::listMenus(PyObject *args) -{ - PY_TRY { - if (!PyArg_ParseTuple(args, "")) - return NULL; - - std::list menus = getPythonBaseWorkbenchPtr()->listMenus(); - - PyObject* pyList = PyList_New(menus.size()); - int i=0; - for (std::list::iterator it = menus.begin(); it != menus.end(); ++it, ++i ) { -#if PY_MAJOR_VERSION >= 3 - PyObject* str = PyUnicode_FromString(it->c_str()); -#else - PyObject* str = PyString_FromString(it->c_str()); -#endif - PyList_SetItem(pyList, i, str); - } - return pyList; - } PY_CATCH; -} - /** Appends new context menu items */ PyObject* PythonWorkbenchPy::appendContextMenu(PyObject *args) { @@ -189,7 +166,7 @@ PyObject* PythonWorkbenchPy::appendContextMenu(PyObject *args) PyObject* pPath; PyObject* pItems; if ( !PyArg_ParseTuple(args, "OO", &pPath, &pItems) ) - return NULL; // NULL triggers exception + return nullptr; // menu path std::list path; @@ -230,7 +207,7 @@ PyObject* PythonWorkbenchPy::appendContextMenu(PyObject *args) #endif } else { PyErr_SetString(PyExc_AssertionError, "Expected either a string or a stringlist as first argument"); - return NULL; // NULL triggers exception + return nullptr; } // menu items @@ -272,7 +249,7 @@ PyObject* PythonWorkbenchPy::appendContextMenu(PyObject *args) #endif } else { PyErr_SetString(PyExc_AssertionError, "Expected either a string or a stringlist as first argument"); - return NULL; // NULL triggers exception + return nullptr; } getPythonBaseWorkbenchPtr()->appendContextMenu( path, items ); @@ -286,8 +263,8 @@ PyObject* PythonWorkbenchPy::removeContextMenu(PyObject *args) { PY_TRY { char *psMenu; - if (!PyArg_ParseTuple(args, "s", &psMenu)) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "s", &psMenu)) + return nullptr; getPythonBaseWorkbenchPtr()->removeContextMenu( psMenu ); Py_Return; @@ -301,10 +278,10 @@ PyObject* PythonWorkbenchPy::appendToolbar(PyObject *args) PyObject* pObject; char* psToolBar; if ( !PyArg_ParseTuple(args, "sO", &psToolBar, &pObject) ) - return NULL; // NULL triggers exception + return nullptr; if (!PyList_Check(pObject)) { PyErr_SetString(PyExc_AssertionError, "Expected a list as second argument"); - return NULL; // NULL triggers exception + return nullptr; } std::list items; @@ -339,37 +316,14 @@ PyObject* PythonWorkbenchPy::removeToolbar(PyObject *args) { PY_TRY { char *psToolBar; - if (!PyArg_ParseTuple(args, "s", &psToolBar)) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "s", &psToolBar)) + return nullptr; getPythonBaseWorkbenchPtr()->removeToolbar( psToolBar ); Py_Return; } PY_CATCH; } -/** Shows a list of all toolbars */ -PyObject* PythonWorkbenchPy::listToolbars(PyObject *args) -{ - PY_TRY { - if (!PyArg_ParseTuple(args, "")) - return NULL; - - std::list bars = getPythonBaseWorkbenchPtr()->listToolbars(); - - PyObject* pyList = PyList_New(bars.size()); - int i=0; - for (std::list::iterator it = bars.begin(); it != bars.end(); ++it, ++i ) { -#if PY_MAJOR_VERSION >= 3 - PyObject* str = PyUnicode_FromString(it->c_str()); -#else - PyObject* str = PyString_FromString(it->c_str()); -#endif - PyList_SetItem(pyList, i, str); - } - return pyList; - } PY_CATCH; -} - /** Appends a new command bar */ PyObject* PythonWorkbenchPy::appendCommandbar(PyObject *args) { @@ -377,10 +331,10 @@ PyObject* PythonWorkbenchPy::appendCommandbar(PyObject *args) PyObject* pObject; char* psToolBar; if ( !PyArg_ParseTuple(args, "sO", &psToolBar, &pObject) ) - return NULL; // NULL triggers exception + return nullptr; if (!PyList_Check(pObject)) { PyErr_SetString(PyExc_AssertionError, "Expected a list as second argument"); - return NULL; // NULL triggers exception + return nullptr; } std::list items; @@ -416,40 +370,17 @@ PyObject* PythonWorkbenchPy::removeCommandbar(PyObject *args) { PY_TRY { char *psToolBar; - if (!PyArg_ParseTuple(args, "s", &psToolBar)) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "s", &psToolBar)) + return nullptr; getPythonBaseWorkbenchPtr()->removeCommandbar( psToolBar ); Py_Return; } PY_CATCH; } -/** Shows a list of all command bars */ -PyObject* PythonWorkbenchPy::listCommandbars(PyObject *args) +PyObject* PythonWorkbenchPy::getCustomAttributes(const char* ) const { - PY_TRY { - if (!PyArg_ParseTuple(args, "")) - return NULL; - - std::list bars = getPythonBaseWorkbenchPtr()->listCommandbars(); - - PyObject* pyList = PyList_New(bars.size()); - int i=0; - for (std::list::iterator it = bars.begin(); it != bars.end(); ++it, ++i) { -#if PY_MAJOR_VERSION >= 3 - PyObject* str = PyUnicode_FromString(it->c_str()); -#else - PyObject* str = PyString_FromString(it->c_str()); -#endif - PyList_SetItem(pyList, i, str); - } - return pyList; - } PY_CATCH; -} - -PyObject *PythonWorkbenchPy::getCustomAttributes(const char* ) const -{ - return 0; + return nullptr; } int PythonWorkbenchPy::setCustomAttributes(const char* , PyObject *) @@ -457,6 +388,8 @@ int PythonWorkbenchPy::setCustomAttributes(const char* , PyObject *) return 0; } +// deprecated methods + PyObject* PythonWorkbenchPy::AppendMenu(PyObject *args) { return appendMenu(args); diff --git a/src/Gui/Quarter/QuarterWidget.cpp b/src/Gui/Quarter/QuarterWidget.cpp index 440f255463..b76266b0b7 100644 --- a/src/Gui/Quarter/QuarterWidget.cpp +++ b/src/Gui/Quarter/QuarterWidget.cpp @@ -999,7 +999,20 @@ bool QuarterWidget::viewportEvent(QEvent* event) QMouseEvent* mouse = static_cast(event); QGraphicsItem *item = itemAt(mouse->pos()); if (!item) { - QGraphicsView::viewportEvent(event); + bool ok = QGraphicsView::viewportEvent(event); + // Avoid that wheel events are handled twice + // https://forum.freecadweb.org/viewtopic.php?f=3&t=44822 + // However, this workaround seems to cause a regression on macOS + // so it's disabled for this platform. + // https://forum.freecadweb.org/viewtopic.php?f=4&t=44855 +#if defined(Q_OS_MAC) + Q_UNUSED(ok) +#else + if (event->type() == QEvent::Wheel) { + event->setAccepted(ok); + return ok; + } +#endif return false; } } diff --git a/src/Gui/View3DInventor.cpp b/src/Gui/View3DInventor.cpp index b0228fe3c1..71c3608b4a 100644 --- a/src/Gui/View3DInventor.cpp +++ b/src/Gui/View3DInventor.cpp @@ -189,6 +189,7 @@ View3DInventor::View3DInventor(Gui::Document* pcDocument, QWidget* parent, OnChange(*hGrp,"Dimensions3dVisible"); OnChange(*hGrp,"DimensionsDeltaVisible"); OnChange(*hGrp,"PickRadius"); + OnChange(*hGrp,"TransparentObjectRenderType"); stopSpinTimer = new QTimer(this); connect(stopSpinTimer, SIGNAL(timeout()), this, SLOT(stopAnimating())); @@ -414,6 +415,17 @@ void View3DInventor::OnChange(ParameterGrp::SubjectType &rCaller,ParameterGrp::M else if (strcmp(Reason, "PickRadius") == 0) { _viewer->setPickRadius(rGrp.GetFloat("PickRadius", 5.0f)); } + else if (strcmp(Reason, "TransparentObjectRenderType") == 0) { + long renderType = rGrp.GetInt("TransparentObjectRenderType", 0); + if (renderType == 0) { + _viewer->getSoRenderManager()->getGLRenderAction() + ->setTransparentDelayedObjectRenderType(SoGLRenderAction::ONE_PASS); + } + else if (renderType == 1) { + _viewer->getSoRenderManager()->getGLRenderAction() + ->setTransparentDelayedObjectRenderType(SoGLRenderAction::NONSOLID_SEPARATE_BACKFACE_PASS); + } + } else { unsigned long col1 = rGrp.GetUnsigned("BackgroundColor",3940932863UL); unsigned long col2 = rGrp.GetUnsigned("BackgroundColor2",859006463UL); // default color (dark blue) diff --git a/src/Gui/View3DInventorViewer.cpp b/src/Gui/View3DInventorViewer.cpp index 83196b85b1..0458794dea 100644 --- a/src/Gui/View3DInventorViewer.cpp +++ b/src/Gui/View3DInventorViewer.cpp @@ -972,7 +972,7 @@ SbBool View3DInventorViewer::containsViewProvider(const ViewProvider* vp) const { SoSearchAction sa; sa.setNode(vp->getRoot()); - sa.setSearchingAll(true); + sa.setSearchingAll(false); sa.apply(getSoRenderManager()->getSceneGraph()); return sa.getPath() != nullptr; } @@ -1332,22 +1332,27 @@ bool View3DInventorViewer::isEnabledVBO() const void View3DInventorViewer::setRenderCache(int mode) { - if(mode<0) { + if (mode<0) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath ("User parameter:BaseApp/Preferences/View"); - int setting = hGrp->GetInt("RenderCache",0); - if(mode==-2) { - if(pcViewProviderRoot && setting!=1) + + int setting = hGrp->GetInt("RenderCache", 0); + if (mode == -2) { + if (pcViewProviderRoot && setting != 1) pcViewProviderRoot->renderCaching = SoSeparator::ON; mode = 2; - }else{ - if(pcViewProviderRoot) + } + else { + if (pcViewProviderRoot) pcViewProviderRoot->renderCaching = SoSeparator::AUTO; mode = setting; } } + SoFCSeparator::setCacheMode( - mode==0?SoSeparator::AUTO:(mode==1?SoSeparator::ON:SoSeparator::OFF)); + mode == 0 ? SoSeparator::AUTO : + (mode == 1 ? SoSeparator::ON : SoSeparator::OFF) + ); } void View3DInventorViewer::setEnabledNaviCube(bool on) diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 18348d2750..d14518fd35 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -429,6 +429,54 @@ void Workbench::removeTaskWatcher(void) taskView->clearTaskWatcher(); } +std::list Workbench::listToolbars() const +{ + std::unique_ptr tb(setupToolBars()); + std::list bars; + QList items = tb->getItems(); + for (QList::ConstIterator item = items.begin(); item != items.end(); ++item) + bars.push_back((*item)->command()); + return bars; +} + +std::list>> Workbench::getToolbarItems() const +{ + std::unique_ptr tb(setupToolBars()); + + std::list>> itemsList; + QList items = tb->getItems(); + for (QList::ConstIterator it = items.begin(); it != items.end(); ++it) { + QList sub = (*it)->getItems(); + std::list cmds; + for (QList::ConstIterator jt = sub.begin(); jt != sub.end(); ++jt) { + cmds.push_back((*jt)->command()); + } + + itemsList.emplace_back((*it)->command(), cmds); + } + return itemsList; +} + +std::list Workbench::listMenus() const +{ + std::unique_ptr mb(setupMenuBar()); + std::list menus; + QList items = mb->getItems(); + for ( QList::ConstIterator it = items.begin(); it != items.end(); ++it ) + menus.push_back((*it)->command()); + return menus; +} + +std::list Workbench::listCommandbars() const +{ + std::unique_ptr cb(setupCommandBars()); + std::list bars; + QList items = cb->getItems(); + for (QList::ConstIterator item = items.begin(); item != items.end(); ++item) + bars.push_back((*item)->command()); + return bars; +} + // -------------------------------------------------------------------- #if 0 // needed for Qt's lupdate utility @@ -1001,15 +1049,6 @@ void PythonBaseWorkbench::removeMenu(const std::string& menu) const } } -std::list PythonBaseWorkbench::listMenus() const -{ - std::list menus; - QList items = _menuBar->getItems(); - for ( QList::ConstIterator it = items.begin(); it != items.end(); ++it ) - menus.push_back((*it)->command()); - return menus; -} - void PythonBaseWorkbench::appendContextMenu(const std::list& menu, const std::list& items) const { MenuItem* item = _contextMenu; @@ -1062,15 +1101,6 @@ void PythonBaseWorkbench::removeToolbar(const std::string& bar) const } } -std::list PythonBaseWorkbench::listToolbars() const -{ - std::list bars; - QList items = _toolBar->getItems(); - for (QList::ConstIterator item = items.begin(); item != items.end(); ++item) - bars.push_back((*item)->command()); - return bars; -} - void PythonBaseWorkbench::appendCommandbar(const std::string& bar, const std::list& items) const { ToolBarItem* item = _commandBar->findItem( bar ); @@ -1093,15 +1123,6 @@ void PythonBaseWorkbench::removeCommandbar(const std::string& bar) const } } -std::list PythonBaseWorkbench::listCommandbars() const -{ - std::list bars; - QList items = _commandBar->getItems(); - for (QList::ConstIterator item = items.begin(); item != items.end(); ++item) - bars.push_back((*item)->command()); - return bars; -} - // ----------------------------------------------------------------------- TYPESYSTEM_SOURCE(Gui::PythonBlankWorkbench, Gui::PythonBaseWorkbench) diff --git a/src/Gui/Workbench.h b/src/Gui/Workbench.h index 2badac7901..a5ee834246 100644 --- a/src/Gui/Workbench.h +++ b/src/Gui/Workbench.h @@ -97,6 +97,15 @@ public: static void createLinkMenu(MenuItem *); + //// Shows a list of all toolbars + std::list listToolbars() const; + /// Shows a list of all toolbars and their commands + std::list>> getToolbarItems() const; + //// Shows a list of all menus + std::list listMenus() const; + //// Shows a list of all command bars + std::list listCommandbars() const; + protected: /** Returns a MenuItem tree structure of menus for this workbench. */ virtual MenuItem* setupMenuBar() const=0; @@ -245,8 +254,6 @@ public: void appendMenu(const std::list& menu, const std::list& items) const; /// Removes a menu void removeMenu(const std::string& menu ) const; - //// Shows a list of all menus - std::list listMenus() const; /// Appends new context menu items void appendContextMenu(const std::list& menu, const std::list& items) const; @@ -259,15 +266,11 @@ public: void appendToolbar(const std::string& bar, const std::list& items) const; /// Removes a toolbar void removeToolbar(const std::string& bar) const; - //// Shows a list of all toolbars - std::list listToolbars() const; /// Appends a new command bar void appendCommandbar(const std::string& bar, const std::list& items) const; /// Removes a command bar void removeCommandbar(const std::string& bar) const; - //// Shows a list of all command bars - std::list listCommandbars() const; //@} protected: diff --git a/src/Gui/WorkbenchPy.xml b/src/Gui/WorkbenchPy.xml index 70917ea74f..457c1d2661 100644 --- a/src/Gui/WorkbenchPy.xml +++ b/src/Gui/WorkbenchPy.xml @@ -23,6 +23,26 @@ Activate this workbench + + + Show a list of all toolbars + + + + + Show a dict of all toolbars and their commands + + + + + Show a list of all command bars + + + + + Show a list of all menus + + - + diff --git a/src/Gui/WorkbenchPyImp.cpp b/src/Gui/WorkbenchPyImp.cpp index 1fa913e8c3..bae0d5ea0e 100644 --- a/src/Gui/WorkbenchPyImp.cpp +++ b/src/Gui/WorkbenchPyImp.cpp @@ -51,25 +51,20 @@ std::string WorkbenchPy::representation(void) const /** Retrieves the workbench name */ PyObject* WorkbenchPy::name(PyObject *args) { - if (!PyArg_ParseTuple(args, "")) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "")) + return nullptr; PY_TRY { - std::string name = getWorkbenchPtr()->name(); -#if PY_MAJOR_VERSION >= 3 - PyObject* pyName = PyUnicode_FromString(name.c_str()); -#else - PyObject* pyName = PyString_FromString(name.c_str()); -#endif - return pyName; + Py::String name(getWorkbenchPtr()->name()); + return Py::new_reference_to(name); }PY_CATCH; } /** Activates the workbench object */ PyObject* WorkbenchPy::activate(PyObject *args) { - if (!PyArg_ParseTuple(args, "")) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "")) + return nullptr; PY_TRY { std::string name = getWorkbenchPtr()->name(); @@ -78,14 +73,84 @@ PyObject* WorkbenchPy::activate(PyObject *args) }PY_CATCH; } -PyObject *WorkbenchPy::getCustomAttributes(const char*) const +/** Shows a list of all menus */ +PyObject* WorkbenchPy::listMenus(PyObject *args) { - return 0; + PY_TRY { + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + std::list menus = getWorkbenchPtr()->listMenus(); + + Py::List list; + for (std::list::iterator it = menus.begin(); it != menus.end(); ++it) { + list.append(Py::String(*it)); + } + return Py::new_reference_to(list); + } PY_CATCH; +} + +/** Shows a list of all toolbars */ +PyObject* WorkbenchPy::listToolbars(PyObject *args) +{ + PY_TRY { + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + std::list bars = getWorkbenchPtr()->listToolbars(); + + Py::List list; + for (std::list::iterator it = bars.begin(); it != bars.end(); ++it) { + list.append(Py::String(*it)); + } + return Py::new_reference_to(list); + } PY_CATCH; +} + +/** Shows a dict of all toolbars and their commands*/ +PyObject* WorkbenchPy::getToolbarItems(PyObject *args) +{ + PY_TRY { + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + std::list>> bars = getWorkbenchPtr()->getToolbarItems(); + + Py::Dict dict; + for (const auto it : bars) { + Py::List list; + for (const auto jt : it.second) { + list.append(Py::String(jt)); + } + dict.setItem(it.first, list); + } + return Py::new_reference_to(dict); + } PY_CATCH; +} + +/** Shows a list of all command bars */ +PyObject* WorkbenchPy::listCommandbars(PyObject *args) +{ + PY_TRY { + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + std::list bars = getWorkbenchPtr()->listCommandbars(); + + Py::List list; + for (std::list::iterator it = bars.begin(); it != bars.end(); ++it) { + list.append(Py::String(*it)); + } + return Py::new_reference_to(list); + } PY_CATCH; +} + +PyObject* WorkbenchPy::getCustomAttributes(const char*) const +{ + return nullptr; } int WorkbenchPy::setCustomAttributes(const char*, PyObject *) { return 0; } - - diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index 621e766637..5ae2414d30 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -475,33 +475,38 @@ void PropertyItem::setPropertyValue(const QString& value) // invalidate the current property array. std::ostringstream ss; for (std::vector::const_iterator it = propertyItems.begin(); - it != propertyItems.end(); ++it) - { + it != propertyItems.end(); ++it) { auto prop = *it; App::PropertyContainer* parent = prop->getContainer(); if (!parent || parent->isReadOnly(prop) || prop->testStatus(App::Property::ReadOnly)) continue; + if (parent->isDerivedFrom(App::Document::getClassTypeId())) { App::Document* doc = static_cast(parent); ss << "FreeCAD.getDocument('" << doc->getName() << "')."; - } else if (parent->isDerivedFrom(App::DocumentObject::getClassTypeId())) { + } + else if (parent->isDerivedFrom(App::DocumentObject::getClassTypeId())) { App::DocumentObject* obj = static_cast(parent); App::Document* doc = obj->getDocument(); ss << "FreeCAD.getDocument('" << doc->getName() << "').getObject('" << obj->getNameInDocument() << "')."; - } else if (parent->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { + } + else if (parent->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { App::DocumentObject* obj = static_cast(parent)->getObject(); App::Document* doc = obj->getDocument(); ss << "FreeCADGui.getDocument('" << doc->getName() << "').getObject('" << obj->getNameInDocument() << "')."; - } else + } + else { continue; + } + ss << parent->getPropertyPrefix() << prop->getName() - << " = " << value.toLatin1().constData() << '\n'; + << " = " << value.toUtf8().constData() << '\n'; } std::string cmd = ss.str(); - if(cmd.empty()) + if (cmd.empty()) return; try { diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 80674ea796..c29e921ad2 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -468,7 +468,7 @@ class CommandAddonManager: macro_path = macro_path.replace("\\","/") FreeCADGui.open(str(macro_path)) - self.hide() + self.dialog.hide() FreeCADGui.SendMsgToActiveView("Run") else: self.dialog.buttonExecute.setEnabled(False) diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index 799cb64f36..321de89fab 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -23,6 +23,7 @@ import os import sys +import codecs import FreeCAD import shutil import re @@ -173,13 +174,8 @@ def install_macro(macro, macro_repo_dir): except OSError: return False macro_path = os.path.join(macro_dir, macro.filename) - if sys.version_info.major < 3: - # In python2 the code is a bytes object. - mode = 'wb' - else: - mode = 'w' try: - with open(macro_path, mode) as macrofile: + with codecs.open(macro_path, 'w', 'utf-8') as macrofile: macrofile.write(macro.code) except IOError: return False diff --git a/src/Mod/Arch/ArchPanel.py b/src/Mod/Arch/ArchPanel.py index 95a3a1e5ed..1b4c806de9 100644 --- a/src/Mod/Arch/ArchPanel.py +++ b/src/Mod/Arch/ArchPanel.py @@ -26,6 +26,7 @@ if FreeCAD.GuiUp: from PySide import QtCore, QtGui from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP + import draftguitools.gui_trackers as DraftTrackers else: # \cond def translate(ctxt,txt): @@ -180,7 +181,7 @@ class CommandPanel: # interactive mode if hasattr(FreeCAD,"DraftWorkingPlane"): FreeCAD.DraftWorkingPlane.setup() - import DraftTrackers + self.points = [] self.tracker = DraftTrackers.boxTracker() self.tracker.width(self.Width) diff --git a/src/Mod/Arch/ArchPipe.py b/src/Mod/Arch/ArchPipe.py index 12e751c722..6b5923a593 100644 --- a/src/Mod/Arch/ArchPipe.py +++ b/src/Mod/Arch/ArchPipe.py @@ -190,15 +190,17 @@ class _ArchPipe(ArchComponent.Component): pl = obj.PropertiesList if not "Diameter" in pl: - obj.addProperty("App::PropertyLength", "Diameter", "Pipe", QT_TRANSLATE_NOOP("App::Property","The diameter of this pipe, if not based on a profile")) + obj.addProperty("App::PropertyLength", "Diameter", "Pipe", QT_TRANSLATE_NOOP("App::Property","The diameter of this pipe, if not based on a profile")) if not "Length" in pl: - obj.addProperty("App::PropertyLength", "Length", "Pipe", QT_TRANSLATE_NOOP("App::Property","The length of this pipe, if not based on an edge")) + obj.addProperty("App::PropertyLength", "Length", "Pipe", QT_TRANSLATE_NOOP("App::Property","The length of this pipe, if not based on an edge")) if not "Profile" in pl: - obj.addProperty("App::PropertyLink", "Profile", "Pipe", QT_TRANSLATE_NOOP("App::Property","An optional closed profile to base this pipe on")) + obj.addProperty("App::PropertyLink", "Profile", "Pipe", QT_TRANSLATE_NOOP("App::Property","An optional closed profile to base this pipe on")) if not "OffsetStart" in pl: - obj.addProperty("App::PropertyLength", "OffsetStart", "Pipe", QT_TRANSLATE_NOOP("App::Property","Offset from the start point")) + obj.addProperty("App::PropertyLength", "OffsetStart", "Pipe", QT_TRANSLATE_NOOP("App::Property","Offset from the start point")) if not "OffsetEnd" in pl: - obj.addProperty("App::PropertyLength", "OffsetEnd", "Pipe", QT_TRANSLATE_NOOP("App::Property","Offset from the end point")) + obj.addProperty("App::PropertyLength", "OffsetEnd", "Pipe", QT_TRANSLATE_NOOP("App::Property","Offset from the end point")) + if not "WallThickness" in pl: + obj.addProperty("App::PropertyLength", "WallThickness","Pipe", QT_TRANSLATE_NOOP("App::Property","The wall thickness of this pipe, if not based on a profile")) self.Type = "Pipe" def onDocumentRestored(self,obj): @@ -231,7 +233,11 @@ class _ArchPipe(ArchComponent.Component): FreeCAD.Console.PrintError(translate("Arch","Unable to build the profile")+"\n") return # move and rotate the profile to the first point - delta = w.Vertexes[0].Point-p.CenterOfMass + if hasattr(p,"CenterOfMass"): + c = p.CenterOfMass + else: + c = p.BoundBox.Center + delta = w.Vertexes[0].Point-c p.translate(delta) import Draft if Draft.getType(obj.Base) == "BezCurve": @@ -240,12 +246,30 @@ class _ArchPipe(ArchComponent.Component): v1 = w.Vertexes[1].Point-w.Vertexes[0].Point v2 = DraftGeomUtils.getNormal(p) rot = FreeCAD.Rotation(v2,v1) - p.rotate(p.CenterOfMass,rot.Axis,math.degrees(rot.Angle)) + p.rotate(c,rot.Axis,math.degrees(rot.Angle)) + shapes = [] try: - sh = w.makePipeShell([p],True,False,2) + if p.Faces: + for f in p.Faces: + sh = w.makePipeShell([f.OuterWire],True,False,2) + for shw in f.Wires: + if shw.hashCode() != f.OuterWire.hashCode(): + sh2 = w.makePipeShell([shw],True,False,2) + sh = sh.cut(sh2) + shapes.append(sh) + elif p.Wires: + for pw in p.Wires: + sh = w.makePipeShell([pw],True,False,2) + shapes.append(sh) except: FreeCAD.Console.PrintError(translate("Arch","Unable to build the pipe")+"\n") else: + if len(shapes) == 0: + return + elif len(shapes) == 1: + sh = shapes[0] + else: + sh = Part.makeCompound(shapes) obj.Shape = sh if obj.Base: obj.Length = w.Length @@ -279,17 +303,19 @@ class _ArchPipe(ArchComponent.Component): if not obj.Profile.getLinkedObject().isDerivedFrom("Part::Part2DObject"): FreeCAD.Console.PrintError(translate("Arch","The profile is not a 2D Part")+"\n") return - if len(obj.Profile.Shape.Wires) != 1: - FreeCAD.Console.PrintError(translate("Arch","Too many wires in the profile")+"\n") - return if not obj.Profile.Shape.Wires[0].isClosed(): FreeCAD.Console.PrintError(translate("Arch","The profile is not closed")+"\n") return - p = obj.Profile.Shape.Wires[0] + p = obj.Profile.Shape else: if obj.Diameter.Value == 0: return p = Part.Wire([Part.Circle(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,0,1),obj.Diameter.Value/2).toShape()]) + if obj.WallThickness.Value and (obj.WallThickness.Value < obj.Diameter.Value/2): + p2 = Part.Wire([Part.Circle(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,0,1),(obj.Diameter.Value/2-obj.WallThickness.Value)).toShape()]) + p = Part.Face(p) + p2 = Part.Face(p2) + p = p.cut(p2) return p diff --git a/src/Mod/Arch/ArchRebar.py b/src/Mod/Arch/ArchRebar.py index 7786f39eef..c9e2168001 100644 --- a/src/Mod/Arch/ArchRebar.py +++ b/src/Mod/Arch/ArchRebar.py @@ -301,14 +301,40 @@ class _Rebar(ArchComponent.Component): if self.clone(obj): return if not obj.Base: + FreeCAD.Console.PrintError( + "No Base, return without a rebar shape for {}.\n" + .format(obj.Name) + ) return if not obj.Base.Shape: + FreeCAD.Console.PrintError( + "No Shape in Base, return without a rebar shape for {}.\n" + .format(obj.Name) + ) return - if not obj.Base.Shape.Wires: + if obj.Base.Shape.Faces: + FreeCAD.Console.PrintError( + "Faces in Shape of Base, return without a rebar shape for {}.\n" + .format(obj.Name) + ) + return + if not obj.Base.Shape.Edges: + FreeCAD.Console.PrintError( + "No Edges in Shape of Base, return without a rebar shape for {}.\n" + .format(obj.Name) + ) return if not obj.Diameter.Value: + FreeCAD.Console.PrintError( + "No Diameter Value, return without a rebar shape for {}.\n" + .format(obj.Name) + ) return if not obj.Amount: + FreeCAD.Console.PrintError( + "No Amount, return without a rebar shape for {}.\n" + .format(obj.Name) + ) return father = obj.Host fathershape = None @@ -322,13 +348,22 @@ class _Rebar(ArchComponent.Component): if hasattr(father,'Shape'): fathershape = father.Shape - wire = obj.Base.Shape.Wires[0] + import Part + # corner cases: + # compound from more Wires + # compound without Wires but with multiple Edges + # Does they make sense? If yes handle them. + # Does it makes sense to handle Shapes with Faces or even Solids? + if not obj.Base.Shape.Wires and len(obj.Base.Shape.Edges) == 1: + wire = Part.Wire(obj.Base.Shape.Edges[0]) + else: + wire = obj.Base.Shape.Wires[0] if hasattr(obj,"Rounding"): #print(obj.Rounding) if obj.Rounding: radius = obj.Rounding * obj.Diameter.Value - import DraftGeomUtils - wire = DraftGeomUtils.filletWire(wire,radius) + from DraftGeomUtils import filletWire + wire = filletWire(wire,radius) bpoint, bvec = self.getBaseAndAxis(wire) if not bpoint: return @@ -362,7 +397,6 @@ class _Rebar(ArchComponent.Component): if length: obj.Length = length pl = obj.Placement - import Part circle = Part.makeCircle(obj.Diameter.Value/2,bpoint,bvec) circle = Part.Wire(circle) try: diff --git a/src/Mod/Arch/ArchStructure.py b/src/Mod/Arch/ArchStructure.py index dc78284e7a..83a3e3dcec 100644 --- a/src/Mod/Arch/ArchStructure.py +++ b/src/Mod/Arch/ArchStructure.py @@ -29,6 +29,8 @@ if FreeCAD.GuiUp: from PySide import QtCore, QtGui from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP + import ArchPrecast + import draftguitools.gui_trackers as DraftTrackers else: # \cond def translate(ctxt,txt): @@ -247,7 +249,7 @@ class _CommandStructure: # interactive mode if hasattr(FreeCAD,"DraftWorkingPlane"): FreeCAD.DraftWorkingPlane.setup() - import DraftTrackers,ArchPrecast + self.points = [] self.tracker = DraftTrackers.boxTracker() self.tracker.width(self.Width) diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index 7cb2e5e25e..65645bdb14 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -26,6 +26,7 @@ if FreeCAD.GuiUp: from PySide import QtCore, QtGui from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP + import draftguitools.gui_trackers as DraftTrackers else: # \cond def translate(ctxt,txt, utf8_decode=False): @@ -232,7 +233,7 @@ class _CommandWall: if not done: # interactive mode - import DraftTrackers + self.points = [] self.tracker = DraftTrackers.boxTracker() if hasattr(FreeCAD,"DraftWorkingPlane"): diff --git a/src/Mod/Arch/ArchWindow.py b/src/Mod/Arch/ArchWindow.py index 7fc7ab2d14..9fe8773919 100644 --- a/src/Mod/Arch/ArchWindow.py +++ b/src/Mod/Arch/ArchWindow.py @@ -27,6 +27,7 @@ if FreeCAD.GuiUp: from PySide import QtCore, QtGui, QtSvg from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP + import draftguitools.gui_trackers as DraftTrackers else: # \cond def translate(ctxt,txt): @@ -653,7 +654,7 @@ class _CommandWindow: # interactive mode if hasattr(FreeCAD,"DraftWorkingPlane"): FreeCAD.DraftWorkingPlane.setup() - import DraftTrackers + self.tracker = DraftTrackers.boxTracker() self.tracker.length(self.Width) self.tracker.width(self.Thickness) @@ -783,6 +784,15 @@ class _CommandWindow: self.librarypresets.append([wtype+" - "+subtype+" - "+os.path.splitext(subfile)[0],os.path.join(subdir,subfile)]) else: librarypath = None + # check for existing presets + presetdir = os.path.join(FreeCAD.getUserAppDataDir(),"Arch") + for tp in ["Windows","Doors"]: + wdir = os.path.join(presetdir,tp) + if os.path.exists(wdir): + for wfile in os.listdir(wdir): + if wfile.lower().endswith(".fcstd"): + self.librarypresets.append([tp[:-1]+" - "+wfile[:-6],wfile]) + # presets box labelp = QtGui.QLabel(translate("Arch","Preset")) diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py index f0b3c71cbe..596b137331 100644 --- a/src/Mod/Arch/exportIFC.py +++ b/src/Mod/Arch/exportIFC.py @@ -137,7 +137,8 @@ def getPreferences(): 'ADD_DEFAULT_STOREY': p.GetBool("IfcAddDefaultStorey",False), 'ADD_DEFAULT_BUILDING': p.GetBool("IfcAddDefaultBuilding",True), 'IFC_UNIT': u, - 'SCALE_FACTOR': f + 'SCALE_FACTOR': f, + 'GET_STANDARD': p.GetBool("getStandardType",False) } return preferences @@ -162,6 +163,8 @@ def export(exportList,filename,colors=None,preferences=None): FreeCAD.Console.PrintMessage("Visit https://www.freecadweb.org/wiki/Arch_IFC to learn how to install it\n") return + # process template + version = FreeCAD.Version() owner = FreeCAD.ActiveDocument.CreatedBy email = '' @@ -171,11 +174,10 @@ def export(exportList,filename,colors=None,preferences=None): email = s[1].strip(">") global template template = ifctemplate.replace("$version",version[0]+"."+version[1]+" build "+version[2]) - getstd = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("getStandardType",False) if hasattr(ifcopenshell,"schema_identifier"): schema = ifcopenshell.schema_identifier elif hasattr(ifcopenshell,"version") and (float(ifcopenshell.version[:3]) >= 0.6): - # v0.6 allows to set our own schema + # v0.6 onwards allows to set our own schema schema = ["IFC4", "IFC2X3"][FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("IfcVersion",0)] else: schema = "IFC2X3" @@ -196,6 +198,9 @@ def export(exportList,filename,colors=None,preferences=None): of.write(template) of.close() os.close(templatefilehandle) + + # create IFC file + global ifcfile, surfstyles, clones, sharedobjects, profiledefs, shapedefs ifcfile = ifcopenshell.open(templatefile) ifcfile = exportIFCHelper.writeUnits(ifcfile,preferences["IFC_UNIT"]) @@ -211,13 +216,17 @@ def export(exportList,filename,colors=None,preferences=None): if obj.Shape: if obj.Shape.Edges and (not obj.Shape.Faces): annotations.append(obj) + # clean objects list of unwanted types + objectslist = [obj for obj in objectslist if obj not in annotations] objectslist = Arch.pruneIncluded(objectslist,strict=True) objectslist = [obj for obj in objectslist if Draft.getType(obj) not in ["Dimension","Material","MaterialContainer","WorkingPlaneProxy"]] if preferences['FULL_PARAMETRIC']: objectslist = Arch.getAllChildren(objectslist) + # create project and context + contextCreator = exportIFCHelper.ContextCreator(ifcfile, objectslist) context = contextCreator.model_view_subcontext project = contextCreator.project @@ -227,6 +236,8 @@ def export(exportList,filename,colors=None,preferences=None): decl = Draft.getObjectsOfType(objectslist, "Site")[0].Declination.getValueAs(FreeCAD.Units.Radian) contextCreator.model_context.TrueNorth.DirectionRatios = (math.cos(decl+math.pi/2), math.sin(decl+math.pi/2)) + # define holders for the different types we create + products = {} # { Name: IfcEntity, ... } subproducts = {} # { Name: IfcEntity, ... } for storing additions/subtractions and other types of subcomponents of a product surfstyles = {} # { (r,g,b): IfcEntity, ... } @@ -267,33 +278,57 @@ def export(exportList,filename,colors=None,preferences=None): # getting generic data - name = obj.Label - if six.PY2: - name = name.encode("utf8") - description = obj.Description if hasattr(obj,"Description") else "" - if six.PY2: - description = description.encode("utf8") - - # getting uid - - uid = None - if hasattr(obj,"IfcData"): - if "IfcUID" in obj.IfcData.keys(): - uid = str(obj.IfcData["IfcUID"]) - if not uid: - uid = ifcopenshell.guid.new() - # storing the uid for further use - if preferences['STORE_UID'] and hasattr(obj,"IfcData"): - d = obj.IfcData - d["IfcUID"] = uid - obj.IfcData = d - + name = getText("Name",obj) + description = getText("Description",obj) + uid = getUID(obj,preferences) ifctype = getIfcTypeFromObj(obj) if ifctype == "IfcGroup": groups[obj.Name] = [o.Name for o in obj.Group] continue + # handle assemblies (arrays, app::parts, references, etc...) + + assemblyElements = [] + + if ifctype == "IfcArray": + if obj.ArrayType == "ortho": + clonedeltas = [] + for i in range(obj.NumberX): + clonedeltas.append(obj.Placement.Base+(i*obj.IntervalX)) + for j in range(obj.NumberY): + if j > 0: + clonedeltas.append(obj.Placement.Base+(i*obj.IntervalX)+(j*obj.IntervalY)) + for k in range(obj.NumberZ): + if k > 0: + clonedeltas.append(obj.Placement.Base+(i*obj.IntervalX)+(j*obj.IntervalY)+(k*obj.IntervalZ)) + #print("clonedeltas:",clonedeltas) + for delta in clonedeltas: + representation,placement,shapetype = getRepresentation( + ifcfile, + context, + obj.Base, + forcebrep=(getBrepFlag(obj.Base,preferences)), + colors=colors, + preferences=preferences, + forceclone=delta + ) + subproduct = createProduct( + ifcfile, + obj.Base, + getIfcTypeFromObj(obj.Base), + getUID(obj.Base,preferences), + history, + getText("Name",obj.Base), + getText("Description",obj.Base), + placement, + representation, + preferences, + schema) + + assemblyElements.append(subproduct) + ifctype = "IfcElementAssembly" + # export grids if ifctype in ["IfcAxis","IfcAxisSystem","IfcGrid"]: @@ -356,61 +391,55 @@ def export(exportList,filename,colors=None,preferences=None): if ifctype not in ArchIFCSchema.IfcProducts.keys(): ifctype = "IfcBuildingElementProxy" - # getting the "Force BREP" flag - - brepflag = False - if hasattr(obj,"IfcData"): - if "FlagForceBrep" in obj.IfcData.keys(): - if obj.IfcData["FlagForceBrep"] == "True": - brepflag = True - # getting the representation representation,placement,shapetype = getRepresentation( ifcfile, context, obj, - forcebrep=(brepflag or preferences['FORCE_BREP']), + forcebrep=(getBrepFlag(obj,preferences)), colors=colors, preferences=preferences ) - if getstd: + if preferences['GET_STANDARD']: if isStandardCase(obj,ifctype): ifctype += "StandardCase" - if preferences['DEBUG']: print(str(count).ljust(3)," : ", ifctype, " (",shapetype,") : ",name) - - # setting the arguments - - kwargs = { - "GlobalId": uid, - "OwnerHistory": history, - "Name": name, - "Description": description, - "ObjectPlacement": placement, - "Representation": representation - } - if ifctype == "IfcSite": - kwargs.update({ - "RefLatitude":dd2dms(obj.Latitude), - "RefLongitude":dd2dms(obj.Longitude), - "RefElevation":obj.Elevation.Value*preferences['SCALE_FACTOR'], - "SiteAddress":buildAddress(obj,ifcfile), - "CompositionType": "ELEMENT" - }) - if schema == "IFC2X3": - kwargs = exportIFC2X3Attributes(obj, kwargs, preferences['SCALE_FACTOR']) - else: - kwargs = exportIfcAttributes(obj, kwargs, preferences['SCALE_FACTOR']) + if preferences['DEBUG']: + print(str(count).ljust(3)," : ", ifctype, " (",shapetype,") : ",name) # creating the product - #print(obj.Label," : ",ifctype," : ",kwargs) - product = getattr(ifcfile,"create"+ifctype)(**kwargs) + product = createProduct( + ifcfile, + obj, + ifctype, + uid, + history, + name, + description, + placement, + representation, + preferences, + schema) + products[obj.Name] = product if ifctype in ["IfcBuilding","IfcBuildingStorey","IfcSite","IfcSpace"]: spatialelements[obj.Name] = product + # gather assembly subelements + + if assemblyElements: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'Assembly', + '', + products[obj.Name], + assemblyElements + ) + if preferences['DEBUG']: print(" aggregating",len(assemblyElements),"object(s)") + # additions if hasattr(obj,"Additions") and (shapetype in ["extrusion","no shape"]): @@ -1743,9 +1772,10 @@ def getProfile(ifcfile,p): return profile -def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1,colors=None,preferences=None): +def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1,colors=None,preferences=None,forceclone=False): - """returns an IfcShapeRepresentation object or None""" + """returns an IfcShapeRepresentation object or None. forceclone can be False (does nothing), + "store" or True (stores the object as clone base) or a Vector (creates a clone)""" import Part import DraftGeomUtils @@ -1756,20 +1786,27 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess shapetype = "no shape" tostore = False subplacement = None + skipshape = False # check for clones - if (not subtraction) and (not forcebrep): + if ((not subtraction) and (not forcebrep)) or forceclone: + if forceclone: + if not obj.Name in clones: + clones[obj.Name] = [] for k,v in clones.items(): if (obj.Name == k) or (obj.Name in v): if k in sharedobjects: # base shape already exists repmap = sharedobjects[k] pla = obj.getGlobalPlacement() + pos = FreeCAD.Vector(pla.Base) + if isinstance(forceclone,FreeCAD.Vector): + pos += forceclone axis1 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0)))) axis2 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0)))) axis3 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,0,1)))) - origin = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(preferences['SCALE_FACTOR']))) + origin = ifcbin.createIfcCartesianPoint(tuple(pos.multiply(preferences['SCALE_FACTOR']))) transf = ifcbin.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3) mapitem = ifcfile.createIfcMappedItem(repmap,transf) shapes = [mapitem] @@ -1783,7 +1820,11 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess if obj.isDerivedFrom("Part::Feature") and (len(obj.Shape.Solids) > 1) and hasattr(obj,"Axis") and obj.Axis: forcebrep = True - if (not shapes) and (not forcebrep): + # specific cases that must ignore their own shape + if Draft.getType(obj) in ["Array"]: + skipshape = True + + if (not shapes) and (not forcebrep) and (not skipshape): profile = None ev = FreeCAD.Vector() if hasattr(obj,"Proxy"): @@ -1858,7 +1899,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess solidType = "SweptSolid" shapetype = "extrusion" - if not shapes: + if (not shapes) and (not skipshape): # check if we keep a null shape (additions-only object) @@ -2106,3 +2147,72 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess productdef = ifcfile.createIfcProductDefinitionShape(None,None,[representation]) return productdef,placement,shapetype + + +def getBrepFlag(obj,preferences): + """returns True if the object must be exported as BREP""" + brepflag = False + if preferences['FORCE_BREP']: + return True + if hasattr(obj,"IfcData"): + if "FlagForceBrep" in obj.IfcData.keys(): + if obj.IfcData["FlagForceBrep"] == "True": + brepflag = True + return brepflag + + +def createProduct(ifcfile,obj,ifctype,uid,history,name,description,placement,representation,preferences,schema): + """creates a product in the given IFC file""" + + kwargs = { + "GlobalId": uid, + "OwnerHistory": history, + "Name": name, + "Description": description, + "ObjectPlacement": placement, + "Representation": representation + } + if ifctype == "IfcSite": + kwargs.update({ + "RefLatitude":dd2dms(obj.Latitude), + "RefLongitude":dd2dms(obj.Longitude), + "RefElevation":obj.Elevation.Value*preferences['SCALE_FACTOR'], + "SiteAddress":buildAddress(obj,ifcfile), + "CompositionType": "ELEMENT" + }) + if schema == "IFC2X3": + kwargs = exportIFC2X3Attributes(obj, kwargs, preferences['SCALE_FACTOR']) + else: + kwargs = exportIfcAttributes(obj, kwargs, preferences['SCALE_FACTOR']) + product = getattr(ifcfile,"create"+ifctype)(**kwargs) + return product + + +def getUID(obj,preferences): + """gets or creates an UUID for an object""" + + uid = None + if hasattr(obj,"IfcData"): + if "IfcUID" in obj.IfcData.keys(): + uid = str(obj.IfcData["IfcUID"]) + if not uid: + uid = ifcopenshell.guid.new() + # storing the uid for further use + if preferences['STORE_UID'] and hasattr(obj,"IfcData"): + d = obj.IfcData + d["IfcUID"] = uid + obj.IfcData = d + return uid + + +def getText(field,obj): + """Returns the value of a text property of an object""" + + result = "" + if field == "Name": + field = "Label" + if hasattr(obj,field): + result = getattr(obj,field) + if six.PY2: + result = result.encode("utf8") + return result diff --git a/src/Mod/Assembly/App/AppAssembly.cpp b/src/Mod/Assembly/App/AppAssembly.cpp index 27a1b7f13a..5a8bd4a9d4 100644 --- a/src/Mod/Assembly/App/AppAssembly.cpp +++ b/src/Mod/Assembly/App/AppAssembly.cpp @@ -77,12 +77,12 @@ void AssemblyExport initAssembly() // call PyType_Ready, otherwise we run into a segmentation fault, later on. // This function is responsible for adding inherited slots from a type's base class. - // Item hirachy + // Item hierarchy Assembly::Item ::init(); Assembly::Product ::init(); Assembly::ProductRef ::init(); - // constraint hirachy + // constraint hierarchy Assembly::Constraint ::init(); Assembly::ConstraintGroup ::init(); } diff --git a/src/Mod/Assembly/App/Product.h b/src/Mod/Assembly/App/Product.h index f58cc9734c..081b9c6d33 100644 --- a/src/Mod/Assembly/App/Product.h +++ b/src/Mod/Assembly/App/Product.h @@ -68,7 +68,7 @@ public: //@{ /** Base color of the Item If the transparency value is 1.0 - the color or the next hirachy is used + the color or the next hierarchy is used */ App::PropertyColor Color; /// Visibility diff --git a/src/Mod/Cloud/App/AppCloud.cpp b/src/Mod/Cloud/App/AppCloud.cpp index d1ae135e13..e669c67cf9 100644 --- a/src/Mod/Cloud/App/AppCloud.cpp +++ b/src/Mod/Cloud/App/AppCloud.cpp @@ -212,6 +212,11 @@ void Cloud::CloudWriter::createBucket() // Let's build the Header and call to curl curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); +#ifdef ALLOW_SELF_SIGNED_CERTIFICATE + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); +#endif + if ( curl ) { struct curl_slist *chunk = NULL; @@ -390,6 +395,10 @@ Cloud::CloudWriter::CloudWriter(const char* Url, const char* AccessKey, const ch // Let's build the Header and call to curl curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); +#ifdef ALLOW_SELF_SIGNED_CERTIFICATE + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); +#endif if ( curl ) { // Let's build our own header @@ -577,6 +586,10 @@ Cloud::CloudReader::CloudReader(const char* Url, const char* AccessKey, const ch std::string s; RequestData = Cloud::ComputeDigestAmzS3v2("GET", "application/xml", path, this->SecretKey, NULL, 0); curl = curl_easy_init(); +#ifdef ALLOW_SELF_SIGNED_CERTIFICATE + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); +#endif if ( curl ) { // Let's build our own header @@ -661,6 +674,10 @@ void Cloud::CloudReader::DownloadFile(Cloud::CloudReader::FileEntry *entry) // Let's build the Header and call to curl curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); +#ifdef ALLOW_SELF_SIGNED_CERTIFICATE + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); +#endif if ( curl ) { struct curl_slist *chunk = NULL; @@ -771,6 +788,10 @@ void Cloud::CloudWriter::pushCloud(const char *FileName, const char *data, long // Let's build the Header and call to curl curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); +#ifdef ALLOW_SELF_SIGNED_CERTIFICATE + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); +#endif if ( curl ) { struct curl_slist *chunk = NULL; diff --git a/src/Mod/Cloud/CMakeLists.txt b/src/Mod/Cloud/CMakeLists.txt index e7293661e1..5038e7308b 100644 --- a/src/Mod/Cloud/CMakeLists.txt +++ b/src/Mod/Cloud/CMakeLists.txt @@ -1,10 +1,13 @@ +#----------------------------- Control certificate validation ------------ +option(ALLOW_SELF_SIGNED_CERTIFICATE "Allow self signed certificate" OFF) +if (ALLOW_SELF_SIGNED_CERTIFICATE) +add_compile_options("-DALLOW_SELF_SIGNED_CERTIFICATE") +endif () #------------------------------ OpenSSL and CURL ------------------------- if (APPLE) - set(OPENSSL_ROOT_DIR ${HOMEBREW_PREFIX}/Cellar/openssl/*) -endif(APPLE) -if (UNIX AND NOT APPLE) find_package(PkgConfig REQUIRED) pkg_search_module(OPENSSL REQUIRED openssl) + set (OPENSSL_LIBRARIES ${OPENSSL_LINK_LIBRARIES}) elseif(WIN32 AND LIBPACK_FOUND) SET( OPENSSL_INCLUDE_DIR ${FREECAD_LIBPACK_DIR}/include) SET( OPENSSL_LIBRARIES @@ -27,8 +30,9 @@ elseif(WIN32 AND LIBPACK_FOUND) set(OPENSSL_VERSION ${openssl_version_str}) endif () else() - find_package(OPENSSL REQUIRED) -endif(UNIX AND NOT APPLE) + find_package(OpenSSL REQUIRED) +endif(APPLE) + if(OPENSSL_FOUND) message(STATUS "openssl-${OPENSSL_VERSION} has been found\n") else() diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index a8bc314f46..c32795e1c3 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -13,12 +13,11 @@ SET(Draft_SRCS_base DraftVecUtils.py DraftGeomUtils.py DraftLayer.py - DraftEdit.py DraftFillet.py - DraftSelectPlane.py WorkingPlane.py getSVG.py TestDraft.py + TestDraftGui.py ) SET(Draft_import @@ -81,10 +80,13 @@ SET(Draft_GUI_tools draftguitools/gui_circulararray.py draftguitools/gui_orthoarray.py draftguitools/gui_polararray.py + draftguitools/gui_planeproxy.py + draftguitools/gui_selectplane.py draftguitools/gui_arrays.py draftguitools/gui_snaps.py draftguitools/gui_snapper.py draftguitools/gui_trackers.py + draftguitools/gui_edit.py draftguitools/README.md ) @@ -93,6 +95,9 @@ SET(Draft_task_panels drafttaskpanels/task_circulararray.py drafttaskpanels/task_orthoarray.py drafttaskpanels/task_polararray.py + drafttaskpanels/task_scale.py + drafttaskpanels/task_selectplane.py + drafttaskpanels/task_shapestring.py drafttaskpanels/README.md ) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index b93d067594..2f0aea0d45 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1,174 +1,173 @@ # -*- coding: utf-8 -*- -#*************************************************************************** -#* Copyright (c) 2009, 2010 Yorik van Havre * -#* Copyright (c) 2009, 2010 Ken Cline * -#* * -#* This program is free software; you can redistribute it and/or modify * -#* it under the terms of the GNU Lesser General Public License (LGPL) * -#* as published by the Free Software Foundation; either version 2 of * -#* the License, or (at your option) any later version. * -#* for detail see the LICENCE text file. * -#* * -#* This program 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 program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** - -#from __future__ import division - -__title__="FreeCAD Draft Workbench" -__author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, Dmitry Chigrin, Daniel Falck" -__url__ = "https://www.freecadweb.org" +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provide the Draft Workbench public programming interface. +The Draft module offers tools to create and manipulate 2D objects. +The functions in this file must be usable without requiring the +graphical user interface. +These functions can be used as the backend for the graphical commands +defined in `DraftTools.py`. +""" ## \addtogroup DRAFT # \brief Create and manipulate basic 2D objects # -# This module offers a range of tools to create and manipulate basic 2D objects +# This module offers tools to create and manipulate basic 2D objects # -# The module allows to create 2D geometric objects such as line, rectangle, circle, -# etc, modify these objects by moving, scaling or rotating them, and offers a couple of -# other utilities to manipulate further these objects, such as decompose them (downgrade) -# into smaller elements. +# The module allows to create 2D geometric objects such as line, rectangle, +# circle, etc., modify these objects by moving, scaling or rotating them, +# and offers a couple of other utilities to manipulate further these objects, +# such as decompose them (downgrade) into smaller elements. # # The functionality of the module is divided into GUI tools, usable from the -# FreeCAD interface, and corresponding python functions, that can perform the same -# operation programmatically. +# visual interface, and corresponding python functions, that can perform +# the same operation programmatically. # # @{ -"""The Draft module offers a range of tools to create and manipulate basic 2D objects""" - -import FreeCAD, math, sys, os, DraftVecUtils, WorkingPlane -import DraftGeomUtils -import draftutils.translate -from FreeCAD import Vector +import math +import sys from PySide.QtCore import QT_TRANSLATE_NOOP +import FreeCAD +from FreeCAD import Vector + +import DraftVecUtils +import WorkingPlane +from draftutils.translate import translate if FreeCAD.GuiUp: - import FreeCADGui, Draft_rc - from PySide import QtCore + import FreeCADGui + import Draft_rc gui = True - #from DraftGui import translate + # To prevent complaints from code checkers (flake8) + True if Draft_rc.__name__ else False else: - # def QT_TRANSLATE_NOOP(ctxt,txt): - # return txt - #print("FreeCAD Gui not present. Draft module will have some features disabled.") gui = False -translate = draftutils.translate.translate +__title__ = "FreeCAD Draft Workbench" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin, Daniel Falck") +__url__ = "https://www.freecadweb.org" -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # Backwards compatibility -#--------------------------------------------------------------------------- - -import DraftLayer -_VisGroup = DraftLayer.Layer -_ViewProviderVisGroup = DraftLayer.ViewProviderLayer -makeLayer = DraftLayer.makeLayer +# --------------------------------------------------------------------------- +from DraftLayer import Layer as _VisGroup +from DraftLayer import ViewProviderLayer as _ViewProviderVisGroup +from DraftLayer import makeLayer # import DraftFillet # Fillet = DraftFillet.Fillet # makeFillet = DraftFillet.makeFillet -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # General functions -#--------------------------------------------------------------------------- -import draftutils.utils -import draftutils.gui_utils +# --------------------------------------------------------------------------- +from draftutils.utils import ARROW_TYPES as arrowtypes -arrowtypes = draftutils.utils.ARROW_TYPES +from draftutils.utils import stringencodecoin +from draftutils.utils import string_encode_coin -stringencodecoin = draftutils.utils.string_encode_coin -string_encode_coin = draftutils.utils.string_encode_coin +from draftutils.utils import typecheck +from draftutils.utils import type_check -typecheck = draftutils.utils.type_check -type_check = draftutils.utils.type_check +from draftutils.utils import getParamType +from draftutils.utils import get_param_type -getParamType = draftutils.utils.get_param_type -get_param_type = draftutils.utils.get_param_type +from draftutils.utils import getParam +from draftutils.utils import get_param -getParam = draftutils.utils.get_param -get_param = draftutils.utils.get_param +from draftutils.utils import setParam +from draftutils.utils import set_param -setParam = draftutils.utils.set_param -set_param = draftutils.utils.set_param +from draftutils.utils import precision +from draftutils.utils import tolerance +from draftutils.utils import epsilon -precision = draftutils.utils.precision -tolerance = draftutils.utils.tolerance -epsilon = draftutils.utils.epsilon +from draftutils.utils import getRealName +from draftutils.utils import get_real_name -getRealName = draftutils.utils.get_real_name -get_real_name = draftutils.utils.get_real_name +from draftutils.utils import getType +from draftutils.utils import get_type -getType = draftutils.utils.get_type -get_type = draftutils.utils.get_type +from draftutils.utils import getObjectsOfType +from draftutils.utils import get_objects_of_type -getObjectsOfType = draftutils.utils.get_objects_of_type -get_objects_of_type = draftutils.utils.get_objects_of_type +from draftutils.utils import isClone +from draftutils.utils import is_clone -get3DView = draftutils.gui_utils.get_3d_view -get_3d_view = draftutils.gui_utils.get_3d_view +from draftutils.utils import getGroupNames +from draftutils.utils import get_group_names -isClone = draftutils.utils.is_clone -is_clone = draftutils.utils.is_clone +from draftutils.utils import ungroup -getGroupNames = draftutils.utils.get_group_names -get_group_names = draftutils.utils.get_group_names +from draftutils.utils import getGroupContents +from draftutils.utils import get_group_contents -ungroup = draftutils.utils.ungroup +from draftutils.utils import printShape +from draftutils.utils import print_shape -autogroup = draftutils.gui_utils.autogroup +from draftutils.utils import compareObjects +from draftutils.utils import compare_objects -dimSymbol = draftutils.gui_utils.dim_symbol -dim_symbol = draftutils.gui_utils.dim_symbol +from draftutils.utils import shapify -dimDash = draftutils.gui_utils.dim_dash -dim_dash = draftutils.gui_utils.dim_dash +from draftutils.utils import loadSvgPatterns +from draftutils.utils import load_svg_patterns -shapify = draftutils.utils.shapify +from draftutils.utils import svgpatterns +from draftutils.utils import svg_patterns -getGroupContents = draftutils.utils.get_group_contents -get_group_contents = draftutils.utils.get_group_contents +from draftutils.utils import getMovableChildren +from draftutils.utils import get_movable_children -removeHidden = draftutils.gui_utils.remove_hidden -remove_hidden = draftutils.gui_utils.remove_hidden +from draftutils.gui_utils import get3DView +from draftutils.gui_utils import get_3d_view -printShape = draftutils.utils.print_shape -print_shape = draftutils.utils.print_shape +from draftutils.gui_utils import autogroup -compareObjects = draftutils.utils.compare_objects -compare_objects = draftutils.utils.compare_objects +from draftutils.gui_utils import dimSymbol +from draftutils.gui_utils import dim_symbol -formatObject = draftutils.gui_utils.format_object -format_object = draftutils.gui_utils.format_object +from draftutils.gui_utils import dimDash +from draftutils.gui_utils import dim_dash -getSelection = draftutils.gui_utils.get_selection -get_selection = draftutils.gui_utils.get_selection +from draftutils.gui_utils import removeHidden +from draftutils.gui_utils import remove_hidden -getSelectionEx = draftutils.gui_utils.get_selection_ex -get_selection_ex = draftutils.gui_utils.get_selection_ex +from draftutils.gui_utils import formatObject +from draftutils.gui_utils import format_object -select = draftutils.gui_utils.select +from draftutils.gui_utils import getSelection +from draftutils.gui_utils import get_selection -loadSvgPatterns = draftutils.utils.load_svg_patterns -load_svg_patterns = draftutils.utils.load_svg_patterns +from draftutils.gui_utils import getSelectionEx +from draftutils.gui_utils import get_selection_ex -svgpatterns = draftutils.utils.svg_patterns -svg_patterns = draftutils.utils.svg_patterns +from draftutils.gui_utils import select -loadTexture = draftutils.gui_utils.load_texture -load_texture = draftutils.gui_utils.load_texture - -getMovableChildren = draftutils.utils.get_movable_children -get_movable_children = draftutils.utils.get_movable_children +from draftutils.gui_utils import loadTexture +from draftutils.gui_utils import load_texture def makeCircle(radius, placement=None, face=None, startangle=None, endangle=None, support=None): @@ -715,7 +714,7 @@ def makeArray(baseobject,arg1,arg2,arg3,arg4=None,arg5=None,arg6=None,name="Arra _Array(obj) obj.Base = baseobject if arg6: - if isinstance(arg1, (int, float)): + if isinstance(arg1, (int, float, FreeCAD.Units.Quantity)): obj.ArrayType = "circular" obj.RadialDistance = arg1 obj.TangentialDistance = arg2 @@ -2222,38 +2221,47 @@ def getCloneBase(obj,strict=False): return obj -def mirror(objlist,p1,p2): - """mirror(objlist,p1,p2,[clone]): creates a mirrored version of the given object(s) - along an axis that passes through the two vectors p1 and p2.""" +def mirror(objlist, p1, p2): + """mirror(objlist, p1, p2) + creates a Part::Mirror of the given object(s), along a plane defined + by the 2 given points and the draft working plane normal. + """ if not objlist: - FreeCAD.Console.PrintError(translate("draft","No object given")+"\n") + _err = "No object given" + FreeCAD.Console.PrintError(translate("draft", _err) + "\n") return if p1 == p2: - FreeCAD.Console.PrintError(translate("draft","The two points are coincident")+"\n") + _err = "The two points are coincident" + FreeCAD.Console.PrintError(translate("draft", _err) + "\n") return if not isinstance(objlist,list): objlist = [objlist] + if hasattr(FreeCAD, "DraftWorkingPlane"): + norm = FreeCAD.DraftWorkingPlane.getNormal() + elif gui: + norm = FreeCADGui.ActiveDocument.ActiveView.getViewDirection().negative() + else: + norm = FreeCAD.Vector(0,0,1) + + pnorm = p2.sub(p1).cross(norm).normalize() + result = [] for obj in objlist: mir = FreeCAD.ActiveDocument.addObject("Part::Mirroring","mirror") - mir.Label = "Mirror of "+obj.Label + mir.Label = "Mirror of " + obj.Label mir.Source = obj - if gui: - norm = FreeCADGui.ActiveDocument.ActiveView.getViewDirection().negative() - else: - norm = FreeCAD.Vector(0,0,1) - pnorm = p2.sub(p1).cross(norm).normalize() mir.Base = p1 mir.Normal = pnorm - formatObject(mir,obj) + formatObject(mir, obj) result.append(mir) if len(result) == 1: result = result[0] select(result) + return result @@ -3866,6 +3874,7 @@ class _ViewProviderDimension(_ViewProviderDraft): return mode def is_linked_to_circle(self): + import DraftGeomUtils _obj = self.Object if _obj.LinkedGeometry and len(_obj.LinkedGeometry) == 1: lobj = _obj.LinkedGeometry[0][0] diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index c98dc29506..6c2cd6ed2b 100644 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -1,29 +1,31 @@ -#*************************************************************************** -#* Copyright (c) 2009, 2010 Yorik van Havre * -#* Copyright (c) 2009, 2010 Ken Cline * -#* * -#* This program is free software; you can redistribute it and/or modify * -#* it under the terms of the GNU Lesser General Public License (LGPL) * -#* as published by the Free Software Foundation; either version 2 of * -#* the License, or (at your option) any later version. * -#* for detail see the LICENCE text file. * -#* * -#* This program 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 program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** - -__title__="FreeCAD Draft Workbench - Geometry library" -__author__ = "Yorik van Havre, Jacques-Antoine Gaudin, Ken Cline" -__url__ = ["https://www.freecadweb.org"] +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Define geometry functions for manipulating shapes in the Draft Workbench. +These functions are used by different object creation functions +of the Draft Workbench, both in `Draft.py` and `DraftTools.py`. +They operate on the internal shapes (`Part::TopoShape`) of different objects +and on their subelements, that is, vertices, edges, and faces. +""" ## \defgroup DRAFTGEOMUTILS DraftGeomUtils # \ingroup UTILITIES # \brief Shape manipulation utilities for the Draft workbench @@ -32,20 +34,27 @@ __url__ = ["https://www.freecadweb.org"] ## \addtogroup DRAFTGEOMUTILS # @{ +import cmath +import math -"this file contains generic geometry functions for manipulating Part shapes" - -import FreeCAD, Part, DraftVecUtils, math, cmath +import FreeCAD +import Part +import DraftVecUtils from FreeCAD import Vector -NORM = Vector(0,0,1) # provisory normal direction for all geometry ops. +__title__ = "FreeCAD Draft Workbench - Geometry library" +__author__ = "Yorik van Havre, Jacques-Antoine Gaudin, Ken Cline" +__url__ = ["https://www.freecadweb.org"] + +NORM = Vector(0, 0, 1) # provisory normal direction for all geometry ops. params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") # Generic functions ********************************************************* + def precision(): - "precision(): returns the Draft precision setting" + """precision(): returns the Draft precision setting""" # Set precision level with a cap to avoid overspecification that: # 1 - whilst it is precise enough (e.g. that OCC would consider 2 points are coincident) # (not sure what it should be 10 or otherwise); @@ -55,10 +64,11 @@ def precision(): precisionMax = 10 precisionInt = params.GetInt("precision",6) precisionInt = (precisionInt if precisionInt <=10 else precisionMax) - return precisionInt #return params.GetInt("precision",6) + return precisionInt # return params.GetInt("precision",6) + def vec(edge): - "vec(edge) or vec(line): returns a vector from an edge or a Part.LineSegment" + """vec(edge) or vec(line): returns a vector from an edge or a Part.LineSegment""" # if edge is not straight, you'll get strange results! if isinstance(edge,Part.Shape): return edge.Vertexes[-1].Point.sub(edge.Vertexes[0].Point) @@ -67,14 +77,16 @@ def vec(edge): else: return None -def edg(p1,p2): - "edg(Vector,Vector): returns an edge from 2 vectors" + +def edg(p1, p2): + """edg(Vector,Vector): returns an edge from 2 vectors""" if isinstance(p1,FreeCAD.Vector) and isinstance(p2,FreeCAD.Vector): if DraftVecUtils.equals(p1,p2): return None else: return Part.LineSegment(p1,p2).toShape() + def getVerts(shape): - "getVerts(shape): returns a list containing vectors of each vertex of the shape" + """getVerts(shape): returns a list containing vectors of each vertex of the shape""" if not hasattr(shape,"Vertexes"): return [] p = [] @@ -82,13 +94,15 @@ def getVerts(shape): p.append(v.Point) return p + def v1(edge): - "v1(edge): returns the first point of an edge" + """v1(edge): returns the first point of an edge""" return edge.Vertexes[0].Point + def isNull(something): - '''isNull(object): returns true if the given shape is null or the given placement is null or - if the given vector is (0,0,0)''' + """isNull(object): returns true if the given shape is null or the given placement is null or + if the given vector is (0,0,0)""" if isinstance(something,Part.Shape): return something.isNull() elif isinstance(something,FreeCAD.Vector): @@ -102,8 +116,9 @@ def isNull(something): else: return False -def isPtOnEdge(pt,edge) : - '''isPtOnEdge(Vector,edge): Tests if a point is on an edge''' + +def isPtOnEdge(pt, edge): + """isPtOnEdge(Vector,edge): Tests if a point is on an edge""" v = Part.Vertex(pt) try: d = v.distToShape(edge) @@ -115,15 +130,17 @@ def isPtOnEdge(pt,edge) : return True return False + def hasCurves(shape): - "hasCurve(shape): checks if the given shape has curves" + """hasCurve(shape): checks if the given shape has curves""" for e in shape.Edges: if not isinstance(e.Curve,(Part.LineSegment,Part.Line)): return True return False -def isAligned(edge,axis="x"): - "isAligned(edge,axis): checks if the given edge or line is aligned to the given axis (x, y or z)" + +def isAligned(edge, axis="x"): + """isAligned(edge,axis): checks if the given edge or line is aligned to the given axis (x, y or z)""" if axis == "x": if isinstance(edge,Part.Edge): if len(edge.Vertexes) == 2: @@ -150,6 +167,7 @@ def isAligned(edge,axis="x"): return True return False + def getQuad(face): """getQuad(face): returns a list of 3 vectors (basepoint, Xdir, Ydir) if the face is a quad, or None if not.""" @@ -170,7 +188,8 @@ def getQuad(face): ov.normalize() return [face.Edges[0].Vertexes[0].Point,v1,ov] -def areColinear(e1,e2): + +def areColinear(e1, e2): """areColinear(e1,e2): returns True if both edges are colinear""" if not isinstance(e1.Curve,(Part.LineSegment,Part.Line)): return False @@ -189,8 +208,9 @@ def areColinear(e1,e2): return True return False + def hasOnlyWires(shape): - "hasOnlyWires(shape): returns True if all the edges are inside a wire" + """hasOnlyWires(shape): returns True if all the edges are inside a wire""" ne = 0 for w in shape.Wires: ne += len(w.Edges) @@ -198,8 +218,9 @@ def hasOnlyWires(shape): return True return False + def geomType(edge): - "returns the type of geom this edge is based on" + """returns the type of geom this edge is based on""" try: if isinstance(edge.Curve,(Part.LineSegment,Part.Line)): return "Line" @@ -216,8 +237,9 @@ def geomType(edge): except: return "Unknown" + def isValidPath(shape): - "isValidPath(shape): returns True if the shape can be used as an extrusion path" + """isValidPath(shape): returns True if the shape can be used as an extrusion path""" if shape.isNull(): return False if shape.Faces: @@ -231,10 +253,11 @@ def isValidPath(shape): return False return True -# edge functions ***************************************************************** +# edge functions ************************************************************* -def findEdge(anEdge,aList): - '''findEdge(anEdge,aList): returns True if anEdge is found in aList of edges''' + +def findEdge(anEdge, aList): + """findEdge(anEdge,aList): returns True if anEdge is found in aList of edges""" for e in range(len(aList)): if str(anEdge.Curve) == str(aList[e].Curve): if DraftVecUtils.equals(anEdge.Vertexes[0].Point,aList[e].Vertexes[0].Point): @@ -243,13 +266,16 @@ def findEdge(anEdge,aList): return None -def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=False,dts=True,findAll=False) : - '''findIntersection(edge1,edge2,infinite1=False,infinite2=False,dts=True): +def findIntersection(edge1, edge2, + infinite1=False, infinite2=False, + ex1=False, ex2=False, + dts=True, findAll=False): + """findIntersection(edge1,edge2,infinite1=False,infinite2=False,dts=True): returns a list containing the intersection point(s) of 2 edges. You can also feed 4 points instead of edge1 and edge2. If dts is used, - Shape.distToShape() is used, which can be buggy''' + Shape.distToShape() is used, which can be buggy""" - def getLineIntersections(pt1,pt2,pt3,pt4,infinite1,infinite2): + def getLineIntersections(pt1, pt2, pt3, pt4, infinite1, infinite2): if pt1: # first check if we don't already have coincident endpoints if (pt1 in [pt3,pt4]): @@ -416,9 +442,7 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F return int elif (geomType(edge1) == "Circle") and (geomType(edge2) == "Circle") : - # deals with 2 arcs or circles - cent1, cent2 = edge1.Curve.Center, edge2.Curve.Center rad1 , rad2 = edge1.Curve.Radius, edge2.Curve.Radius axis1, axis2 = edge1.Curve.Axis , edge2.Curve.Axis @@ -450,7 +474,7 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F else : int = [cent1.add(c2c)] else : - return [] # circles are on parallel planes + return [] # circles are on parallel planes else : # circles aren't on same plane axis1.normalize() ; axis2.normalize() @@ -481,15 +505,17 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F print("DraftGeomUtils: Unsupported curve type: (" + str(edge1.Curve) + ", " + str(edge2.Curve) + ")") return [] -def wiresIntersect(wire1,wire2): - "wiresIntersect(wire1,wire2): returns True if some of the edges of the wires are intersecting otherwise False" + +def wiresIntersect(wire1, wire2): + """wiresIntersect(wire1,wire2): returns True if some of the edges of the wires are intersecting otherwise False""" for e1 in wire1.Edges: for e2 in wire2.Edges: if findIntersection(e1,e2,dts=False): return True return False -def pocket2d(shape,offset): + +def pocket2d(shape, offset): """pocket2d(shape,offset): return a list of wires obtained from offsetting the wires from the given shape by the given offset, and intersection if needed.""" # find the outer wire @@ -555,6 +581,7 @@ def pocket2d(shape,offset): offsetWires = [o for o in offsetWires if o != None] return offsetWires + def orientEdge(edge, normal=None, make_arc=False): """Re-orients 'edge' such that it is in the x-y plane. If 'normal' is passed, this is used as the basis for the rotation, otherwise the Placement property of 'edge' @@ -585,8 +612,9 @@ def orientEdge(edge, normal=None, make_arc=False): edge.LastParameter,edge.Curve.Axis.z>0) return edge.Curve -def mirror (point, edge): - "finds mirror point relative to an edge" + +def mirror(point, edge): + """Find mirror point relative to an edge.""" normPoint = point.add(findDistance(point, edge, False)) if normPoint: normPoint_point = Vector.sub(point, normPoint) @@ -596,8 +624,9 @@ def mirror (point, edge): else: return None -def isClockwise(edge,ref=None): - """Returns True if a circle-based edge has a clockwise direction""" + +def isClockwise(edge, ref=None): + """Return True if a circle-based edge has a clockwise direction.""" if not geomType(edge) == "Circle": return True v1 = edge.Curve.tangent(edge.ParameterRange[0])[0] @@ -617,7 +646,8 @@ def isClockwise(edge,ref=None): return False return True -def isSameLine(e1,e2): + +def isSameLine(e1, e2): """isSameLine(e1,e2): return True if the 2 edges are lines and have the same points""" if not isinstance(e1.Curve,Part.LineSegment): @@ -632,6 +662,7 @@ def isSameLine(e1,e2): return True return False + def isWideAngle(edge): """returns True if the given edge is an arc with angle > 180 degrees""" if geomType(edge) != "Circle": @@ -642,12 +673,13 @@ def isWideAngle(edge): return True return False -def findClosest(basepoint,pointslist): - ''' + +def findClosest(basepoint, pointslist): + """ findClosest(vector,list) in a list of 3d points, finds the closest point to the base point. an index from the list is returned. - ''' + """ npoint = None if not pointslist: return None @@ -659,8 +691,9 @@ def findClosest(basepoint,pointslist): npoint = n return npoint + def concatenate(shape): - "concatenate(shape) -- turns several faces into one" + """concatenate(shape) -- turns several faces into one""" edges = getBoundary(shape) edges = Part.__sortEdges__(edges) try: @@ -673,8 +706,9 @@ def concatenate(shape): if not wire.isClosed(): return(wire) else: return(face) + def getBoundary(shape): - "getBoundary(shape) -- this function returns the boundary edges of a group of faces" + """getBoundary(shape) -- this function returns the boundary edges of a group of faces""" # make a lookup-table where we get the number of occurrences # to each edge in the fused face if isinstance(shape,list): @@ -691,8 +725,9 @@ def getBoundary(shape): if lut[e.hashCode()] == 1: bound.append(e) return bound + def isLine(bsp): - "returns True if the given BSpline curve is a straight line" + """Return True if the given BSpline curve is a straight line.""" step = bsp.LastParameter/10 b = bsp.tangent(0) for i in range(10): @@ -702,8 +737,7 @@ def isLine(bsp): def sortEdges(edges): - "Deprecated. Use Part.__sortEdges__ instead" - + """Deprecated. Use Part.__sortEdges__ instead.""" raise DeprecationWarning("Deprecated. Use Part.__sortEdges__ instead") # Build a dictionary of edges according to their end points. @@ -780,8 +814,7 @@ def sortEdges(edges): def sortEdgesOld(lEdges, aVertex=None): - "Deprecated. Use Part.__sortEdges__ instead" - + """Deprecated. Use Part.__sortEdges__ instead.""" raise DeprecationWarning("Deprecated. Use Part.__sortEdges__ instead") #There is no reason to limit this to lines only because every non-closed edge always @@ -792,8 +825,8 @@ def sortEdgesOld(lEdges, aVertex=None): # return lEdges def lookfor(aVertex, inEdges): - ''' Look for (aVertex, inEdges) returns count, the position of the instance - the position in the instance and the instance of the Edge''' + """Look for (aVertex, inEdges) returns count, the position of the instance + the position in the instance and the instance of the Edge""" count = 0 linstances = [] #lists the instances of aVertex for i in range(len(inEdges)) : @@ -872,7 +905,7 @@ def sortEdgesOld(lEdges, aVertex=None): def invert(shape): - '''invert(edge): returns an inverted copy of this edge or wire''' + """invert(edge): returns an inverted copy of this edge or wire""" if shape.ShapeType == "Wire": edges = [invert(edge) for edge in shape.OrderedEdges] edges.reverse() @@ -894,9 +927,10 @@ def invert(shape): print("DraftGeomUtils.invert: unable to handle",shape.ShapeType) return shape + def flattenWire(wire): - '''flattenWire(wire): forces a wire to get completely flat - along its normal.''' + """flattenWire(wire): forces a wire to get completely flat + along its normal.""" import WorkingPlane n = getNormal(wire) if not n: @@ -912,11 +946,13 @@ def flattenWire(wire): w = Part.makePolygon(verts) return w + def findWires(edgeslist): return [ Part.Wire(e) for e in Part.sortEdges(edgeslist)] + def findWiresOld2(edgeslist): - '''finds connected wires in the given list of edges''' + """Find connected wires in the given list of edges.""" def touches(e1,e2): if len(e1.Vertexes) < 2: @@ -973,9 +1009,10 @@ def findWiresOld2(edgeslist): nwires.append(wi) return nwires -def superWire(edgeslist,closed=False): - '''superWire(edges,[closed]): forces a wire between edges that don't necessarily - have coincident endpoints. If closed=True, wire will always be closed''' + +def superWire(edgeslist, closed=False): + """superWire(edges,[closed]): forces a wire between edges that don't necessarily + have coincident endpoints. If closed=True, wire will always be closed""" def median(v1,v2): vd = v2.sub(v1) vd.scale(.5,.5,.5) @@ -1027,8 +1064,9 @@ def superWire(edgeslist,closed=False): print(newedges) return Part.Wire(newedges) + def findMidpoint(edge): - "calculates the midpoint of an edge" + """Calculate the midpoint of an edge.""" first = edge.Vertexes[0].Point last = edge.Vertexes[-1].Point if geomType(edge) == "Circle": @@ -1058,15 +1096,15 @@ def findMidpoint(edge): return None -def findPerpendicular(point,edgeslist,force=None): - ''' +def findPerpendicular(point, edgeslist, force=None): + """ findPerpendicular(vector,wire,[force]): finds the shortest perpendicular distance between a point and an edgeslist. If force is specified, only the edge[force] will be considered, and it will be considered infinite. The function will return a list [vector_from_point_to_closest_edge,edge_index] or None if no perpendicular vector could be found. - ''' + """ if not isinstance(edgeslist,list): try: edgeslist = edgeslist.Edges @@ -1089,13 +1127,14 @@ def findPerpendicular(point,edgeslist,force=None): else: return None return None -def offset(edge,vector,trim=False): - ''' + +def offset(edge, vector, trim=False): + """ offset(edge,vector) returns a copy of the edge at a certain (vector) distance if the edge is an arc, the vector will be added at its first point and a complete circle will be returned - ''' + """ if (not isinstance(edge,Part.Shape)) or (not isinstance(vector,FreeCAD.Vector)): return None if geomType(edge) == "Line": @@ -1113,9 +1152,9 @@ def offset(edge,vector,trim=False): else: return None -def isReallyClosed(wire): - "checks if a wire is really closed" +def isReallyClosed(wire): + """Check if a wire is really closed.""" ## TODO yet to find out why not use wire.isClosed() direct, in isReallyClosed(wire) # Remark out below - Found not true if a vertex is used again in a wire in sketch ( e.g. wire with shape like 'd', 'b', 'g'... ) @@ -1139,8 +1178,20 @@ def isReallyClosed(wire): if DraftVecUtils.equals(v1,v2): return True return False +def getSplineNormal(edge): + """Find the normal of a BSpline edge""" + startPoint = edge.valueAt(edge.FirstParameter) + endPoint = edge.valueAt(edge.LastParameter) + midParameter = edge.FirstParameter + (edge.LastParameter - edge.FirstParameter)/2 + midPoint = edge.valueAt(midParameter) + v1 = midPoint - startPoint + v2 = midPoint - endPoint + n = v1.cross(v2) + n.normalize() + return n + def getNormal(shape): - "finds the normal of a shape, if possible" + """Find the normal of a shape, if possible.""" n = Vector(0,0,1) if shape.isNull(): return n @@ -1149,11 +1200,18 @@ def getNormal(shape): elif shape.ShapeType == "Edge": if geomType(shape.Edges[0]) in ["Circle","Ellipse"]: n = shape.Edges[0].Curve.Axis + elif geomType(edge) == "BSplineCurve" or \ + geomType(edge) == "BezierCurve": + n = getSplineNormal(edge) else: for e in shape.Edges: if geomType(e) in ["Circle","Ellipse"]: n = e.Curve.Axis break + elif geomType(e) == "BSplineCurve" or \ + geomType(e) == "BezierCurve": + n = getSplineNormal(e) + break e1 = vec(shape.Edges[0]) for i in range(1,len(shape.Edges)): e2 = vec(shape.Edges[i]) @@ -1169,8 +1227,9 @@ def getNormal(shape): return None return n -def getRotation(v1,v2=FreeCAD.Vector(0,0,1)): - '''Get the rotation Quaternion between 2 vectors''' + +def getRotation(v1, v2=FreeCAD.Vector(0, 0, 1)): + """Get the rotation Quaternion between 2 vectors.""" if (v1.dot(v2) > 0.999999) or (v1.dot(v2) < -0.999999): # vectors are opposite return None @@ -1180,10 +1239,11 @@ def getRotation(v1,v2=FreeCAD.Vector(0,0,1)): angle = math.degrees(DraftVecUtils.angle(v1,v2,axis)) return FreeCAD.Rotation(axis,angle) + def calculatePlacement(shape): - '''calculatePlacement(shape): if the given shape is planar, this function + """calculatePlacement(shape): if the given shape is planar, this function returns a placement located at the center of gravity of the shape, and oriented - towards the shape's normal. Otherwise, it returns a null placement.''' + towards the shape's normal. Otherwise, it returns a null placement.""" if not isPlanar(shape): return FreeCAD.Placement() pos = shape.BoundBox.Center @@ -1196,8 +1256,10 @@ def calculatePlacement(shape): return pla -def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, alignList=[], normal=None, basewireOffset=0): # offsetMode="BasewireMode" or None - ''' +def offsetWire(wire, dvec, bind=False, occ=False, + widthList=None, offsetMode=None, alignList=[], + normal=None, basewireOffset=0): # offsetMode="BasewireMode" or None + """ offsetWire(wire,vector,[bind]): offsets the given wire along the given vector. The vector will be applied at the first vertex of the wire. If bind is True (and the shape is open), the original wire and the offsetted one @@ -1218,16 +1280,16 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a 'dvec' vector to offset is now derived (and can be ignored) in this function if widthList and alignList are provided - 'dvec' to be obsolete in future ? 'basewireOffset' corresponds to 'offset' in ArchWall which offset the basewire before creating the wall outline - ''' + """ # Accept 'wire' as a list of edges (use the list directly), or previously as a wire or a face (Draft Wire with MakeFace True or False supported) if isinstance(wire,Part.Wire) or isinstance(wire,Part.Face): - edges = wire.Edges # Seems has repeatedly sortEdges, remark out here - edges = Part.__sortEdges__(wire.Edges) + edges = wire.Edges # Seems has repeatedly sortEdges, remark out here - edges = Part.__sortEdges__(wire.Edges) elif isinstance(wire, list): if isinstance(wire[0],Part.Edge): edges = wire.copy() - wire = Part.Wire( Part.__sortEdges__(edges) ) # How to avoid __sortEdges__ again? Make getNormal directly tackle edges ? + wire = Part.Wire( Part.__sortEdges__(edges) ) # How to avoid __sortEdges__ again? Make getNormal directly tackle edges ? else: print ("Either Part.Wire or Part.Edges should be provided, returning None ") return None @@ -1237,7 +1299,7 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a if normal: norm = normal else: - norm = getNormal(wire) #norm = Vector(0,0,1) + norm = getNormal(wire) # norm = Vector(0, 0, 1) closed = isReallyClosed(wire) nedges = [] @@ -1260,10 +1322,10 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a # Make a copy of alignList - to avoid changes in this function become starting input of next call of this function ? # https://www.dataquest.io/blog/tutorial-functions-modify-lists-dictionaries-python/ - alignListC = alignList.copy() + # alignListC = alignList.copy() # Only Python 3 + alignListC = list(alignList) # Python 2 and 3 # Check the direction / offset of starting edge - firstDir = None try: if alignListC[0] == 'Left': @@ -1279,7 +1341,6 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a pass # Should no longer happen for ArchWall - as aligns are 'filled in' by ArchWall # If not provided by alignListC checked above, check the direction of offset in dvec (not 'align') - if not firstDir: ## TODO Should check if dvec is provided or not ('legacy/backward-compatible' mode) if isinstance(e.Curve,Part.Circle): # need to test against Part.Circle, not Part.ArcOfCircle v0 = e.Vertexes[0].Point.sub(e.Curve.Center) @@ -1301,7 +1362,6 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a alignListC.append('Left') for i in range(len(edges)): - # make a copy so it do not reverse the self.baseWires edges pointed to by _Wall.getExtrusionData() ? curredge = edges[i].copy() @@ -1322,7 +1382,6 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a curOrientation = curredge.Vertexes[0].Orientation # TODO Could be edge.Orientation in fact # Consider individual edge width - if widthList: # ArchWall should now always provide widthList try: if widthList[i] > 0: @@ -1342,12 +1401,11 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a delta = DraftVecUtils.scaleTo(delta,dvec.Length) # Consider individual edge Align direction - ArchWall should now always provide alignList - if i == 0: if alignListC[0] == 'Center': delta = DraftVecUtils.scaleTo(delta, delta.Length/2) - #No need to do anything for 'Left' and 'Rigtht' as original dvec have set both the direction and amount of offset correct - #elif alignListC[i] == 'Left': #elif alignListC[i] == 'Right': + # No need to do anything for 'Left' and 'Right' as original dvec have set both the direction and amount of offset correct + # elif alignListC[i] == 'Left': #elif alignListC[i] == 'Right': if i != 0: try: if alignListC[i] == 'Left': @@ -1370,7 +1428,6 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a delta = DraftVecUtils.scaleTo(delta, delta.Length/2) # Consider whether generating the 'offset wire' or the 'base wire' - if offsetMode == None: # Consider if curOrientation and/or curDir match their firstOrientation/firstDir - to determine whether and how to offset the current edge if (curOrientation == firstOrientation) != (curDir == firstDir): # i.e. xor @@ -1385,7 +1442,7 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a delta = DraftVecUtils.scaleTo(delta, delta.Length+basewireOffset) nedge = offset(curredge,delta,trim=True) - if curOrientation == "Reversed": # TODO arc always in counter-clockwise directinon ... ( not necessarily 'reversed') + if curOrientation == "Reversed": # TODO arc always in counter-clockwise directinon ... ( not necessarily 'reversed') if not isinstance(curredge.Curve,Part.Circle): # need to test against Part.Circle, not Part.ArcOfCircle # if not arc/circle, assume straight line, reverse it nedge = Part.Edge(nedge.Vertexes[1],nedge.Vertexes[0]) @@ -1428,7 +1485,7 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a nedge = Part.ArcOfCircle(nedge.Vertexes[1].Point, midOfArc, nedge.Vertexes[0].Point).toShape() # TODO any better solution than to calculate midpoint of arc to reverse ? else: - print (" something wrong ") + print(" something wrong ") return if not nedge: return None @@ -1449,8 +1506,8 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a else: return nedges -def connect(edges,closed=False): - '''connects the edges in the given list by their intersections''' +def connect(edges, closed=False): + """Connect the edges in the given list by their intersections.""" nedges = [] v2 = None @@ -1515,12 +1572,12 @@ def connect(edges,closed=False): print(e.Curve, " ",e.Vertexes[0].Point, " ", e.Vertexes[-1].Point) return None -def findDistance(point,edge,strict=False): - ''' +def findDistance(point, edge, strict=False): + """ findDistance(vector,edge,[strict]) - Returns a vector from the point to its closest point on the edge. If strict is True, the vector will be returned only if its endpoint lies on the edge. Edge can also be a list of 2 points. - ''' + """ if isinstance(point, FreeCAD.Vector): if isinstance(edge,list): segment = edge[1].sub(edge[0]) @@ -1603,7 +1660,7 @@ def findDistance(point,edge,strict=False): def angleBisection(edge1, edge2): - "angleBisection(edge,edge) - Returns an edge that bisects the angle between the 2 edges." + """angleBisection(edge,edge) - Returns an edge that bisects the angle between the 2 edges.""" if (geomType(edge1) == "Line") and (geomType(edge2) == "Line"): p1 = edge1.Vertexes[0].Point p2 = edge1.Vertexes[-1].Point @@ -1626,8 +1683,8 @@ def angleBisection(edge1, edge2): else: return None -def findClosestCircle(point,circles): - "findClosestCircle(Vector, list of circles) -- returns the circle with closest center" +def findClosestCircle(point, circles): + """Return the circle with closest center.""" dist = 1000000 closest = None for c in circles: @@ -1636,8 +1693,9 @@ def findClosestCircle(point,circles): closest = c return closest -def isCoplanar(faces,tolerance=0): - "isCoplanar(faces,[tolerance]): checks if all faces in the given list are coplanar. Tolerance is the max deviation to be considered coplanar" + +def isCoplanar(faces, tolerance=0): + """isCoplanar(faces,[tolerance]): checks if all faces in the given list are coplanar. Tolerance is the max deviation to be considered coplanar""" if len(faces) < 2: return True base =faces[0].normalAt(0,0) @@ -1649,8 +1707,9 @@ def isCoplanar(faces,tolerance=0): return False return True + def isPlanar(shape): - "checks if the given shape is planar" + """Check if the given shape is planar.""" if len(shape.Vertexes) <= 3: return True n = getNormal(shape) @@ -1661,9 +1720,10 @@ def isPlanar(shape): return False return True + def findWiresOld(edges): - '''finds connected edges in the list, and returns a list of lists containing edges - that can be connected''' + """finds connected edges in the list, and returns a list of lists containing edges + that can be connected""" raise DeprecationWarning("This function shouldn't be called anymore - use findWires() instead") def verts(shape): return [shape.Vertexes[0].Point,shape.Vertexes[-1].Point] @@ -1693,11 +1753,12 @@ def findWiresOld(edges): edgeSet = result[1] return result[1] -def getTangent(edge,frompoint=None): - ''' + +def getTangent(edge, frompoint=None): + """ returns the tangent to an edge. If from point is given, it is used to calculate the tangent (only useful for an arc of course). - ''' + """ if geomType(edge) == "Line": return vec(edge) elif geomType(edge) == "BSplineCurve" or \ @@ -1714,9 +1775,10 @@ def getTangent(edge,frompoint=None): return v1.cross(edge.Curve.Axis) return None -def bind(w1,w2): - '''bind(wire1,wire2): binds 2 wires by their endpoints and - returns a face''' + +def bind(w1, w2): + """bind(wire1,wire2): binds 2 wires by their endpoints and + returns a face""" if (not w1) or (not w2): print("DraftGeomUtils: unable to bind wires") return None @@ -1739,16 +1801,16 @@ def bind(w1,w2): return None def cleanFaces(shape): - "removes inner edges from coplanar faces" + """Remove inner edges from coplanar faces.""" faceset = shape.Faces def find(hc): - "finds a face with the given hashcode" + """finds a face with the given hashcode""" for f in faceset: if f.hashCode() == hc: return f def findNeighbour(hface,hfacelist): - "finds the first neighbour of a face in a list, and returns its index" + """finds the first neighbour of a face in a list, and returns its index""" eset = [] for e in find(hface).Edges: eset.append(e.hashCode()) @@ -1839,8 +1901,8 @@ def cleanFaces(shape): def isCubic(shape): - '''isCubic(shape): verifies if a shape is cubic, that is, has - 8 vertices, 6 faces, and all angles are 90 degrees.''' + """isCubic(shape): verifies if a shape is cubic, that is, has + 8 vertices, 6 faces, and all angles are 90 degrees.""" # first we try fast methods if len(shape.Vertexes) != 8: return False @@ -1864,10 +1926,11 @@ def isCubic(shape): return False return True + def getCubicDimensions(shape): - '''getCubicDimensions(shape): returns a list containing the placement, + """getCubicDimensions(shape): returns a list containing the placement, the length, the width and the height of a cubic shape. If not cubic, nothing - is returned. The placement point is the lowest corner of the shape.''' + is returned. The placement point is the lowest corner of the shape.""" if not isCubic(shape): return None # determine lowest face, which will be our base z = [10,1000000000000] @@ -1905,9 +1968,10 @@ def getCubicDimensions(shape): mat.rotateZ(rotZ) return [FreeCAD.Placement(mat),round(vx.Length,precision()),round(vy.Length,precision()),round(vz.Length,precision())] + def removeInterVertices(wire): - '''removeInterVertices(wire) - remove unneeded vertices (those that - are in the middle of a straight line) from a wire, returns a new wire.''' + """removeInterVertices(wire) - remove unneeded vertices (those that + are in the middle of a straight line) from a wire, returns a new wire.""" edges = Part.__sortEdges__(wire.Edges) nverts = [] def getvec(v1,v2): @@ -1928,6 +1992,7 @@ def removeInterVertices(wire): else: return wire + def arcFromSpline(edge): """arcFromSpline(edge): turns the given edge into an arc, by taking its first point, midpoint and endpoint. Works best with bspline @@ -1961,33 +2026,33 @@ def arcFromSpline(edge): except: print("couldn't make a circle out of this edge") -# Fillet code graciously donated by Jacques-Antoine Gaudin -def fillet(lEdges,r,chamfer=False): - '''fillet(lEdges,r,chamfer=False): Take a list of two Edges & a float as argument, - Returns a list of sorted edges describing a round corner''' +def fillet(lEdges, r, chamfer=False): + """fillet(lEdges,r,chamfer=False): Take a list of two Edges & a float as argument, + Returns a list of sorted edges describing a round corner""" + # Fillet code graciously donated by Jacques-Antoine Gaudin - def getCurveType(edge,existingCurveType = None): - '''Builds or completes a dictionary containing edges with keys "Arc" and "Line"''' - if not existingCurveType : + def getCurveType(edge, existingCurveType=None): + """Builds or completes a dictionary containing edges with keys "Arc" and 'Line'""" + if not existingCurveType: existingCurveType = { 'Line' : [], 'Arc' : [] } - if issubclass(type(edge.Curve),Part.LineSegment) : + if issubclass(type(edge.Curve),Part.LineSegment): existingCurveType['Line'] += [edge] - elif issubclass(type(edge.Curve),Part.Line) : + elif issubclass(type(edge.Curve),Part.Line): existingCurveType['Line'] += [edge] - elif issubclass(type(edge.Curve),Part.Circle) : + elif issubclass(type(edge.Curve),Part.Circle): existingCurveType['Arc'] += [edge] - else : + else: raise ValueError("Edge's curve must be either Line or Arc") return existingCurveType rndEdges = lEdges[0:2] rndEdges = Part.__sortEdges__(rndEdges) - if len(rndEdges) < 2 : + if len(rndEdges) < 2: return rndEdges - if r <= 0 : + if r <= 0: print("DraftGeomUtils.fillet : Error : radius is negative.") return rndEdges @@ -1997,9 +2062,7 @@ def fillet(lEdges,r,chamfer=False): lVertexes = rndEdges[0].Vertexes + [rndEdges[1].Vertexes[-1]] if len(curveType['Line']) == 2: - # Deals with 2-line-edges lists -------------------------------------- - U1 = lVertexes[0].Point.sub(lVertexes[1].Point) ; U1.normalize() U2 = lVertexes[2].Point.sub(lVertexes[1].Point) ; U2.normalize() alpha = U1.getAngle(U2) @@ -2046,12 +2109,10 @@ def fillet(lEdges,r,chamfer=False): return rndEdges elif len(curveType['Arc']) == 1 : - - # Deals with lists containing an arc and a line ---------------------------------- - - if lEdges[0] in curveType['Arc'] : + # Deals with lists containing an arc and a line ---------------------- + if lEdges[0] in curveType['Arc']: lineEnd = lVertexes[2] ; arcEnd = lVertexes[0] ; arcFirst = True - else : + else: lineEnd = lVertexes[0] ; arcEnd = lVertexes[2] ; arcFirst = False arcCenter = curveType['Arc'][0].Curve.Center arcRadius = curveType['Arc'][0].Curve.Radius @@ -2062,23 +2123,23 @@ def fillet(lEdges,r,chamfer=False): toCenter = arcCenter.sub(lVertexes[1].Point) if arcFirst : # make sure the tangent points towards the arc T = arcAxis.cross(toCenter) - else : + else: T = toCenter.cross(arcAxis) projCenter = toCenter.dot(U1) - if round(abs(projCenter),precision()) > 0 : + if round(abs(projCenter),precision()) > 0: normToLine = U1.cross(T).cross(U1) - else : + else: normToLine = Vector(toCenter) normToLine.normalize() dCenterToLine = toCenter.dot(normToLine) - r - if round(projCenter,precision()) > 0 : + if round(projCenter,precision()) > 0: newRadius = arcRadius - r elif round(projCenter,precision()) < 0 or (round(projCenter,precision()) == 0 and U1.dot(T) > 0): newRadius = arcRadius + r - else : + else: print("DraftGeomUtils.fillet : Warning : edges are already tangent. Did nothing") return rndEdges @@ -2137,9 +2198,7 @@ def fillet(lEdges,r,chamfer=False): return rndEdges elif len(curveType['Arc']) == 2 : - - # Deals with lists of 2 arc-edges -------------------------------------------- - + # Deals with lists of 2 arc-edges ----------------------------------- arcCenter, arcRadius, arcAxis, arcLength, toCenter, T, newRadius = [], [], [], [], [], [], [] for i in range(2) : arcCenter += [curveType['Arc'][i].Curve.Center] @@ -2164,10 +2223,10 @@ def fillet(lEdges,r,chamfer=False): elif T[0].dot(T[1]) > 0 : newRadius += [arcRadius[0]+r] newRadius += [arcRadius[1]+r] - else : + else: print("DraftGeomUtils.fillet : Warning : edges are already tangent. Did nothing") return rndEdges - elif not sameDirection : + elif not sameDirection: if round(TcrossT.dot(arcAxis[0]),precision()) > 0 : newRadius += [arcRadius[0]+r] newRadius += [arcRadius[1]-r] @@ -2238,10 +2297,11 @@ def fillet(lEdges,r,chamfer=False): return rndEdges -def filletWire(aWire,r,chamfer=False): - ''' Fillets each angle of a wire with r as radius value + +def filletWire(aWire, r, chamfer=False): + """Fillets each angle of a wire with r as radius value if chamfer is true, a chamfer is made instead and r is the - size of the chamfer''' + size of the chamfer""" edges = aWire.Edges edges = Part.__sortEdges__(edges) @@ -2259,8 +2319,9 @@ def filletWire(aWire,r,chamfer=False): filEdges[0] = result[2] return Part.Wire(filEdges) + def getCircleFromSpline(edge): - "returns a circle-based edge from a bspline-based edge" + """Return a circle-based edge from a bspline-based edge.""" if geomType(edge) != "BSplineCurve": return None if len(edge.Vertexes) != 1: @@ -2288,7 +2349,8 @@ def getCircleFromSpline(edge): #print(circle.Curve) return circle -def curvetowire(obj,steps): + +def curvetowire(obj, steps): points = obj.copy().discretize(steps) p0 = points[0] edgelist = [] @@ -2298,8 +2360,9 @@ def curvetowire(obj,steps): p0 = p return edgelist -def cleanProjection(shape,tessellate=True,seglength=.05): - "returns a valid compound of edges, by recreating them" + +def cleanProjection(shape, tessellate=True, seglength=0.05): + """Return a valid compound of edges, by recreating them.""" # this is because the projection algorithm somehow creates wrong shapes. # they display fine, but on loading the file the shape is invalid # Now with tanderson's fix to ProjectionAlgos, that isn't the case, but this @@ -2342,7 +2405,8 @@ def cleanProjection(shape,tessellate=True,seglength=.05): print("Debug: error cleaning edge ",e) return Part.makeCompound(newedges) -def curvetosegment(curve,seglen): + +def curvetosegment(curve, seglen): points = curve.discretize(seglen) p0 = points[0] edgelist = [] @@ -2352,9 +2416,10 @@ def curvetosegment(curve,seglen): p0 = p return edgelist -def tessellateProjection(shape,seglen): - ''' Returns projection with BSplines and Ellipses broken into line segments. - Useful for exporting projected views to *dxf files.''' + +def tessellateProjection(shape, seglen): + """Returns projection with BSplines and Ellipses broken into line segments. + Useful for exporting projected views to *dxf files.""" oldedges = shape.Edges newedges = [] for e in oldedges: @@ -2374,8 +2439,7 @@ def tessellateProjection(shape,seglen): return Part.makeCompound(newedges) -def rebaseWire(wire,vidx): - +def rebaseWire(wire, vidx): """rebaseWire(wire,vidx): returns a new wire which is a copy of the current wire, but where the first vertex is the vertex indicated by the given index vidx, starting from 1. 0 will return an exact copy of the wire.""" @@ -2415,9 +2479,9 @@ def removeSplitter(shape): # circle functions ********************************************************* -def getBoundaryAngles(angle,alist): - '''returns the 2 closest angles from the list that - encompass the given angle''' +def getBoundaryAngles(angle, alist): + """returns the 2 closest angles from the list that + encompass the given angle""" negs = True while negs: negs = False @@ -2458,7 +2522,7 @@ def getBoundaryAngles(angle,alist): def circleFrom2tan1pt(tan1, tan2, point): - "circleFrom2tan1pt(edge, edge, Vector)" + """circleFrom2tan1pt(edge, edge, Vector)""" if (geomType(tan1) == "Line") and (geomType(tan2) == "Line") and isinstance(point, FreeCAD.Vector): return circlefrom2Lines1Point(tan1, tan2, point) elif (geomType(tan1) == "Circle") and (geomType(tan2) == "Line") and isinstance(point, FreeCAD.Vector): @@ -2468,8 +2532,9 @@ def circleFrom2tan1pt(tan1, tan2, point): elif (geomType(tan2) == "Circle") and (geomType(tan1) == "Circle") and isinstance(point, FreeCAD.Vector): return circlefrom2Circles1Point(tan2, tan1, point) + def circleFrom2tan1rad(tan1, tan2, rad): - "circleFrom2tan1rad(edge, edge, float)" + """circleFrom2tan1rad(edge, edge, float)""" if (geomType(tan1) == "Line") and (geomType(tan2) == "Line"): return circleFrom2LinesRadius(tan1, tan2, rad) elif (geomType(tan1) == "Circle") and (geomType(tan2) == "Line"): @@ -2479,18 +2544,21 @@ def circleFrom2tan1rad(tan1, tan2, rad): elif (geomType(tan1) == "Circle") and (geomType(tan2) == "Circle"): return circleFrom2CirclesRadius(tan1, tan2, rad) + def circleFrom1tan2pt(tan1, p1, p2): if (geomType(tan1) == "Line") and isinstance(p1, FreeCAD.Vector) and isinstance(p2, FreeCAD.Vector): return circlefrom1Line2Points(tan1, p1, p2) if (geomType(tan1) == "Line") and isinstance(p1, FreeCAD.Vector) and isinstance(p2, FreeCAD.Vector): return circlefrom1Circle2Points(tan1, p1, p2) + def circleFrom1tan1pt1rad(tan1, p1, rad): if (geomType(tan1) == "Line") and isinstance(p1, FreeCAD.Vector): return circleFromPointLineRadius(p1, tan1, rad) if (geomType(tan1) == "Circle") and isinstance(p1, FreeCAD.Vector): return circleFromPointCircleRadius(p1, tan1, rad) + def circleFrom3tan(tan1, tan2, tan3): tan1IsLine = (geomType(tan1) == "Line") tan2IsLine = (geomType(tan2) == "Line") @@ -2515,15 +2583,17 @@ def circleFrom3tan(tan1, tan2, tan3): elif (tan1IsCircle and tan2IsCircle and tan3IsLine): return circleFrom2Circle1Lines(tan1, tan2, tan3) + def circlefrom2Lines1Point(edge1, edge2, point): - "circlefrom2Lines1Point(edge, edge, Vector)" + """circlefrom2Lines1Point(edge, edge, Vector)""" bis = angleBisection(edge1, edge2) if not bis: return None mirrPoint = mirror(point, bis) return circlefrom1Line2Points(edge1, point, mirrPoint) + def circlefrom1Line2Points(edge, p1, p2): - "circlefrom1Line2Points(edge, Vector, Vector)" + """circlefrom1Line2Points(edge, Vector, Vector)""" p1_p2 = edg(p1, p2) s = findIntersection(edge, p1_p2, True, True) if not s: return None @@ -2553,8 +2623,9 @@ def circlefrom1Line2Points(edge, p1, p2): if circles: return circles else: return None -def circleFrom2LinesRadius (edge1, edge2, radius): - "circleFrom2LinesRadius(edge,edge,radius)" + +def circleFrom2LinesRadius(edge1, edge2, radius): + """circleFrom2LinesRadius(edge,edge,radius)""" int = findIntersection(edge1, edge2, True, True) if not int: return None int = int[0] @@ -2575,8 +2646,9 @@ def circleFrom2LinesRadius (edge1, edge2, radius): circles.append(Part.Circle(cen, NORM, radius)) return circles -def circleFrom3LineTangents (edge1, edge2, edge3): - "circleFrom3LineTangents(edge,edge,edge)" + +def circleFrom3LineTangents(edge1, edge2, edge3): + """circleFrom3LineTangents(edge,edge,edge)""" def rot(ed): return Part.LineSegment(v1(ed),v1(ed).add(DraftVecUtils.rotate(vec(ed),math.pi/2))).toShape() bis12 = angleBisection(edge1,edge2) @@ -2621,8 +2693,8 @@ def circleFrom3LineTangents (edge1, edge2, edge3): else: return None -def circleFromPointLineRadius (point, edge, radius): - "circleFromPointLineRadius (point, edge, radius)" +def circleFromPointLineRadius(point, edge, radius): + """circleFromPointLineRadius (point, edge, radius)""" dist = findDistance(point, edge, False) center1 = None center2 = None @@ -2667,8 +2739,9 @@ def circleFromPointLineRadius (point, edge, radius): else: return None + def circleFrom2PointsRadius(p1, p2, radius): - "circleFrom2PointsRadiust(Vector, Vector, radius)" + """circleFrom2PointsRadiust(Vector, Vector, radius)""" if DraftVecUtils.equals(p1, p2): return None p1_p2 = Part.LineSegment(p1, p2).toShape() @@ -2690,9 +2763,8 @@ def circleFrom2PointsRadius(p1, p2, radius): else: return None -def arcFrom2Pts(firstPt,lastPt,center,axis=None): - - '''Builds an arc with center and 2 points, can be oriented with axis''' +def arcFrom2Pts(firstPt, lastPt, center, axis=None): + """Build an arc with center and 2 points, can be oriented with axis.""" radius1 = firstPt.sub(center).Length radius2 = lastPt.sub(center).Length @@ -2721,9 +2793,7 @@ def arcFrom2Pts(firstPt,lastPt,center,axis=None): def outerSoddyCircle(circle1, circle2, circle3): - ''' - Computes the outer soddy circle for three tightly packed circles. - ''' + """Compute the outer soddy circle for three tightly packed circles.""" if (geomType(circle1) == "Circle") and (geomType(circle2) == "Circle") \ and (geomType(circle3) == "Circle"): # Original Java code Copyright (rc) 2008 Werner Randelshofer @@ -2773,10 +2843,9 @@ def outerSoddyCircle(circle1, circle2, circle3): # FreeCAD.Console.PrintMessage("debug: outerSoddyCircle bad parameters!\n") return None + def innerSoddyCircle(circle1, circle2, circle3): - ''' - Computes the inner soddy circle for three tightly packed circles. - ''' + """Compute the inner soddy circle for three tightly packed circles.""" if (geomType(circle1) == "Circle") and (geomType(circle2) == "Circle") \ and (geomType(circle3) == "Circle"): # Original Java code Copyright (rc) 2008 Werner Randelshofer @@ -2825,12 +2894,13 @@ def innerSoddyCircle(circle1, circle2, circle3): # FreeCAD.Console.PrintMessage("debug: innerSoddyCircle bad parameters!\n") return None + def circleFrom3CircleTangents(circle1, circle2, circle3): - ''' + """ http://en.wikipedia.org/wiki/Problem_of_Apollonius#Inversive_methods http://mathworld.wolfram.com/ApolloniusCircle.html http://mathworld.wolfram.com/ApolloniusProblem.html - ''' + """ if (geomType(circle1) == "Circle") and (geomType(circle2) == "Circle") \ and (geomType(circle3) == "Circle"): @@ -2879,9 +2949,9 @@ def circleFrom3CircleTangents(circle1, circle2, circle3): return None -def linearFromPoints (p1, p2): - ''' - Calculate linear equation from points. +def linearFromPoints(p1, p2): + """Calculate linear equation from points. + Calculate the slope and offset parameters of the linear equation of a line defined by two points. Linear equation: @@ -2890,7 +2960,7 @@ def linearFromPoints (p1, p2): m ... Slope b ... Offset (point where the line intersects the y axis) dx/dy ... Delta x and y. Using both as a vector results in a non-offset direction vector. - ''' + """ if isinstance(p1, Vector) and isinstance(p2, Vector): line = {} line['dx'] = (p2.x - p1.x) @@ -2902,11 +2972,11 @@ def linearFromPoints (p1, p2): return None -def determinant (mat,n): - ''' +def determinant(mat, n): + """ determinant(matrix,int) - Determinat function. Returns the determinant of a n-matrix. It recursively expands the minors. - ''' + """ matTemp = [[0.0,0.0,0.0],[0.0,0.0,0.0],[0.0,0.0,0.0]] if (n > 1): if n == 2: @@ -2929,13 +2999,11 @@ def determinant (mat,n): def findHomotheticCenterOfCircles(circle1, circle2): - ''' - findHomotheticCenterOfCircles(circle1, circle2) - Calculates the homothetic center(s) of two circles. + """Calculate the homothetic center(s) of two circles. http://en.wikipedia.org/wiki/Homothetic_center http://mathworld.wolfram.com/HomotheticCenter.html - ''' + """ if (geomType(circle1) == "Circle") and (geomType(circle2) == "Circle"): if DraftVecUtils.equals(circle1.Curve.Center, circle2.Curve.Center): @@ -2972,14 +3040,13 @@ def findHomotheticCenterOfCircles(circle1, circle2): return None else: - print("debug: findHomotheticCenterOfCircles bad parameters!\n") FreeCAD.Console.PrintMessage("debug: findHomotheticCenterOfCirclescleFrom3tan bad parameters!\n") return None def findRadicalAxis(circle1, circle2): - ''' - Calculates the radical axis of two circles. + """Calculate the radical axis of two circles. + On the radical axis (also called power line) of two circles any tangents drawn from a point on the axis to both circles have the same length. @@ -2987,8 +3054,7 @@ def findRadicalAxis(circle1, circle2): http://mathworld.wolfram.com/RadicalLine.html @sa findRadicalCenter - ''' - + """ if (geomType(circle1) == "Circle") and (geomType(circle2) == "Circle"): if DraftVecUtils.equals(circle1.Curve.Center, circle2.Curve.Center): return None @@ -3023,14 +3089,12 @@ def findRadicalAxis(circle1, circle2): else: return None else: - print("debug: findRadicalAxis bad parameters!\n") FreeCAD.Console.PrintMessage("debug: findRadicalAxis bad parameters!\n") return None - def findRadicalCenter(circle1, circle2, circle3): - ''' + """ findRadicalCenter(circle1, circle2, circle3): Calculates the radical center (also called the power center) of three circles. It is the intersection point of the three radical axes of the pairs of circles. @@ -3039,8 +3103,7 @@ def findRadicalCenter(circle1, circle2, circle3): http://mathworld.wolfram.com/RadicalCenter.html @sa findRadicalAxis - ''' - + """ if (geomType(circle1) == "Circle") and (geomType(circle2) == "Circle"): radicalAxis12 = findRadicalAxis(circle1, circle2) radicalAxis23 = findRadicalAxis(circle1, circle2) @@ -3057,22 +3120,21 @@ def findRadicalCenter(circle1, circle2, circle3): # No radical center could be calculated. return None else: - print("debug: findRadicalCenter bad parameters!\n") FreeCAD.Console.PrintMessage("debug: findRadicalCenter bad parameters!\n") return None + def pointInversion(circle, point): - ''' + """Circle inversion of a point. + pointInversion(Circle, Vector) - Circle inversion of a point. Will calculate the inversed point an return it. If the given point is equal to the center of the circle "None" will be returned. See also: http://en.wikipedia.org/wiki/Inversive_geometry - ''' - + """ if (geomType(circle) == "Circle") and isinstance(point, FreeCAD.Vector): cen = circle.Curve.Center rad = circle.Curve.Radius @@ -3093,19 +3155,20 @@ def pointInversion(circle, point): return invPoint else: - print("debug: pointInversion bad parameters!\n") FreeCAD.Console.PrintMessage("debug: pointInversion bad parameters!\n") return None + def polarInversion(circle, edge): - ''' + """Return the inversion pole of a line. + polarInversion(circle, edge): - Returns the inversion pole of a line. + edge ... The polar. i.e. The nearest point on the line is inversed. http://mathworld.wolfram.com/InversionPole.html - ''' + """ if (geomType(circle) == "Circle") and (geomType(edge) == "Line"): nearest = circle.Curve.Center.add(findDistance(circle.Curve.Center, edge, False)) @@ -3113,18 +3176,17 @@ def polarInversion(circle, edge): inversionPole = pointInversion(circle, nearest) if inversionPole: return inversionPole - else: - print("debug: circleInversionPole bad parameters!\n") FreeCAD.Console.PrintMessage("debug: circleInversionPole bad parameters!\n") return None + def circleInversion(circle, circle2): - ''' + """ pointInversion(Circle, Circle) Circle inversion of a circle. - ''' + """ if (geomType(circle) == "Circle") and (geomType(circle2) == "Circle"): cen1 = circle.Curve.Center rad1 = circle.Curve.Radius @@ -3140,7 +3202,6 @@ def circleInversion(circle, circle2): return Part.Circle(invCen2, norm, DraftVecUtils.dist(invCen2, invPointOnCircle2)) else: - print("debug: circleInversion bad parameters!\n") FreeCAD.Console.PrintMessage("debug: circleInversion bad parameters!\n") return None diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index f5e91a7e35..4c12910ac3 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -1619,7 +1619,7 @@ class DraftToolBar: """this function sends the entered text to the active draft command if enter has been pressed twice. Otherwise it blanks the line. """ - self.sourceCmd.text = self.textValue.toPlainText().split() + self.sourceCmd.text = self.textValue.toPlainText().splitlines() self.sourceCmd.createObject() def displayPoint(self, point=None, last=None, plane=None, mask=None): @@ -2169,113 +2169,6 @@ class FacebinderTaskPanel: self.addButton.setText(QtGui.QApplication.translate("draft", "Add", None)) self.title.setText(QtGui.QApplication.translate("draft", "Facebinder elements", None)) - -class ScaleTaskPanel: - """A Task Panel for the Scale tool""" - - def __init__(self): - self.sourceCmd = None - self.form = QtGui.QWidget() - layout = QtGui.QGridLayout(self.form) - self.xLabel = QtGui.QLabel() - layout.addWidget(self.xLabel,0,0,1,1) - self.xValue = QtGui.QDoubleSpinBox() - self.xValue.setRange(.0000001,1000000.0) - self.xValue.setDecimals(Draft.getParam("precision")) - self.xValue.setValue(1) - layout.addWidget(self.xValue,0,1,1,1) - self.yLabel = QtGui.QLabel() - layout.addWidget(self.yLabel,1,0,1,1) - self.yValue = QtGui.QDoubleSpinBox() - self.yValue.setRange(.0000001,1000000.0) - self.yValue.setDecimals(Draft.getParam("precision")) - self.yValue.setValue(1) - layout.addWidget(self.yValue,1,1,1,1) - self.zLabel = QtGui.QLabel() - layout.addWidget(self.zLabel,2,0,1,1) - self.zValue = QtGui.QDoubleSpinBox() - self.zValue.setRange(.0000001,1000000.0) - self.zValue.setDecimals(Draft.getParam("precision")) - self.zValue.setValue(1) - layout.addWidget(self.zValue,2,1,1,1) - self.lock = QtGui.QCheckBox() - self.lock.setChecked(FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleUniform",False)) - layout.addWidget(self.lock,3,0,1,2) - self.relative = QtGui.QCheckBox() - self.relative.setChecked(FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleRelative",False)) - layout.addWidget(self.relative,4,0,1,2) - self.isCopy = QtGui.QCheckBox() - self.isCopy.setChecked(FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleCopy",False)) - layout.addWidget(self.isCopy,5,0,1,2) - self.isSubelementMode = QtGui.QCheckBox() - layout.addWidget(self.isSubelementMode,6,0,1,2) - self.isClone = QtGui.QCheckBox() - layout.addWidget(self.isClone,7,0,1,2) - self.isClone.setChecked(FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleClone",False)) - self.pickrefButton = QtGui.QPushButton() - layout.addWidget(self.pickrefButton,8,0,1,2) - QtCore.QObject.connect(self.xValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) - QtCore.QObject.connect(self.yValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) - QtCore.QObject.connect(self.zValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) - QtCore.QObject.connect(self.pickrefButton,QtCore.SIGNAL("clicked()"),self.pickRef) - QtCore.QObject.connect(self.lock,QtCore.SIGNAL("toggled(bool)"),self.setLock) - QtCore.QObject.connect(self.relative,QtCore.SIGNAL("toggled(bool)"),self.setRelative) - QtCore.QObject.connect(self.isCopy,QtCore.SIGNAL("toggled(bool)"),self.setCopy) - QtCore.QObject.connect(self.isClone,QtCore.SIGNAL("toggled(bool)"),self.setClone) - self.retranslateUi() - - def setLock(self,state): - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleUniform",state) - - def setRelative(self,state): - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleRelative",state) - - def setCopy(self,state): - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleCopy",state) - if state and self.isClone.isChecked(): - self.isClone.setChecked(False) - - def setClone(self,state): - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleClone",state) - if state and self.isCopy.isChecked(): - self.isCopy.setChecked(False) - - def setValue(self,val=None): - if self.lock.isChecked(): - self.xValue.setValue(val) - self.yValue.setValue(val) - self.zValue.setValue(val) - if self.sourceCmd: - self.sourceCmd.scaleGhost(self.xValue.value(),self.yValue.value(),self.zValue.value(),self.relative.isChecked()) - - def retranslateUi(self,widget=None): - self.form.setWindowTitle(QtGui.QApplication.translate("Draft", "Scale", None)) - self.xLabel.setText(QtGui.QApplication.translate("Draft", "X factor", None)) - self.yLabel.setText(QtGui.QApplication.translate("Draft", "Y factor", None)) - self.zLabel.setText(QtGui.QApplication.translate("Draft", "Z factor", None)) - self.lock.setText(QtGui.QApplication.translate("Draft", "Uniform scaling", None)) - self.relative.setText(QtGui.QApplication.translate("Draft", "Working plane orientation", None)) - self.isCopy.setText(QtGui.QApplication.translate("draft", "Copy")) - self.isSubelementMode.setText(QtGui.QApplication.translate("draft", "Modify subelements")) - self.pickrefButton.setText(QtGui.QApplication.translate("Draft", "Pick from/to points", None)) - self.isClone.setText(QtGui.QApplication.translate("Draft", "Create a clone", None)) - - def pickRef(self): - if self.sourceCmd: - self.sourceCmd.pickRef() - - def accept(self): - if self.sourceCmd: - self.sourceCmd.scale() - FreeCADGui.ActiveDocument.resetEdit() - return True - - def reject(self): - if self.sourceCmd: - self.sourceCmd.finish() - FreeCADGui.ActiveDocument.resetEdit() - return True - #def translateWidget(w, context=None, disAmb=None): # '''translator for items where retranslateUi() is unavailable. # translates widget w and children.''' @@ -2313,142 +2206,6 @@ class ScaleTaskPanel: ## msg = "TranslateWidget: Can not translate widget: {0} type: {1}\n".format(w.objectName(),w.metaObject().className()) ## FreeCAD.Console.PrintMessage(msg) -class ShapeStringTaskPanel: - '''A TaskPanel for ShapeString''' - oldValueBuffer = False - - def __init__(self): - self.form = QtGui.QWidget() - self.form.setObjectName("ShapeStringTaskPanel") - self.form.setWindowTitle(translate("draft","ShapeString")) - layout = QtGui.QVBoxLayout(self.form) - uiFile = QtCore.QFile(u":/ui/TaskShapeString.ui") #this has to change if ui not in Resource file - loader = FreeCADGui.UiLoader() - self.task = loader.load(uiFile) - layout.addWidget(self.task) - - qStart = FreeCAD.Units.Quantity(0.0, FreeCAD.Units.Length) - self.task.sbX.setProperty('rawValue',qStart.Value) - self.task.sbX.setProperty('unit',qStart.getUserPreferred()[2]) - self.task.sbY.setProperty('rawValue',qStart.Value) - self.task.sbY.setProperty('unit',qStart.getUserPreferred()[2]) - self.task.sbZ.setProperty('rawValue',qStart.Value) - self.task.sbZ.setProperty('unit',qStart.getUserPreferred()[2]) - self.task.sbHeight.setProperty('rawValue',10.0) - self.task.sbHeight.setProperty('unit',qStart.getUserPreferred()[2]) - - self.stringText = translate("draft","Default") - self.task.leString.setText(self.stringText) - self.platWinDialog("Overwrite") - self.task.fcFontFile.setFileName(Draft.getParam("FontFile","")) - self.fileSpec = Draft.getParam("FontFile","") - self.point = FreeCAD.Vector(0.0,0.0,0.0) - self.pointPicked = False - - QtCore.QObject.connect(self.task.fcFontFile,QtCore.SIGNAL("fileNameSelected(const QString&)"),self.fileSelect) - QtCore.QObject.connect(self.task.pbReset,QtCore.SIGNAL("clicked()"),self.resetPoint) - self.point = None - self.view = Draft.get3DView() - self.call = self.view.addEventCallback("SoEvent",self.action) - FreeCAD.Console.PrintMessage(translate("draft", "Pick ShapeString location point:")+"\n") - - - def fileSelect(self, fn): - self.fileSpec = fn - - def resetPoint(self): - self.pointPicked = False - origin = FreeCAD.Vector(0.0,0.0,0.0) - self.setPoint(origin) - - def action(self,arg): - """scene event handler""" - import DraftTools - if arg["Type"] == "SoKeyboardEvent": - if arg["Key"] == "ESCAPE": - self.reject() - elif arg["Type"] == "SoLocation2Event": #mouse movement detection - self.point,ctrlPoint,info = DraftTools.getPoint(self.sourceCmd,arg,noTracker=True) - if not self.pointPicked: - self.setPoint(self.point) - elif arg["Type"] == "SoMouseButtonEvent": - if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): - self.setPoint(self.point) - self.pointPicked = True - - def setPoint(self, point): - self.task.sbX.setProperty('rawValue',point.x) - self.task.sbY.setProperty('rawValue',point.y) - self.task.sbZ.setProperty('rawValue',point.z) - - def createObject(self): - """creates object in the current doc""" - dquote = '"' - if sys.version_info.major < 3: # Python3: no more unicode - String = 'u' + dquote + str(self.task.leString.text().encode('unicode_escape')) + dquote - else: - String = dquote + self.task.leString.text() + dquote - FFile = dquote + str(self.fileSpec) + dquote - - Size = str(FreeCAD.Units.Quantity(self.task.sbHeight.text()).Value) - Tracking = str(0.0) - x = FreeCAD.Units.Quantity(self.task.sbX.text()).Value - y = FreeCAD.Units.Quantity(self.task.sbY.text()).Value - z = FreeCAD.Units.Quantity(self.task.sbZ.text()).Value - ssBase = FreeCAD.Vector(x,y,z) - # this try block is almost identical to the one in DraftTools - try: - qr,sup,points,fil = self.sourceCmd.getStrings() - FreeCADGui.addModule("Draft") - self.sourceCmd.commit(translate("draft","Create ShapeString"), - ['ss=Draft.makeShapeString(String='+String+',FontFile='+FFile+',Size='+Size+',Tracking='+Tracking+')', - 'plm=FreeCAD.Placement()', - 'plm.Base='+DraftVecUtils.toString(ssBase), - 'plm.Rotation.Q='+qr, - 'ss.Placement=plm', - 'ss.Support='+sup, - 'Draft.autogroup(ss)']) - except Exception as e: - FreeCAD.Console.PrintError("Draft_ShapeString: error delaying commit\n") - - def platWinDialog(self, Flag): - ParamGroup = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Dialog") - if Flag == "Overwrite": - GroupContent = ParamGroup.GetContents() - Found = False - if GroupContent: - for ParamSet in GroupContent: - if ParamSet[1] == "DontUseNativeFontDialog": - Found = True - break - - if Found == False: - ParamGroup.SetBool("DontUseNativeFontDialog", True) #initialize nonexisting one - - param = ParamGroup.GetBool("DontUseNativeFontDialog") - ShapeStringTaskPanel.oldValueBuffer = ParamGroup.GetBool("DontUseNativeDialog") - ParamGroup.SetBool("DontUseNativeDialog", param) - - elif Flag == "Restore": - ParamGroup.SetBool("DontUseNativeDialog", ShapeStringTaskPanel.oldValueBuffer) - - def accept(self): - self.createObject(); - if self.call: self.view.removeEventCallback("SoEvent",self.call) - FreeCADGui.ActiveDocument.resetEdit() - FreeCADGui.Snapper.off() - self.sourceCmd.creator.finish(self.sourceCmd) - self.platWinDialog("Restore") - return True - - def reject(self): - if self.call: self.view.removeEventCallback("SoEvent",self.call) - FreeCADGui.ActiveDocument.resetEdit() - FreeCADGui.Snapper.off() - self.sourceCmd.creator.finish(self.sourceCmd) - self.platWinDialog("Restore") - return True - if not hasattr(FreeCADGui,"draftToolBar"): FreeCADGui.draftToolBar = DraftToolBar() #----End of Python Features Definitions----# diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 7629b91c65..569c8d0704 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -1,50 +1,69 @@ # -*- coding: utf8 -*- -#*************************************************************************** -#* Copyright (c) 2009, 2010 Yorik van Havre * -#* Copyright (c) 2009, 2010 Ken Cline * -#* * -#* This program is free software; you can redistribute it and/or modify * -#* it under the terms of the GNU Lesser General Public License (LGPL) * -#* as published by the Free Software Foundation; either version 2 of * -#* the License, or (at your option) any later version. * -#* for detail see the LICENCE text file. * -#* * -#* This program 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 program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** - -__title__="FreeCAD Draft Workbench GUI Tools" -__author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, Dmitry Chigrin" -__url__ = "https://www.freecadweb.org" +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provide GUI commands of the Draft Workbench. +This module loads all graphical commands of the Draft Workbench, +that is, those actions that can be called from menus and buttons. +This module must be imported only when the graphical user interface +is available, for example, during the workbench definition in `IntiGui.py`. +""" ## @package DraftTools # \ingroup DRAFT -# \brief GUI Commands of the Draft workbench +# \brief Provide GUI commands of the Draft workbench. # -# This module contains all the FreeCAD commands -# of the Draft module +# This module contains all the graphical commands of the Draft workbench, +# that is, those actions that can be called from menus and buttons. -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # Generic stuff -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +import math +import sys +from PySide import QtCore, QtGui +from pivy import coin -import sys, os, FreeCAD, FreeCADGui, WorkingPlane, math, re, Draft, Draft_rc, DraftVecUtils +import FreeCAD +import FreeCADGui from FreeCAD import Vector -from PySide import QtCore,QtGui -from draftutils.todo import todo + +import Draft +import Draft_rc +import DraftGui # Initializes the DraftToolBar class +import DraftVecUtils +import WorkingPlane +from draftutils.todo import ToDo from draftutils.translate import translate import draftguitools.gui_snapper as gui_snapper -import DraftGui import draftguitools.gui_trackers as trackers -from pivy import coin + +# The module is used to prevent complaints from code checkers (flake8) +True if Draft_rc.__name__ else False +True if DraftGui.__name__ else False + +__title__ = "FreeCAD Draft Workbench GUI Tools" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin") +__url__ = "https://www.freecadweb.org" if not hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper = gui_snapper.Snapper() @@ -52,18 +71,19 @@ if not hasattr(FreeCADGui, "Snapper"): if not hasattr(FreeCAD, "DraftWorkingPlane"): FreeCAD.DraftWorkingPlane = WorkingPlane.plane() -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # Commands that have been migrated to their own modules -#--------------------------------------------------------------------------- - -import DraftEdit +# --------------------------------------------------------------------------- +import draftguitools.gui_edit +import draftguitools.gui_selectplane +import draftguitools.gui_planeproxy # import DraftFillet -import DraftSelectPlane +import drafttaskpanels.task_shapestring as task_shapestring +import drafttaskpanels.task_scale as task_scale -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # Preflight stuff -#--------------------------------------------------------------------------- - +# --------------------------------------------------------------------------- # update the translation engine FreeCADGui.updateLocale() @@ -84,10 +104,9 @@ MODCONSTRAIN = MODS[Draft.getParam("modconstrain",0)] MODSNAP = MODS[Draft.getParam("modsnap",1)] MODALT = MODS[Draft.getParam("modalt",2)] -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # General functions -#--------------------------------------------------------------------------- - +# --------------------------------------------------------------------------- def formatUnit(exp,unit="mm"): '''returns a formatting string to set a number to the correct unit''' return FreeCAD.Units.Quantity(exp,FreeCAD.Units.Length).UserString @@ -219,12 +238,9 @@ def setMod(args,mod,state): args["AltDown"] = state - - -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # Base Class -#--------------------------------------------------------------------------- - +# --------------------------------------------------------------------------- class DraftTool: """The base class of all Draft Tools""" @@ -294,7 +310,7 @@ class DraftTool: pass self.call = None if self.commitList: - todo.delayCommit(self.commitList) + ToDo.delayCommit(self.commitList) self.commitList = [] def commit(self,name,func): @@ -332,10 +348,9 @@ class DraftTool: return qr,sup,points,fil -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # Geometry constructors -#--------------------------------------------------------------------------- - +# --------------------------------------------------------------------------- def redraw3DView(): """redraw3DView(): forces a redraw of 3d view.""" try: @@ -461,7 +476,7 @@ class Line(Creator): # object already deleted, for some reason pass else: - todo.delay(self.doc.removeObject,old) + ToDo.delay(self.doc.removeObject, old) self.obj = None def undolast(self): @@ -573,7 +588,7 @@ class Wire(Line): pts = pts.replace("Vector","FreeCAD.Vector") rems = ["FreeCAD.ActiveDocument.removeObject(\""+o.Name+"\")" for o in FreeCADGui.Selection.getSelection()] FreeCADGui.addModule("Draft") - todo.delayCommit([(translate("draft","Convert to Wire"), + ToDo.delayCommit([(translate("draft", "Convert to Wire"), ['wire = Draft.makeWire(['+pts+'])']+rems+['Draft.autogroup(wire)', 'FreeCAD.ActiveDocument.recompute()'])]) return @@ -663,7 +678,7 @@ class BSpline(Line): if self.obj: # remove temporary object, if any old = self.obj.Name - todo.delay(self.doc.removeObject,old) + ToDo.delay(self.doc.removeObject, old) if (len(self.node) > 1): try: # building command string @@ -783,7 +798,7 @@ class BezCurve(Line): if self.obj: # remove temporary object, if any old = self.obj.Name - todo.delay(self.doc.removeObject,old) + ToDo.delay(self.doc.removeObject, old) if (len(self.node) > 1): try: # building command string @@ -942,7 +957,7 @@ class CubicBezCurve(Line): if self.obj: # remove temporary object, if any old = self.obj.Name - todo.delay(self.doc.removeObject,old) + ToDo.delay(self.doc.removeObject, old) if closed == False : cleannd=(len(self.node)-1) % self.degree if cleannd == 0 : self.node = self.node[0:-3] @@ -2223,14 +2238,22 @@ class Dimension(Creator): if not self.cont: self.finish() + class ShapeString(Creator): - """This class creates a shapestring feature.""" + """The Draft_ShapeString FreeCAD command definition.""" def GetResources(self): - return {'Pixmap' : 'Draft_ShapeString', - 'Accel' : "S, S", - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ShapeString", "Shape from text..."), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_ShapeString", "Creates text string in shapes.")} + """Set icon, menu and tooltip.""" + _menu = "Shape from text" + _tooltip = ("Creates a shape from a text string by choosing " + "a specific font and a placement.\n" + "The closed shapes can be used for extrusions " + "and boolean operations.") + d = {'Pixmap': 'Draft_ShapeString', + 'Accel': "S, S", + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ShapeString", _menu), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_ShapeString", _tooltip)} + return d def Activated(self): name = translate("draft","ShapeString") @@ -2244,9 +2267,9 @@ class ShapeString(Creator): del self.task except AttributeError: pass - self.task = DraftGui.ShapeStringTaskPanel() + self.task = task_shapestring.ShapeStringTaskPanel() self.task.sourceCmd = self - todo.delay(FreeCADGui.Control.showDialog,self.task) + ToDo.delay(FreeCADGui.Control.showDialog, self.task) else: self.dialog = None self.text = '' @@ -2406,7 +2429,7 @@ class Move(Modifier): ghost.finalize() if cont and self.ui: if self.ui.continueMode: - todo.delayAfter(self.Activated,[]) + ToDo.delayAfter(self.Activated, []) Modifier.finish(self) def action(self,arg): @@ -2748,7 +2771,7 @@ class Rotate(Modifier): ghost.finalize() if cont and self.ui: if self.ui.continueMode: - todo.delayAfter(self.Activated,[]) + ToDo.delayAfter(self.Activated, []) Modifier.finish(self) if self.doc: self.doc.recompute() @@ -4129,11 +4152,11 @@ class Scale(Modifier): self.ui.offUi() if self.call: self.view.removeEventCallback("SoEvent",self.call) - self.task = DraftGui.ScaleTaskPanel() + self.task = task_scale.ScaleTaskPanel() self.task.sourceCmd = self - todo.delay(FreeCADGui.Control.showDialog,self.task) - todo.delay(self.task.xValue.selectAll,None) - todo.delay(self.task.xValue.setFocus,None) + ToDo.delay(FreeCADGui.Control.showDialog, self.task) + ToDo.delay(self.task.xValue.selectAll, None) + ToDo.delay(self.task.xValue.setFocus, None) for ghost in self.ghosts: ghost.on() elif len(self.node) == 2: @@ -4786,7 +4809,7 @@ class Point(Creator): ['point = Draft.makePoint('+str(self.stack[0][0])+','+str(self.stack[0][1])+','+str(self.stack[0][2])+')', 'Draft.autogroup(point)', 'FreeCAD.ActiveDocument.recompute()'])) - todo.delayCommit(commitlist) + ToDo.delayCommit(commitlist) FreeCADGui.Snapper.off() self.finish() @@ -4854,7 +4877,7 @@ class Draft_Clone(Modifier): def finish(self,close=False): Modifier.finish(self,close=False) if self.moveAfterCloning: - todo.delay(FreeCADGui.runCommand,"Draft_Move") + ToDo.delay(FreeCADGui.runCommand, "Draft_Move") class ToggleGrid(): diff --git a/src/Mod/Draft/DraftVecUtils.py b/src/Mod/Draft/DraftVecUtils.py index d829e36a2a..87fbfbf7d5 100644 --- a/src/Mod/Draft/DraftVecUtils.py +++ b/src/Mod/Draft/DraftVecUtils.py @@ -1,19 +1,3 @@ -## \defgroup DRAFTVECUTILS DraftVecUtils -# \ingroup UTILITIES -# \brief Vector math utilities used in Draft workbench -# -# Vector math utilities used primarily in the Draft workbench -# but which can also be used in other workbenches and in macros. -"""\defgroup DRAFTVECUTILS DraftVecUtils -\ingroup UTILITIES -\brief Vector math utilities used in Draft workbench - -Vector math utilities used primarily in the Draft workbench -but which can also be used in other workbenches and in macros. -""" -# Check code with -# flake8 --ignore=E226,E266,E401,W503 - # *************************************************************************** # * Copyright (c) 2009, 2010 Yorik van Havre * # * Copyright (c) 2009, 2010 Ken Cline * @@ -35,19 +19,32 @@ but which can also be used in other workbenches and in macros. # * USA * # * * # *************************************************************************** +"""Provide vector math utilities used in the Draft workbench. + +Vector math utilities used primarily in the Draft workbench +but which can also be used in other workbenches and in macros. +""" +## \defgroup DRAFTVECUTILS DraftVecUtils +# \ingroup UTILITIES +# \brief Vector math utilities used in Draft workbench +# +# Vector math utilities used primarily in the Draft workbench +# but which can also be used in other workbenches and in macros. + +# Check code with +# flake8 --ignore=E226,E266,E401,W503 + +import math +import sys + +import FreeCAD +from FreeCAD import Vector +import draftutils.messages as messages __title__ = "FreeCAD Draft Workbench - Vector library" __author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline" __url__ = "https://www.freecadweb.org" -## \addtogroup DRAFTVECUTILS -# @{ - -import sys -import math, FreeCAD -from FreeCAD import Vector, Matrix -from FreeCAD import Console as FCC - # Python 2 has two integer types, int and long. # In Python 3 there is no 'long' anymore, so make it 'int'. try: @@ -57,6 +54,9 @@ except NameError: params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") +## \addtogroup DRAFTVECUTILS +# @{ + def precision(): """Get the number of decimal numbers used for precision. @@ -97,16 +97,15 @@ def typecheck(args_and_types, name="?"): Defaults to `'?'`. The name of the check. Raises - ------- + ------ TypeError If the first element in the tuple is not an instance of the second element. """ for v, t in args_and_types: if not isinstance(v, t): - _msg = ("typecheck[" + str(name) + "]: " - + str(v) + " is not " + str(t) + "\n") - FCC.PrintWarning(_msg) + _msg = "typecheck[{0}]: {1} is not {2}".format(name, v, t) + messages._wrn(_msg) raise TypeError("fcvec." + str(name)) @@ -208,7 +207,7 @@ def equals(u, v): The second vector. Returns - ------ + ------- bool `True` if the vectors are within the precision, `False` otherwise. """ @@ -497,9 +496,9 @@ def rotate(u, angle, axis=Vector(0, 0, 1)): ys = y * s zs = z * s - m = Matrix(c + x*x*t, xyt - zs, xzt + ys, 0, - xyt + zs, c + y*y*t, yzt - xs, 0, - xzt - ys, yzt + xs, c + z*z*t, 0) + m = FreeCAD.Matrix(c + x*x*t, xyt - zs, xzt + ys, 0, + xyt + zs, c + y*y*t, yzt - xs, 0, + xzt - ys, yzt + xs, c + z*z*t, 0) return m.multiply(u) @@ -547,7 +546,7 @@ def getRotation(vector, reference=Vector(1, 0, 0)): def isNull(vector): - """Returns `False` if each of the components of the vector is zero. + """Return False if each of the components of the vector is zero. Due to rounding errors, an element is probably never going to be exactly zero. Therefore, it rounds the element by the number diff --git a/src/Mod/Draft/Init.py b/src/Mod/Draft/Init.py index 47d60d6f63..8798db3f9e 100644 --- a/src/Mod/Draft/Init.py +++ b/src/Mod/Draft/Init.py @@ -1,31 +1,36 @@ -#*************************************************************************** -#* Copyright (c) 2009 Yorik van Havre * -#* * -#* This program is free software; you can redistribute it and/or modify * -#* it under the terms of the GNU Lesser General Public License (LGPL) * -#* as published by the Free Software Foundation; either version 2 of * -#* the License, or (at your option) any later version. * -#* for detail see the LICENCE text file. * -#* * -#* This program 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 program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** +# *************************************************************************** +# * Copyright (c) 2009 Yorik van Havre * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Initialization file of the workbench, non-GUI.""" + +import FreeCAD as App # add Import/Export types -App.addImportType("Autodesk DXF 2D (*.dxf)","importDXF") -App.addImportType("SVG as geometry (*.svg)","importSVG") -App.addImportType("Open CAD Format (*.oca *.gcad)","importOCA") -App.addImportType("Common airfoil data (*.dat)","importAirfoilDAT") -App.addExportType("Autodesk DXF 2D (*.dxf)","importDXF") -App.addExportType("Flattened SVG (*.svg)","importSVG") -App.addExportType("Open CAD Format (*.oca)","importOCA") -App.addImportType("Autodesk DWG 2D (*.dwg)","importDWG") -App.addExportType("Autodesk DWG 2D (*.dwg)","importDWG") +App.addImportType("Autodesk DXF 2D (*.dxf)", "importDXF") +App.addImportType("SVG as geometry (*.svg)", "importSVG") +App.addImportType("Open CAD Format (*.oca *.gcad)", "importOCA") +App.addImportType("Common airfoil data (*.dat)", "importAirfoilDAT") +App.addExportType("Autodesk DXF 2D (*.dxf)", "importDXF") +App.addExportType("Flattened SVG (*.svg)", "importSVG") +App.addExportType("Open CAD Format (*.oca)", "importOCA") +App.addImportType("Autodesk DWG 2D (*.dwg)", "importDWG") +App.addExportType("Autodesk DWG 2D (*.dwg)", "importDWG") + +App.__unit_test__ += ["TestDraft"] diff --git a/src/Mod/Draft/InitGui.py b/src/Mod/Draft/InitGui.py index d864f31af4..1141e911dd 100644 --- a/src/Mod/Draft/InitGui.py +++ b/src/Mod/Draft/InitGui.py @@ -1,4 +1,3 @@ -"""Initialization of the Draft workbench (graphical interface).""" # *************************************************************************** # * Copyright (c) 2009 Yorik van Havre * # * * @@ -19,7 +18,10 @@ # * USA * # * * # *************************************************************************** +"""Initialization of the Draft workbench (graphical interface).""" + import os + import FreeCAD import FreeCADGui @@ -173,4 +175,4 @@ FreeCADGui.addPreferencePage(":/ui/preferences-dwg.ui", QT_TRANSLATE_NOOP("Draft FreeCADGui.addPreferencePage(":/ui/preferences-svg.ui", QT_TRANSLATE_NOOP("Draft", "Import-Export")) FreeCADGui.addPreferencePage(":/ui/preferences-oca.ui", QT_TRANSLATE_NOOP("Draft", "Import-Export")) -FreeCAD.__unit_test__ += ["TestDraft"] +FreeCAD.__unit_test__ += ["TestDraftGui"] diff --git a/src/Mod/Draft/Resources/Draft.qrc b/src/Mod/Draft/Resources/Draft.qrc index ec7219db5c..fc9da4a2e6 100644 --- a/src/Mod/Draft/Resources/Draft.qrc +++ b/src/Mod/Draft/Resources/Draft.qrc @@ -55,6 +55,7 @@ icons/Draft_Offset.svg icons/Draft_PathArray.svg icons/Draft_PathLinkArray.svg + icons/Draft_PlaneProxy.svg icons/Draft_Point.svg icons/Draft_PointArray.svg icons/Draft_PolarArray.svg diff --git a/src/Mod/Draft/Resources/icons/Draft_PlaneProxy.svg b/src/Mod/Draft/Resources/icons/Draft_PlaneProxy.svg new file mode 100644 index 0000000000..129e3a3658 --- /dev/null +++ b/src/Mod/Draft/Resources/icons/Draft_PlaneProxy.svg @@ -0,0 +1,436 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Mon Oct 10 13:44:52 2011 +0000 + + + [wmayer] + + + + + FreeCAD LGPL2+ + + + + + FreeCAD + + + FreeCAD/src/Mod/Draft/Resources/icons/Draft_PlaneProxy.svg + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + [agryson] Alexander Gryson, vocx + + + + + rectangle + grid + plane + + + A rectangle sitting on a plane aligned to a grid that is going into the page from the left to the right; color variation + + + + + diff --git a/src/Mod/Draft/Resources/ui/TaskPanel_CircularArray.ui b/src/Mod/Draft/Resources/ui/TaskPanel_CircularArray.ui index ece65b12ee..b70551ce8b 100644 --- a/src/Mod/Draft/Resources/ui/TaskPanel_CircularArray.ui +++ b/src/Mod/Draft/Resources/ui/TaskPanel_CircularArray.ui @@ -7,7 +7,7 @@ 0 0 445 - 488 + 511 @@ -54,7 +54,8 @@ - The coordinates of the point through which the axis of rotation passes. + The coordinates of the point through which the axis of rotation passes. +Change the direction of the axis itself in the property editor. Center of rotation @@ -127,7 +128,7 @@ - Reset the coordinates of the center of rotation + Reset the coordinates of the center of rotation. Reset point @@ -142,7 +143,8 @@ - If checked, the resulting objects in the array will be fused if they touch each other + If checked, the resulting objects in the array will be fused if they touch each other. +This only works if "Link array" is off. Fuse @@ -152,10 +154,14 @@ - If checked, the resulting objects in the array will be Links instead of simple copies + If checked, the resulting object will be a "Link array" instead of a regular array. +A Link array is more efficient when creating multiple copies, but it cannot be fused together. - Use Links + Link array + + + true @@ -166,7 +172,8 @@ - Distance from one element in the array to the next element in the same layer. It cannot be zero. + Distance from one element in one ring of the array to the next element in the same ring. +It cannot be zero. Tangential distance @@ -176,7 +183,8 @@ - Distance from one element in the array to the next element in the same layer. It cannot be zero. + Distance from one element in one ring of the array to the next element in the same ring. +It cannot be zero. @@ -189,7 +197,7 @@ - Distance from the center of the array to the outer layers + Distance from one layer of objects to the next layer of objects. Radial distance @@ -199,7 +207,7 @@ - Distance from the center of the array to the outer layers + Distance from one layer of objects to the next layer of objects. @@ -212,7 +220,10 @@ - Number that controls how the objects will be distributed + The number of symmetry lines in the circular array. + + + 1 1 @@ -222,7 +233,11 @@ - Number of circular arrays to create, including a copy of the original object. It must be at least 2. + Number of circular layers or rings to create, including a copy of the original object. +It must be at least 2. + + + 2 3 @@ -232,7 +247,8 @@ - Number of circular arrays to create, including a copy of the original object. It must be at least 2. + Number of circular layers or rings to create, including a copy of the original object. +It must be at least 2. Number of circular layers @@ -242,7 +258,7 @@ - Number that controls how the objects will be distributed + The number of symmetry lines in the circular array. Symmetry diff --git a/src/Mod/Draft/Resources/ui/TaskPanel_OrthoArray.ui b/src/Mod/Draft/Resources/ui/TaskPanel_OrthoArray.ui index 52541fe2f9..d4e8889bcc 100644 --- a/src/Mod/Draft/Resources/ui/TaskPanel_OrthoArray.ui +++ b/src/Mod/Draft/Resources/ui/TaskPanel_OrthoArray.ui @@ -41,10 +41,12 @@ - Distance between the elements in the Z direction. Normally, only the Z value is necessary; the other two values can give an additional shift in their respective directions. + Distance between the elements in the Z direction. +Normally, only the Z value is necessary; the other two values can give an additional shift in their respective directions. +Negative values will result in copies produced in the negative direction. - Interval Z + Z intervals @@ -117,7 +119,7 @@ - Reset the distances + Reset the distances. Reset Z @@ -132,7 +134,8 @@ - If checked, the resulting objects in the array will be fused if they touch each other + If checked, the resulting objects in the array will be fused if they touch each other. +This only works if "Link array" is off. Fuse @@ -142,10 +145,14 @@ - If checked, the resulting objects in the array will be Links instead of simple copies + If checked, the resulting object will be a "Link array" instead of a regular array. +A Link array is more efficient when creating multiple copies, but it cannot be fused together. - Use Links + Link array + + + true @@ -164,73 +171,6 @@ - - - - Number of elements in the array in the specified direction, including a copy of the original object. The number must be at least 1 in each direction. - - - Number of elements - - - - - - - - X - - - - - - - Z - - - - - - - Y - - - - - - - 1 - - - 2 - - - - - - - 1 - - - 2 - - - - - - - 1 - - - 1 - - - - - - - - @@ -241,10 +181,12 @@ - Distance between the elements in the X direction. Normally, only the X value is necessary; the other two values can give an additional shift in their respective directions. + Distance between the elements in the X direction. +Normally, only the X value is necessary; the other two values can give an additional shift in their respective directions. +Negative values will result in copies produced in the negative direction. - Interval X + X intervals @@ -317,7 +259,7 @@ - Reset the distances + Reset the distances. Reset X @@ -330,10 +272,12 @@ - Distance between the elements in the Y direction. Normally, only the Y value is necessary; the other two values can give an additional shift in their respective directions. + Distance between the elements in the Y direction. +Normally, only the Y value is necessary; the other two values can give an additional shift in their respective directions. +Negative values will result in copies produced in the negative direction. - Interval Y + Y intervals @@ -406,7 +350,7 @@ - Reset the distances + Reset the distances. Reset Y @@ -416,6 +360,74 @@ + + + + Number of elements in the array in the specified direction, including a copy of the original object. +The number must be at least 1 in each direction. + + + Number of elements + + + + + + + + X + + + + + + + Z + + + + + + + Y + + + + + + + 1 + + + 2 + + + + + + + 1 + + + 2 + + + + + + + 1 + + + 1 + + + + + + + + @@ -429,10 +441,21 @@ + spinbox_n_X + spinbox_n_Y + spinbox_n_Z input_X_x input_X_y input_X_z button_reset_X + input_Y_x + input_Y_y + input_Y_z + button_reset_Y + input_Z_x + input_Z_y + input_Z_z + button_reset_Z checkbox_fuse checkbox_link diff --git a/src/Mod/Draft/Resources/ui/TaskPanel_PolarArray.ui b/src/Mod/Draft/Resources/ui/TaskPanel_PolarArray.ui index 5c85cf69f3..0994d58d79 100644 --- a/src/Mod/Draft/Resources/ui/TaskPanel_PolarArray.ui +++ b/src/Mod/Draft/Resources/ui/TaskPanel_PolarArray.ui @@ -54,7 +54,8 @@ - The coordinates of the point through which the axis of rotation passes. + The coordinates of the point through which the axis of rotation passes. +Change the direction of the axis itself in the property editor. Center of rotation @@ -127,7 +128,7 @@ - Reset the coordinates of the center of rotation + Reset the coordinates of the center of rotation. Reset point @@ -142,7 +143,8 @@ - If checked, the resulting objects in the array will be fused if they touch each other + If checked, the resulting objects in the array will be fused if they touch each other. +This only works if "Link array" is off. Fuse @@ -152,10 +154,14 @@ - If checked, the resulting objects in the array will be Links instead of simple copies + If checked, the resulting object will be a "Link array" instead of a regular array. +A Link array is more efficient when creating multiple copies, but it cannot be fused together. - Use Links + Link array + + + true @@ -166,7 +172,9 @@ - Sweeping angle of the polar distribution + Sweeping angle of the polar distribution. +A negative angle produces a polar pattern in the opposite direction. +The maximum absolute value is 360 degrees. Polar angle @@ -176,20 +184,29 @@ - Sweeping angle of the polar distribution + Sweeping angle of the polar distribution. +A negative angle produces a polar pattern in the opposite direction. +The maximum absolute value is 360 degrees. + + -360.000000000000000 + + + 360.000000000000000 + - 180.000000000000000 + 360.000000000000000 - Number of elements in the array, including a copy of the original object. It must be at least 2. + Number of elements in the array, including a copy of the original object. +It must be at least 2. Number of elements @@ -199,10 +216,14 @@ - Number of elements in the array, including a copy of the original object. It must be at least 2. + Number of elements in the array, including a copy of the original object. +It must be at least 2. + + + 2 - 4 + 5 diff --git a/src/Mod/Draft/TestDraft.py b/src/Mod/Draft/TestDraft.py index 0c9c096544..d585a9fa75 100644 --- a/src/Mod/Draft/TestDraft.py +++ b/src/Mod/Draft/TestDraft.py @@ -1,12 +1,3 @@ -"""Unit tests for the Draft workbench. - -From the terminal, run the following: -FreeCAD -t TestDraft - -From within FreeCAD, run the following: -import Test, TestDraft -Test.runTestsFromModule(TestDraft) -""" # *************************************************************************** # * Copyright (c) 2013 Yorik van Havre * # * Copyright (c) 2019 Eliud Cabrera Castillo * @@ -30,6 +21,17 @@ Test.runTestsFromModule(TestDraft) # * USA * # * * # *************************************************************************** +"""Unit tests for the Draft workbench, non-GUI only. + +From the terminal, run the following: +FreeCAD -t TestDraft + +From within FreeCAD, run the following: +import Test, TestDraft +Test.runTestsFromModule(TestDraft) + +For the GUI-only tests see TestDraftGui. +""" # =========================================================================== # The unit tests can be run from the operating system terminal, or from @@ -94,20 +96,17 @@ Test.runTestsFromModule(TestDraft) # Import tests from drafttests.test_import import DraftImport as DraftTest01 -from drafttests.test_import_gui import DraftGuiImport as DraftTest02 -from drafttests.test_import_tools import DraftImportTools as DraftTest03 -from drafttests.test_pivy import DraftPivy as DraftTest04 # Objects tests -from drafttests.test_creation import DraftCreation as DraftTest05 -from drafttests.test_modification import DraftModification as DraftTest06 +from drafttests.test_creation import DraftCreation as DraftTest02 +from drafttests.test_modification import DraftModification as DraftTest03 # Handling of file formats tests -from drafttests.test_svg import DraftSVG as DraftTest07 -from drafttests.test_dxf import DraftDXF as DraftTest08 -from drafttests.test_dwg import DraftDWG as DraftTest09 -from drafttests.test_oca import DraftOCA as DraftTest10 -from drafttests.test_airfoildat import DraftAirfoilDAT as DraftTest11 +from drafttests.test_svg import DraftSVG as DraftTest04 +from drafttests.test_dxf import DraftDXF as DraftTest05 +from drafttests.test_dwg import DraftDWG as DraftTest06 +from drafttests.test_oca import DraftOCA as DraftTest07 +from drafttests.test_airfoildat import DraftAirfoilDAT as DraftTest08 # Use the modules so that code checkers don't complain (flake8) True if DraftTest01 else False @@ -118,6 +117,3 @@ True if DraftTest05 else False True if DraftTest06 else False True if DraftTest07 else False True if DraftTest08 else False -True if DraftTest09 else False -True if DraftTest10 else False -True if DraftTest11 else False diff --git a/src/Mod/Draft/TestDraftGui.py b/src/Mod/Draft/TestDraftGui.py new file mode 100644 index 0000000000..268a0502e2 --- /dev/null +++ b/src/Mod/Draft/TestDraftGui.py @@ -0,0 +1,105 @@ +# *************************************************************************** +# * Copyright (c) 2013 Yorik van Havre * +# * Copyright (c) 2020 Eliud Cabrera Castillo * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * FreeCAD 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 FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Unit tests for the Draft workbench, GUI only. + +From the terminal, run the following: +FreeCAD -t TestDraftGui + +From within FreeCAD, run the following: +import Test, TestDraftGui +Test.runTestsFromModule(TestDraftGui) + +For the non-GUI tests see TestDraft. +""" + +# =========================================================================== +# The unit tests can be run from the operating system terminal, or from +# within FreeCAD itself. +# +# The tests can be run using the full 'FreeCAD' executable +# or the console only 'FreeCADCmd' executable. In the latter case +# some functions cannot be tested as the view providers (visual properties) +# are not available. +# +# =========================================================================== +# In the following, first the command to run the test from the operating +# system terminal is listed, followed by the commands to run the test +# from the Python console within FreeCAD. +# +# =========================================================================== +# Run all Draft tests +# ---- +# FreeCAD -t TestDraft +# +# >>> import Test, TestDraft +# >>> Test.runTestsFromModule(TestDraft) +# +# =========================================================================== +# Run tests from a specific module (all classes within this module) +# ---- +# FreeCAD -t drafttests.test_creation +# +# >>> import Test, drafttests.test_creation +# >>> Test.runTestsFromModule(drafttests.test_creation) +# +# =========================================================================== +# Run tests from a specific class within a module +# ---- +# FreeCAD -t drafttests.test_creation.DraftCreation +# +# >>> import Test, drafttests.test_creation +# >>> Test.runTestsFromClass(drafttests.test_creation.DraftCreation) +# +# =========================================================================== +# Run a specific unit test from a class within a module +# ---- +# FreeCAD -t drafttests.test_creation.DraftCreation.test_line +# +# >>> import unittest +# >>> one_test = "drafttests.test_creation.DraftCreation.test_line" +# >>> all_tests = unittest.TestLoader().loadTestsFromName(one_test) +# >>> unittest.TextTestRunner().run(all_tests) + +# =========================================================================== +# When the full test is run +# FreeCAD -t TestDraft +# +# all classes that are found in this file are run. +# +# We import the classes from submodules. These classes contain +# the actual unit tests. +# +# The classes will be run in alphabetical order. So, to force +# a particular order of testing we import them with a name +# that follows a defined alphanumeric sequence. + +# Import tests +from drafttests.test_import_gui import DraftGuiImport as DraftTestGui01 +from drafttests.test_import_tools import DraftImportTools as DraftTestGui02 +from drafttests.test_pivy import DraftPivy as DraftTestGui03 + +# Use the modules so that code checkers don't complain (flake8) +True if DraftTestGui01 else False +True if DraftTestGui02 else False +True if DraftTestGui03 else False diff --git a/src/Mod/Draft/WorkingPlane.py b/src/Mod/Draft/WorkingPlane.py index f0910f28e5..7201433eaf 100644 --- a/src/Mod/Draft/WorkingPlane.py +++ b/src/Mod/Draft/WorkingPlane.py @@ -1,20 +1,3 @@ -## @package WorkingPlane -# \ingroup DRAFT -# \brief This module handles the Working Plane and grid of the Draft module. -# -# This module provides the plane class which provides a virtual working plane -# in FreeCAD and a couple of utility functions. -"""@package WorkingPlane -\ingroup DRAFT -\brief This module handles the working plane and grid of the Draft Workbench. - -This module provides the plane class which provides a virtual working plane -in FreeCAD and a couple of utility functions. -The working plane is mostly intended to be used in the Draft Workbench -to draw 2D objects in various orientations, not only in the standard XY, -YZ, and XZ planes. -""" - # *************************************************************************** # * Copyright (c) 2009, 2010 Ken Cline * # * * @@ -35,64 +18,87 @@ YZ, and XZ planes. # * USA * # * * # *************************************************************************** +"""Provide the working plane code and utilities for the Draft Workbench. +This module provides the plane class which provides a virtual working plane +in FreeCAD and a couple of utility functions. +The working plane is mostly intended to be used in the Draft Workbench +to draw 2D objects in various orientations, not only in the standard XY, +YZ, and XZ planes. +""" +## @package WorkingPlane +# \ingroup DRAFT +# \brief This module handles the Working Plane and grid of the Draft module. +# +# This module provides the plane class which provides a virtual working plane +# in FreeCAD and a couple of utility functions. -import FreeCAD, math, DraftVecUtils +import math + +import FreeCAD +import DraftVecUtils from FreeCAD import Vector -from FreeCAD import Console as FCC __title__ = "FreeCAD Working Plane utility" __author__ = "Ken Cline" __url__ = "https://www.freecadweb.org" -class plane: +class Plane: """A WorkPlane object. + Parameters + ---------- + u: Base::Vector3, optional + An axis (vector) that helps define the working plane. + It defaults to `(1, 0, 0)`, or the +X axis. + + v: Base::Vector3, optional + An axis (vector) that helps define the working plane. + It defaults to `(0, 1, 0)`, or the +Y axis. + + w: Base::Vector3, optional + An axis that is supposed to be perpendicular to `u` and `v`; + it is redundant. + It defaults to `(0, 0, 1)`, or the +Z axis. + + pos: Base::Vector3, optional + A point through which the plane goes through. + It defaults to the origin `(0, 0, 0)`. + Attributes ---------- - doc : App::Document + doc: App::Document The active document. Reset view when `doc` changes. - weak : bool + + weak: bool It is `True` if the plane has been defined by `setup()` or has been reset. A weak plane can be changed (it is the "auto" mode), while a strong plane will keep its position until weakened (it is "locked") - u : Base::Vector3 + + u: Base::Vector3 An axis (vector) that helps define the working plane. - v : Base::Vector3 + + v: Base::Vector3 An axis (vector) that helps define the working plane. - axis : Base::Vector3 + + axis: Base::Vector3 A vector that is supposed to be perpendicular to `u` and `v`; it is helpful although redundant. - position : Base::Vector3 + + position: Base::Vector3 A point, which the plane goes through, that helps define the working plane. - stored : bool + + stored: bool A placeholder for a stored state. """ def __init__(self, u=Vector(1, 0, 0), v=Vector(0, 1, 0), w=Vector(0, 0, 1), pos=Vector(0, 0, 0)): - """Initialize the working plane. - Parameters - ---------- - u : Base::Vector3, optional - An axis (vector) that helps define the working plane. - It defaults to `(1, 0, 0)`, or the +X axis. - v : Base::Vector3, optional - An axis (vector) that helps define the working plane. - It defaults to `(0, 1, 0)`, or the +Y axis. - w : Base::Vector3, optional - An axis that is supposed to be perpendicular to `u` and `v`; - it is redundant. - It defaults to `(0, 0, 1)`, or the +Z axis. - pos : Base::Vector3, optional - A point through which the plane goes through. - It defaults to the origin `(0, 0, 0)`. - """ # keep track of active document. Reset view when doc changes. self.doc = None self.weak = True @@ -122,6 +128,7 @@ class plane: ---------- p : Base::Vector3 The external point to consider. + direction : Base::Vector3, optional The unit vector that indicates the direction of the distance. @@ -326,8 +333,8 @@ class plane: offsetVector.multiply(offset) self.position = point.add(offsetVector) self.weak = False - # FCC.PrintMessage("(position = " + str(self.position) + ")\n") - # FCC.PrintMessage(self.__repr__() + "\n") + # Console.PrintMessage("(position = " + str(self.position) + ")\n") + # Console.PrintMessage(self.__repr__() + "\n") def alignToPointAndAxis_SVG(self, point, axis, offset=0): """Align the working plane to a point and an axis (vector). @@ -436,14 +443,14 @@ class plane: # spat_vec = self.u.cross(self.v) # spat_res = spat_vec.dot(axis) - # FCC.PrintMessage(projcase + " spat Prod = " + str(spat_res) + "\n") + # Console.PrintMessage(projcase + " spat Prod = " + str(spat_res) + "\n") offsetVector = Vector(axis) offsetVector.multiply(offset) self.position = point.add(offsetVector) self.weak = False - # FCC.PrintMessage("(position = " + str(self.position) + ")\n") - # FCC.PrintMessage(self.__repr__() + "\n") + # Console.PrintMessage("(position = " + str(self.position) + ")\n") + # Console.PrintMessage(self.__repr__() + "\n") def alignToCurve(self, shape, offset=0): """Align plane to curve. NOT YET IMPLEMENTED. @@ -631,7 +638,7 @@ class plane: When the interface is not loaded it should fail and print a message, `FreeCAD.Console.PrintError()`. - See also + See Also -------- alignToFace, alignToCurve """ @@ -652,7 +659,7 @@ class plane: return False def setup(self, direction=None, point=None, upvec=None, force=False): - """Setup the working plane if it exists but is undefined. + """Set up the working plane if it exists but is undefined. If `direction` and `point` are present, it calls `alignToPointAndAxis(point, direction, 0, upvec)`. @@ -704,7 +711,7 @@ class plane: # perpendicular to the current view self.alignToPointAndAxis(Vector(0, 0, 0), vdir.negative(), 0, upvec) - except: + except Exception: pass if force: self.weak = False @@ -719,6 +726,39 @@ class plane: self.doc = None self.weak = True + def setTop(self): + """sets the WP to top position and updates the GUI""" + self.alignToPointAndAxis(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(0, 0, 1), 0.0) + if FreeCAD.GuiUp: + import FreeCADGui + from draftutils.translate import translate + if hasattr(FreeCADGui,"Snapper"): + FreeCADGui.Snapper.setGrid() + if hasattr(FreeCADGui,"draftToolBar"): + FreeCADGui.draftToolBar.wplabel.setText(translate("draft", "Top")) + + def setFront(self): + """sets the WP to front position and updates the GUI""" + self.alignToPointAndAxis(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(0, 1, 0), 0.0) + if FreeCAD.GuiUp: + import FreeCADGui + from draftutils.translate import translate + if hasattr(FreeCADGui,"Snapper"): + FreeCADGui.Snapper.setGrid() + if hasattr(FreeCADGui,"draftToolBar"): + FreeCADGui.draftToolBar.wplabel.setText(translate("draft", "Front")) + + def setSide(self): + """sets the WP to top position and updates the GUI""" + self.alignToPointAndAxis(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(-1, 0, 0), 0.0) + if FreeCAD.GuiUp: + import FreeCADGui + from draftutils.translate import translate + if hasattr(FreeCADGui,"Snapper"): + FreeCADGui.Snapper.setGrid() + if hasattr(FreeCADGui,"draftToolBar"): + FreeCADGui.draftToolBar.wplabel.setText(translate("draft", "Side")) + def getRotation(self): """Return a placement describing the plane orientation only. @@ -878,7 +918,7 @@ class plane: Base::Vector3 The relative coordinates of the point from the plane. - See also + See Also -------- getGlobalCoords, getLocalRot, getGlobalRot @@ -944,7 +984,7 @@ class plane: Base::Vector3 The coordinates of the point from the absolute origin. - See also + See Also -------- getLocalCoords, getLocalRot, getGlobalRot @@ -999,7 +1039,7 @@ class plane: The relative coordinates of the point from the plane, if the plane had its `position` at the global origin. - See also + See Also -------- getLocalCoords, getGlobalCoords, getGlobalRot """ @@ -1039,7 +1079,7 @@ class plane: Base::Vector3 The coordinates of the point from the absolute origin. - See also + See Also -------- getGlobalCoords, getLocalCoords, getLocalRot """ @@ -1144,7 +1184,7 @@ class plane: Angle between the `u` vector, and a projected vector on the global horizontal plane. - See also + See Also -------- DraftVecUtils.angle """ @@ -1156,6 +1196,9 @@ class plane: return DraftVecUtils.angle(self.u, proj, norm) +plane = Plane + + def getPlacementFromPoints(points): """Return a placement from a list of 3 or 4 points. @@ -1185,7 +1228,7 @@ def getPlacementFromPoints(points): defined by `points`, or `None` is it fails to use the points. - See also + See Also -------- getPlacement """ @@ -1198,7 +1241,7 @@ def getPlacementFromPoints(points): pl.axis = (points[3].sub(points[0]).normalize()) else: pl.axis = ((pl.u).cross(pl.v)).normalize() - except: + except Exception: return None p = pl.getPlacement() del pl @@ -1229,14 +1272,14 @@ def getPlacementFromFace(face, rotated=False): defined by `face`, or `None` if it fails to use `face`. - See also + See Also -------- alignToFace, getPlacement """ pl = plane() try: pl.alignToFace(face) - except: + except Exception: return None p = pl.getPlacement(rotated) del pl diff --git a/src/Mod/Draft/draftguitools/__init__.py b/src/Mod/Draft/draftguitools/__init__.py index e69de29bb2..5566380aa1 100644 --- a/src/Mod/Draft/draftguitools/__init__.py +++ b/src/Mod/Draft/draftguitools/__init__.py @@ -0,0 +1,6 @@ +"""Commands that require the graphical user interface to work. + +These GUI commands are called by buttons, menus, contextual menus, +toolbars, or other ways that require graphical widgets. +They are normally loaded in the workbench's `InitGui.py`. +""" diff --git a/src/Mod/Draft/draftguitools/gui_arrays.py b/src/Mod/Draft/draftguitools/gui_arrays.py index 49a7f096f5..81bff17fbb 100644 --- a/src/Mod/Draft/draftguitools/gui_arrays.py +++ b/src/Mod/Draft/draftguitools/gui_arrays.py @@ -1,8 +1,3 @@ -"""Provide the Draft ArrayTools command to group the other array tools.""" -## @package gui_arrays -# \ingroup DRAFT -# \brief Provide the Draft ArrayTools command to group the other array tools. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -25,9 +20,14 @@ # * USA * # * * # *************************************************************************** +"""Provide the Draft ArrayTools command to group the other array tools.""" +## @package gui_arrays +# \ingroup DRAFT +# \brief Provide the Draft ArrayTools command to group the other array tools. +from PySide.QtCore import QT_TRANSLATE_NOOP + import FreeCAD as App import FreeCADGui as Gui -from PySide.QtCore import QT_TRANSLATE_NOOP class ArrayGroupCommand: @@ -49,8 +49,11 @@ class ArrayGroupCommand: 'ToolTip': QT_TRANSLATE_NOOP("Arch", _tooltip)} def IsActive(self): - """Be active only when a document is active.""" - return App.ActiveDocument is not None + """Return True when this command should be available.""" + if App.ActiveDocument: + return True + else: + return False Gui.addCommand('Draft_ArrayTools', ArrayGroupCommand()) diff --git a/src/Mod/Draft/draftguitools/gui_base.py b/src/Mod/Draft/draftguitools/gui_base.py index f307b67315..1a149cc8cc 100644 --- a/src/Mod/Draft/draftguitools/gui_base.py +++ b/src/Mod/Draft/draftguitools/gui_base.py @@ -1,9 +1,3 @@ -"""This module provides the Base object for all Draft Gui commands. -""" -## @package gui_base -# \ingroup DRAFT -# \brief This module provides the Base object for all Draft Gui commands. - # *************************************************************************** # * (c) 2009 Yorik van Havre * # * (c) 2010 Ken Cline * @@ -28,10 +22,14 @@ # * USA * # * * # *************************************************************************** +"""Provide the Base object for all Draft Gui commands.""" +## @package gui_base +# \ingroup DRAFT +# \brief This module provides the Base object for all Draft Gui commands. import FreeCAD as App import FreeCADGui as Gui -import DraftGui +import draftutils.todo as todo class GuiCommandBase: @@ -52,7 +50,7 @@ class GuiCommandBase: Each string in the list of strings represents a Python instruction which will be executed in a delayed fashion - by `DraftGui.todo.delayCommit()` + by `todo.ToDo.delayCommit()` :: list1 = ["a = FreeCAD.Vector()", "pl = FreeCAD.Placement()", @@ -69,6 +67,7 @@ class GuiCommandBase: >>> pl = FreeCAD.Placement() >>> Draft.autogroup(obj) """ + def __init__(self): self.call = None self.commit_list = [] @@ -78,7 +77,8 @@ class GuiCommandBase: self.planetrack = None def IsActive(self): - if Gui.ActiveDocument: + """Return True when this command should be available.""" + if App.ActiveDocument: return True else: return False @@ -102,7 +102,7 @@ class GuiCommandBase: pass self.call = None if self.commit_list: - DraftGui.todo.delayCommit(self.commit_list) + todo.ToDo.delayCommit(self.commit_list) self.commit_list = [] def commit(self, name, func): diff --git a/src/Mod/Draft/draftguitools/gui_circulararray.py b/src/Mod/Draft/draftguitools/gui_circulararray.py index e75139f35e..cc229af358 100644 --- a/src/Mod/Draft/draftguitools/gui_circulararray.py +++ b/src/Mod/Draft/draftguitools/gui_circulararray.py @@ -1,9 +1,3 @@ -"""This module provides the Draft CircularArray tool. -""" -## @package gui_circulararray -# \ingroup DRAFT -# \brief This module provides the Draft CircularArray tool. - # *************************************************************************** # * (c) 2019 Eliud Cabrera Castillo * # * * @@ -26,45 +20,34 @@ # * USA * # * * # *************************************************************************** +"""Provides the Draft CircularArray GuiCommand.""" +## @package gui_circulararray +# \ingroup DRAFT +# \brief This module provides the Draft CircularArray tool. + +from pivy import coin +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui import Draft -import DraftGui -import Draft_rc -from . import gui_base +import Draft_rc # include resources, icons, ui files +from draftutils.messages import _msg, _log +from draftutils.translate import _tr +from draftguitools import gui_base from drafttaskpanels import task_circulararray +import draftutils.todo as todo - -if App.GuiUp: - from PySide.QtCore import QT_TRANSLATE_NOOP - # import DraftTools - from DraftGui import translate - # from DraftGui import displayExternal - from pivy import coin -else: - def QT_TRANSLATE_NOOP(context, text): - return text - - def translate(context, text): - return text - - -def _tr(text): - """Function to translate with the context set""" - return translate("Draft", text) - - -# So the resource file doesn't trigger errors from code checkers (flake8) -True if Draft_rc.__name__ else False +# The module is used to prevent complaints from code checkers (flake8) +bool(Draft_rc.__name__) class GuiCommandCircularArray(gui_base.GuiCommandBase): - """Gui command for the CircularArray tool""" + """Gui command for the CircularArray tool.""" def __init__(self): super().__init__() - self.command_name = "CircularArray" + self.command_name = "Circular array" self.location = None self.mouse_event = None self.view = None @@ -74,22 +57,28 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): self.point = App.Vector() def GetResources(self): - _msg = ("Creates copies of a selected object, " + """Set icon, menu and tooltip.""" + _tip = ("Creates copies of a selected object, " "and places the copies in a circular pattern.\n" "The properties of the array can be further modified after " "the new object is created, including turning it into " "a different type of array.") + d = {'Pixmap': 'Draft_CircularArray', 'MenuText': QT_TRANSLATE_NOOP("Draft", "Circular array"), - 'ToolTip': QT_TRANSLATE_NOOP("Draft", _msg)} + 'ToolTip': QT_TRANSLATE_NOOP("Draft", _tip)} return d def Activated(self): - """This is called when the command is executed. + """Execute when the command is called. We add callbacks that connect the 3D view with the widgets of the task panel. """ + _log("GuiCommand: {}".format(_tr(self.command_name))) + _msg("{}".format(16*"-")) + _msg("GuiCommand: {}".format(_tr(self.command_name))) + self.location = coin.SoLocation2Event.getClassTypeId() self.mouse_event = coin.SoMouseButtonEvent.getClassTypeId() self.view = Draft.get3DView() @@ -103,10 +92,10 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): # of the interface, to be able to call a function from within it. self.ui.source_command = self # Gui.Control.showDialog(self.ui) - DraftGui.todo.delay(Gui.Control.showDialog, self.ui) + todo.ToDo.delay(Gui.Control.showDialog, self.ui) def move(self, event_cb): - """This is a callback for when the mouse pointer moves in the 3D view. + """Execute as a callback when the pointer moves in the 3D view. It should automatically update the coordinates in the widgets of the task panel. @@ -119,7 +108,7 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): self.ui.display_point(self.point) def click(self, event_cb=None): - """This is a callback for when the mouse pointer clicks on the 3D view. + """Execute as a callback when the pointer clicks on the 3D view. It should act as if the Enter key was pressed, or the OK button was pressed in the task panel. @@ -136,7 +125,7 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): self.ui.accept() def completed(self): - """This is called when the command is terminated. + """Execute when the command is terminated. We should remove the callbacks that were added to the 3D view and then close the task panel. @@ -146,10 +135,8 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): self.view.removeEventCallbackPivy(self.mouse_event, self.callback_click) if Gui.Control.activeDialog(): - Gui.Snapper.off() Gui.Control.closeDialog() super().finish() -if App.GuiUp: - Gui.addCommand('Draft_CircularArray', GuiCommandCircularArray()) +Gui.addCommand('Draft_CircularArray', GuiCommandCircularArray()) diff --git a/src/Mod/Draft/DraftEdit.py b/src/Mod/Draft/draftguitools/gui_edit.py similarity index 78% rename from src/Mod/Draft/DraftEdit.py rename to src/Mod/Draft/draftguitools/gui_edit.py index 095115269c..a8c3e3a574 100644 --- a/src/Mod/Draft/DraftEdit.py +++ b/src/Mod/Draft/draftguitools/gui_edit.py @@ -1,90 +1,89 @@ -# -*- coding: utf8 -*- -#*************************************************************************** -#* Copyright (c) 2009, 2010 Yorik van Havre * -#* Copyright (c) 2009, 2010 Ken Cline * -#* Copyright (c) 2019, 2020 Carlo Pavan * -#* * -#* This program is free software; you can redistribute it and/or modify * -#* it under the terms of the GNU Lesser General Public License (LGPL) * -#* as published by the Free Software Foundation; either version 2 of * -#* the License, or (at your option) any later version. * -#* for detail see the LICENCE text file. * -#* * -#* This program 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 program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2019, 2020 Carlo Pavan * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provide the Draft_Edit command used by the Draft workbench.""" +## @package gui_edit +# \ingroup DRAFT +# \brief Provide the Draft_Edit command used by the Draft workbench -__title__= "FreeCAD Draft Edit Tool" -__author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, \ - Dmitry Chigrin, Carlo Pavan" -__url__ = "https://www.freecadweb.org" +import math +from pivy import coin +from PySide import QtCore, QtGui import FreeCAD as App -import math +import FreeCADGui as Gui import Draft +import DraftTools +from draftutils.translate import translate +import draftguitools.gui_trackers as trackers -if App.GuiUp: - # Do not import GUI-related modules if GUI is not there - import FreeCADGui as Gui - import DraftTools - from draftguitools.gui_trackers import editTracker, wireTracker, arcTracker, bsplineTracker, bezcurveTracker - from pivy import coin - from PySide import QtCore, QtGui - from PySide.QtCore import QT_TRANSLATE_NOOP - from DraftTools import translate +__title__ = "FreeCAD Draft Edit Tool" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin, Carlo Pavan") +__url__ = "https://www.freecadweb.org" - COLORS = { - "default": Gui.draftToolBar.getDefaultColor("snap"), - "black": (0., 0., 0.), - "white": (1., 1., 1.), - "grey": (.5, .5, .5), - "red": (1., 0., 0.), - "green": (0., 1., 0.), - "blue": (0., 0., 1.), - "yellow": (1., 1., 0.), - "cyan": (0., 1., 1.), - "magenta":(1., 0., 1.) - } +COLORS = { + "default": Gui.draftToolBar.getDefaultColor("snap"), + "black": (0., 0., 0.), + "white": (1., 1., 1.), + "grey": (.5, .5, .5), + "red": (1., 0., 0.), + "green": (0., 1., 0.), + "blue": (0., 0., 1.), + "yellow": (1., 1., 0.), + "cyan": (0., 1., 1.), + "magenta": (1., 0., 1.) +} -class Edit(): - """ - The Draft_Edit FreeCAD command definition. +class Edit: + """The Draft_Edit FreeCAD command definition. + A tool to graphically edit FreeCAD objects. Current implementation use many parts of pivy graphics code by user "looo". - The tool collect editpoints from objects and display Trackers on them to allow - editing their Shape and their parameters. - + The tool collect editpoints from objects and display Trackers on them + to allow editing their Shape and their parameters. Callbacks - ---------- + --------- selection_callback registered when tool is launched, identify selected objects. - + editing_callbacks self._keyPressedCB -> self.keyPressed self._mouseMovedCB -> self._mouseMovedCB if self._mousePressedCB -> self.mousePressed when trackers are displayed for selected objects, - these callbacks capture user events and forward + these callbacks capture user events and forward them to related functions - Task panel (Draft Toolbar) ---------- self.ui = Gui.draftToolBar TODO: since we introduced context menu for interacting - with editTrackers, point 2 should become obsolete, + with editTrackers, point 2 should become obsolete, because not consistent with multi-object editing. + Draft_Edit uses taskpanel in 3 ways: 1 - when waiting for user to select an object @@ -108,62 +107,58 @@ class Edit(): self.ui.lineUi() self.ui.isRelative.show() - Tracker selection - ---------- + ----------------- If the tool recognize mouse click as an attempt to startEditing, using soRayPickAction, it identifies the selected editTracker and start editing it. Here is where "looo" code was very useful. Editing preview - ---------- - When object editing begins, self.ghost is initiated with the + --------------- + When object editing begins, self.ghost is initiated with the corresponding DraftTracker of the object type. The object Tracker is deleted when user clicks again and endEditing. - Context Menu - ---------- + ------------ Activated with Alt+LeftClick or pressing key "e" It's a custom context menu, that depends on clicked tracker or on clicked object. display_tracker_menu populates the menu with custom actions - + evaluate_menu_action evaluate user chosen action and launch corresponding function. - Preferences - ---------- - maxObjects : Int + ----------- + maxObjects: Int set by "DraftEditMaxObjects" in user preferences The max number of FreeCAD objects the tool is allowed to edit at the same time. - pick_radius : Int + pick_radius: Int set by "DraftEditPickRadius" in user preferences The pick radius during editing operation. Increase if you experience problems in clicking on a editTracker because of screen resolution. - Attributes ---------- - obj : Edited object + obj: Edited object I'm planning to discard this attribute. - In old implementation every function was supposed to + In old implementation every function was supposed to act on self.obj, self.editpoints, self.trackers, self.pl, self.invpl. Due to multiple object editing, i'm planning to keep just self.trackers. Any other object will be identified and processed starting from editTracker information. - - editing : Int - Index of the editTracker that has been clicked by the + + editing: Int + Index of the editTracker that has been clicked by the user. Tracker selection mechanism is based on it. if self.editing == None : the user didn't click any node, and next click will @@ -172,51 +167,48 @@ class Edit(): the user is editing corresponding node, so next click will be processed as an attempt to end editing operation - editpoints : List [FreeCAD::App.Vector] - List of editpoints collected from the edited object, + editpoints: List [FreeCAD::App.Vector] + List of editpoints collected from the edited object, on whick editTrackers will be placed. - trackers : Dictionary {object.Name : [editTrackers]} + trackers: Dictionary {object.Name : [editTrackers]} It records the list of DraftTrackers.editTracker. {object.Name as String : [editTrackers for the object]} - Each tracker is created with (position,obj.Name,idx), - so it's possible to recall it + Each tracker is created with (position,obj.Name,idx), + so it's possible to recall it self.trackers[str(node.objectName.getValue())][ep] - overNode : DraftTrackers.editTracker + overNode: DraftTrackers.editTracker It represent the editTracker under the cursor position. It is used to preview the tracker selection action. - ghost : DraftTrackers.* + ghost: DraftTrackers.* Handles the tracker to preview editing operations. it is initialized when user clicks on a editTracker by self.startEditing() function. - alt_edit_mode : Int + alt_edit_mode: Int Allows alternative editing modes for objects. ATM supported for: - - arcs: if 0 edit by 3 points, if 1 edit by center, + - arcs: if 0 edit by 3 points, if 1 edit by center, radius, angles - supportedObjs : List + supportedObjs: List List of supported Draft Objects. The tool use Draft.getType(obj) to compare object type to the list. - supportedPartObjs : List + supportedPartObjs: List List of supported Part Objects. - The tool use Draft.getType(obj) and obj.TypeId to compare + The tool use Draft.getType(obj) and obj.TypeId to compare object type to the list. - """ - + def __init__(self): - """ - Initialize Draft_Edit Command. - """ + """Initialize Draft_Edit Command.""" self.running = False - self.trackers = {'object':[]} - self.overNode = None # preselected node with mouseover + self.trackers = {'object': []} + self.overNode = None # preselected node with mouseover self.obj = None self.editing = None @@ -265,9 +257,9 @@ class Edit(): } - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # MAIN FUNCTIONS - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def Activated(self): """ @@ -277,7 +269,7 @@ class Edit(): """ if self.running: self.finish() - DraftTools.Modifier.Activated(self,"Edit") + DraftTools.Modifier.Activated(self, "Edit") if not App.ActiveDocument: self.finish() @@ -289,26 +281,26 @@ class Edit(): else: self.ui.selectUi() App.Console.PrintMessage(translate("draft", - "Select a Draft object to edit") - + "\n") + "Select a Draft object to edit") + + "\n") self.register_selection_callback() def proceed(self): - "this method defines editpoints and set the editTrackers" + """this method defines editpoints and set the editTrackers""" self.unregister_selection_callback() self.edited_objects = self.getObjsFromSelection() if not self.edited_objects: - return self.finish() + return self.finish() # Save selectstate and turn selectable false. # Object can remain selectable commenting following lines: - # self.saveSelectState(self.obj) + # self.saveSelectState(self.obj) # self.setSelectState(self.obj, False) # start object editing Gui.Selection.clearSelection() Gui.Snapper.setSelectMode(True) - + self.ui.editUi() for obj in self.edited_objects: @@ -321,10 +313,8 @@ class Edit(): # self.alignWorkingPlane() - def finish(self,closed=False): - """ - terminates Edit Tool - """ + def finish(self, closed=False): + """Terminate Edit Tool.""" self.unregister_selection_callback() self.unregister_editing_callbacks() self.editing = None @@ -355,16 +345,14 @@ class Edit(): self.running = False # delay resetting edit mode otherwise it doesn't happen from PySide import QtCore - QtCore.QTimer.singleShot(0,Gui.ActiveDocument.resetEdit) + QtCore.QTimer.singleShot(0, Gui.ActiveDocument.resetEdit) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # SCENE EVENTS CALLBACKS - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def register_selection_callback(self): - """ - register callback for selection when command is launched - """ + """Register callback for selection when command is launched.""" self.unregister_selection_callback() self.selection_callback = self.view.addEventCallback("SoEvent",DraftTools.selectObject) @@ -373,7 +361,7 @@ class Edit(): remove selection callback if it exists """ if self.selection_callback: - self.view.removeEventCallback("SoEvent",self.selection_callback) + self.view.removeEventCallback("SoEvent", self.selection_callback) self.selection_callback = None def register_editing_callbacks(self): @@ -412,28 +400,26 @@ class Edit(): self._mousePressedCB = None #App.Console.PrintMessage("Draft edit mouse button callback unregistered \n") - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # SCENE EVENT HANDLERS - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def keyPressed(self, event_callback): - """ - keyboard event handler - """ - #TODO: Get the keys from preferences + """Execute as callback for keyboard event.""" + # TODO: Get the keys from preferences event = event_callback.getEvent() if event.getState() == coin.SoKeyboardEvent.DOWN: key = event.getKey() - #App.Console.PrintMessage("pressed key : "+str(key)+"\n") - if key == 65307: # ESC + # App.Console.PrintMessage("pressed key : "+str(key)+"\n") + if key == 65307: # ESC self.finish() - if key == 97: # "a" + if key == 97: # "a" self.finish() - if key == 111: # "o" + if key == 111: # "o" self.finish(closed=True) - if key == 101: # "e" + if key == 101: # "e" self.display_tracker_menu(event) - if key == 105: # "i" + if key == 105: # "i" if Draft.getType(self.obj) == "Circle": self.arcInvert(self.obj) @@ -449,14 +435,17 @@ class Edit(): if self.editing is None: self.startEditing(event) else: - self.endEditing(self.obj,self.editing) - elif event.wasAltDown(): #left click with ctrl down + self.endEditing(self.obj, self.editing) + elif event.wasAltDown(): # left click with ctrl down self.display_tracker_menu(event) - + def mouseMoved(self, event_callback): - "mouse moved event handler, update tracker position and update preview ghost" + """Execute as callback for mouse movement. + + Update tracker position and update preview ghost. + """ event = event_callback.getEvent() - if self.editing != None: + if self.editing is not None: self.updateTrackerAndGhost(event) else: # look for a node in mouse position and highlight it @@ -474,7 +463,7 @@ class Edit(): self.overNode = None def startEditing(self, event): - "start editing selected EditNode" + """Start editing selected EditNode.""" pos = event.getPosition() node = self.getEditNode(pos) ep = self.getEditNodeIndex(node) @@ -502,18 +491,19 @@ class Edit(): self.hideTrackers() def updateTrackerAndGhost(self, event): - "updates tracker position when editing and update ghost" + """Update tracker position when editing and update ghost.""" pos = event.getPosition().getValue() orthoConstrain = False - if event.wasShiftDown() == 1: orthoConstrain = True + if event.wasShiftDown() == 1: + orthoConstrain = True snappedPos = Gui.Snapper.snap((pos[0],pos[1]),self.node[-1], constrain=orthoConstrain) self.trackers[self.obj.Name][self.editing].set(snappedPos) - self.ui.displayPoint(snappedPos,self.node[-1]) + self.ui.displayPoint(snappedPos, self.node[-1]) if self.ghost: - self.updateGhost(obj=self.obj,idx=self.editing,pt=snappedPos) + self.updateGhost(obj=self.obj, idx=self.editing, pt=snappedPos) - def endEditing(self, obj, nodeIndex, v = None): - "terminate editing and start object updating process" + def endEditing(self, obj, nodeIndex, v=None): + """Terminate editing and start object updating process.""" self.finalizeGhost() self.trackers[obj.Name][nodeIndex].on() Gui.Snapper.setSelectMode(True) @@ -532,12 +522,12 @@ class Edit(): self.showTrackers() DraftTools.redraw3DView() - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # UTILS - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def getObjsFromSelection(self): - "evaluate selection and returns a valid object to edit" + """Evaluate selection and return a valid object to edit.""" selection = Gui.Selection.getSelection() self.edited_objects = [] if len(selection) > self.maxObjects: @@ -560,8 +550,9 @@ class Edit(): return self.edited_objects def get_selected_obj_at_position(self, pos): - """return object at given position - if object is one of the edited objects (self.edited_objects) + """Return object at given position. + + If object is one of the edited objects (self.edited_objects). """ selobjs = Gui.ActiveDocument.ActiveView.getObjectsInfo((pos[0],pos[1])) if not selobjs: @@ -574,19 +565,22 @@ class Edit(): return obj def numericInput(self, v, numy=None, numz=None): - """this function gets called by the toolbar - or by the mouse click and activate the update function""" - if (numy is not None): - v = App.Vector(v,numy,numz) + """Execute callback by the toolbar to activate the update function. + + This function gets called by the toolbar + or by the mouse click and activate the update function. + """ + if numy: + v = App.Vector(v, numy, numz) self.endEditing(self.obj, self.editing, v) App.ActiveDocument.recompute() - def setSelectState(self, obj, selState = False): - if hasattr(obj.ViewObject,"Selectable"): + def setSelectState(self, obj, selState=False): + if hasattr(obj.ViewObject, "Selectable"): obj.ViewObject.Selectable = selState def saveSelectState(self, obj): - if hasattr(obj.ViewObject,"Selectable"): + if hasattr(obj.ViewObject, "Selectable"): self.selectstate = obj.ViewObject.Selectable def restoreSelectState(self,obj): @@ -594,8 +588,12 @@ class Edit(): if hasattr(obj.ViewObject,"Selectable") and (self.selectstate is not None): obj.ViewObject.Selectable = self.selectstate - def setPlacement(self,obj): - "set self.pl and self.invpl to self.obj placement and inverse placement" + def setPlacement(self, obj): + """Set placement of object. + + Set self.pl and self.invpl to self.obj placement + and inverse placement. + """ if not obj: return if "Placement" in obj.PropertiesList: @@ -603,7 +601,7 @@ class Edit(): self.invpl = self.pl.inverse() def alignWorkingPlane(self): - "align working plane to self.obj" + """Align working plane to self.obj.""" if "Shape" in self.obj.PropertiesList: if DraftTools.plane.weak: DraftTools.plane.alignToFace(self.obj.Shape) @@ -611,12 +609,12 @@ class Edit(): self.planetrack.set(self.editpoints[0]) def getEditNode(self, pos): - "get edit node from given screen position" + """Get edit node from given screen position.""" node = self.sendRay(pos) return node def sendRay(self, mouse_pos): - "sends a ray through the scene and return the nearest entity" + """Send a ray through the scene and return the nearest entity.""" ray_pick = coin.SoRayPickAction(self.render_manager.getViewportRegion()) ray_pick.setPoint(coin.SbVec2s(*mouse_pos)) ray_pick.setRadius(self.pick_radius) @@ -626,7 +624,7 @@ class Edit(): return self.searchEditNode(picked_point) def searchEditNode(self, picked_point): - "search edit node inside picked point list and retrurn node number" + """Search edit node inside picked point list and return node number.""" for point in picked_point: path = point.getPath() length = path.getLength() @@ -637,7 +635,7 @@ class Edit(): return None def getEditNodeIndex(self, point): - "get edit node index from given screen position" + """Get edit node index from given screen position.""" if point: subElement = str(point.subElementName.getValue()) ep = int(subElement[8:]) @@ -645,19 +643,18 @@ class Edit(): else: return None - - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT TRACKERS functions - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def setTrackers(self, obj, points=None): - "set Edit Trackers for editpoints collected from self.obj" + """Set Edit Trackers for editpoints collected from self.obj.""" if points is None or len(points) == 0: App.Console.PrintWarning(translate("draft", - "No edit point found for selected object") - + "\n") + "No edit point found for selected object") + + "\n") # do not finish if some trackers are still present - if self.trackers == {'object':[]}: + if self.trackers == {'object': []}: self.finish() return self.trackers[obj.Name] = [] @@ -667,15 +664,15 @@ class Edit(): if obj.Name in self.trackers: self.removeTrackers(obj) for ep in range(len(points)): - self.trackers[obj.Name].append(editTracker(pos=points[ep],name=obj.Name,idx=ep)) + self.trackers[obj.Name].append(trackers.editTracker(pos=points[ep],name=obj.Name,idx=ep)) def resetTrackers(self, obj): - "reset Edit Trackers and set them again" + """Reset Edit Trackers and set them again.""" self.removeTrackers(obj) self.setTrackers(obj, self.getEditPoints(obj)) - def removeTrackers(self, obj = None): - "reset Edit Trackers and set them again" + def removeTrackers(self, obj=None): + """Remove Edit Trackers.""" if obj: if obj.Name in self.trackers: for t in self.trackers[obj.Name]: @@ -685,16 +682,16 @@ class Edit(): for key in self.trackers.keys(): for t in self.trackers[key]: t.finalize() - self.trackers = {'object':[]} + self.trackers = {'object': []} def hideTrackers(self, obj=None): - """hide Edit Trackers + """Hide Edit Trackers. Attributes ---------- - obj : FreeCAD object - hides trackers only for given object, + obj: FreeCAD object + Hides trackers only for given object, if obj is None, hides all trackers """ if obj is None: @@ -706,12 +703,12 @@ class Edit(): t.off() def showTrackers(self, obj=None): - """show Edit Trackers + """Show Edit Trackers. Attributes ---------- - obj : FreeCAD object - shows trackers only for given object, + obj: FreeCAD object + Shows trackers only for given object, if obj is None, shows all trackers """ if obj is None: @@ -722,22 +719,22 @@ class Edit(): for t in self.trackers[obj.Name]: t.on() - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # PREVIEW - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- - def initGhost(self,obj): - "initialize preview ghost" + def initGhost(self, obj): + """Initialize preview ghost.""" if Draft.getType(obj) == "Wire": - return wireTracker(obj.Shape) + return trackers.wireTracker(obj.Shape) elif Draft.getType(obj) == "BSpline": - return bsplineTracker() + return trackers.bsplineTracker() elif Draft.getType(obj) == "BezCurve": - return bezcurveTracker() + return trackers.bezcurveTracker() elif Draft.getType(obj) == "Circle": - return arcTracker() + return trackers.arcTracker() - def updateGhost(self,obj,idx,pt): + def updateGhost(self, obj, idx, pt): if Draft.getType(obj) in ["Wire"]: self.ghost.on() pointList = self.applyPlacement(obj.Points) @@ -785,9 +782,9 @@ class Edit(): self.ghost.setCenter(self.pl.multVec(p0)) return else: - p1=self.getArcStart(obj,global_placement=True) - p2=self.getArcMid(obj,global_placement=True) - p3=self.getArcEnd(obj,global_placement=True) + p1 = self.getArcStart(obj, global_placement=True) + p2 = self.getArcMid(obj, global_placement=True) + p3 = self.getArcEnd(obj, global_placement=True) if self.editing == 1: p1=pt elif self.editing == 3: @@ -809,7 +806,7 @@ class Edit(): self.ghost.setRadius(self.invpl.multVec(pt).Length) DraftTools.redraw3DView() - def applyPlacement(self,pointList): + def applyPlacement(self, pointList): if self.pl: plist = [] for p in pointList: @@ -826,14 +823,14 @@ class Edit(): except: return - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Add/Delete Vertexes - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- - def addPoint(self,event): - "called by action, add point to obj and reset trackers" + def addPoint(self, event): + """Execute callback, add point to obj and reset trackers.""" pos = event.getPosition() - #self.setSelectState(self.obj, True) + # self.setSelectState(self.obj, True) selobjs = Gui.ActiveDocument.ActiveView.getObjectsInfo((pos[0],pos[1])) if not selobjs: return @@ -850,27 +847,26 @@ class Edit(): pt = App.Vector(info["x"], info["y"], info["z"]) self.addPointToWire(self.obj, pt, int(info["Component"][4:])) elif Draft.getType(self.obj) in ["BSpline", "BezCurve"]: #to fix double vertex created - #pt = self.point + # pt = self.point if "x" in info:# prefer "real" 3D location over working-plane-driven one if possible pt = App.Vector(info["x"], info["y"], info["z"]) else: continue - self.addPointToCurve(pt,info) + self.addPointToCurve(pt, info) self.obj.recompute() self.removeTrackers(self.obj) self.setEditPoints(self.obj) - #self.setSelectState(self.obj, False) + # self.setSelectState(self.obj, False) return - def addPointToWire(self, obj, newPoint, edgeIndex): newPoints = [] hasAddedPoint = False if hasattr(obj, "ChamferSize") and hasattr(obj, "FilletRadius"): if obj.ChamferSize > 0 and obj.FilletRadius > 0: - edgeIndex = (edgeIndex +3) / 4 + edgeIndex = (edgeIndex + 3) / 4 elif obj.ChamferSize > 0 or obj.FilletRadius > 0: - edgeIndex = (edgeIndex +1) / 2 + edgeIndex = (edgeIndex + 1) / 2 for index, point in enumerate(self.obj.Points): if index == edgeIndex: @@ -882,24 +878,24 @@ class Edit(): newPoints.append(self.invpl.multVec(newPoint)) obj.Points = newPoints - def addPointToCurve(self,point,info=None): + def addPointToCurve(self, point, info=None): import Part - if not (Draft.getType(self.obj) in ["BSpline","BezCurve"]): + if not (Draft.getType(self.obj) in ["BSpline", "BezCurve"]): return pts = self.obj.Points if Draft.getType(self.obj) == "BezCurve": if not info['Component'].startswith('Edge'): - return # clicked control point - edgeindex = int(info['Component'].lstrip('Edge')) -1 + return # clicked control point + edgeindex = int(info['Component'].lstrip('Edge')) - 1 wire = self.obj.Shape.Wires[0] bz = wire.Edges[edgeindex].Curve param = bz.parameter(point) seg1 = wire.Edges[edgeindex].copy().Curve seg2 = wire.Edges[edgeindex].copy().Curve - seg1.segment(seg1.FirstParameter,param) - seg2.segment(param,seg2.LastParameter) + seg1.segment(seg1.FirstParameter, param) + seg2.segment(param, seg2.LastParameter) if edgeindex == len(wire.Edges): - #we hit the last segment, we need to fix the degree + # we hit the last segment, we need to fix the degree degree=wire.Edges[0].Curve.Degree seg1.increase(degree) seg2.increase(degree) @@ -925,68 +921,68 @@ class Edit(): uPoints = [] for p in self.obj.Points: uPoints.append(curve.parameter(p)) - for i in range(len(uPoints) -1): + for i in range(len(uPoints) - 1): if ( uNewPoint > uPoints[i] ) and ( uNewPoint < uPoints[i+1] ): pts.insert(i + 1, self.invpl.multVec(point)) break # DNC: fix: add points to last segment if curve is closed - if ( self.obj.Closed ) and ( uNewPoint > uPoints[-1] ) : + if self.obj.Closed and (uNewPoint > uPoints[-1]): pts.append(self.invpl.multVec(point)) self.obj.Points = pts - def delPoint(self,event): + def delPoint(self, event): pos = event.getPosition() node = self.getEditNode(pos) ep = self.getEditNodeIndex(node) if ep is None: return App.Console.PrintWarning(translate("draft", - "Node not found") - + "\n") + "Node not found") + + "\n") doc = App.getDocument(str(node.documentName.getValue())) self.obj = doc.getObject(str(node.objectName.getValue())) if self.obj is None: return - if not (Draft.getType(self.obj) in ["Wire","BSpline","BezCurve"]): + if not (Draft.getType(self.obj) in ["Wire", "BSpline", "BezCurve"]): return if len(self.obj.Points) <= 2: App.Console.PrintWarning(translate("draft", - "Active object must have more than two points/nodes") - + "\n") + "Active object must have more than two points/nodes") + + "\n") return pts = self.obj.Points pts.pop(ep) self.obj.Points = pts - if Draft.getType(self.obj) =="BezCurve": + if Draft.getType(self.obj) == "BezCurve": self.obj.Proxy.resetcontinuity(self.obj) self.obj.recompute() - + # don't do tan/sym on DWire/BSpline! self.removeTrackers(self.obj) self.setEditPoints(self.obj) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : GENERAL - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- - def setEditPoints(self,obj): - "append given object's editpoints to self.edipoints and set EditTrackers" - + def setEditPoints(self, obj): + """append given object's editpoints to self.edipoints and set EditTrackers""" self.setPlacement(obj) self.editpoints = self.getEditPoints(obj) - if self.editpoints: # set trackers and align plane + if self.editpoints: # set trackers and align plane self.setTrackers(obj, self.editpoints) self.editpoints = [] def getEditPoints(self, obj): - """ - (object) return a list of App.Vectors relative to object edit nodes + """Return a list of App.Vectors relative to object edit nodes. + + (object) """ objectType = Draft.getType(obj) - if objectType in ["Wire","BSpline"]: + if objectType in ["Wire", "BSpline"]: self.ui.editUi("Wire") return self.getWirePts(obj) elif objectType == "BezCurve": @@ -1003,7 +999,7 @@ class Edit(): elif objectType == "Dimension": return self.getDimensionPts(obj) elif objectType == "Wall": - return self.getWallPts(obj) + return self.getWallPts(obj) elif objectType == "Window": return self.getWindowPts(obj) elif objectType == "Space": @@ -1023,13 +1019,13 @@ class Edit(): else: return None - def update(self,obj, nodeIndex, v): - "apply the App.Vector to the modified point and update self.obj" + def update(self, obj, nodeIndex, v): + """Apply the App.Vector to the modified point and update self.obj.""" objectType = Draft.getType(obj) App.ActiveDocument.openTransaction("Edit") - if objectType in ["Wire","BSpline"]: + if objectType in ["Wire", "BSpline"]: self.updateWire(obj, nodeIndex, v) elif objectType == "BezCurve": self.updateWire(obj, nodeIndex, v) @@ -1059,7 +1055,6 @@ class Edit(): self.updatePartLine(obj, nodeIndex, v) elif objectType == "Part" and self.obj.TypeId == "Part::Box": self.updatePartBox(obj, nodeIndex, v) - obj.recompute() App.ActiveDocument.commitTransaction() @@ -1069,11 +1064,11 @@ class Edit(): except AttributeError as err: pass - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Line/Wire/Bspline/Bezcurve - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- - def getWirePts(self,obj): + def getWirePts(self, obj): editpoints = [] for p in obj.Points: p = obj.getGlobalPlacement().multVec(p) @@ -1102,7 +1097,7 @@ class Edit(): return if Draft.getType(obj) in ["BezCurve"]: pts = self.recomputePointsBezier(obj,pts,nodeIndex,v,obj.Degree,moveTrackers=False) - + if obj.Closed: # check that the new point lies on the plane of the wire if hasattr(obj.Shape,"normalAt"): @@ -1116,8 +1111,8 @@ class Edit(): obj.Points = pts self.trackers[obj.Name][nodeIndex].set(v) - - def recomputePointsBezier(self,obj,pts,idx,v,degree,moveTrackers=True): + def recomputePointsBezier(self, obj, pts, idx, v, + degree, moveTrackers=True): """ (object, Points as list, nodeIndex as Int, App.Vector of new point, moveTrackers as Bool) return the new point list, applying the App.Vector to the given index point @@ -1151,18 +1146,18 @@ class Edit(): elif ispole == 1 and (idx >=2 or obj.Closed): #right pole knot = idx -1 - changep = idx -2 # -1 in case of closed curve + changep = idx - 2 # -1 in case of closed curve - elif ispole == degree-1 and idx <= len(pts)-3: #left pole - knot = idx +1 - changep = idx +2 + elif ispole == degree-1 and idx <= len(pts)-3: # left pole + knot = idx + 1 + changep = idx + 2 elif ispole == degree-1 and obj.Closed and idx == len(pts)-1: #last pole knot = 0 changep = 1 - if knot is not None: # we need to modify the opposite pole - segment = int(knot / degree) -1 + if knot is not None: # we need to modify the opposite pole + segment = int(knot / degree) - 1 cont = obj.Continuity[segment] if len(obj.Continuity) > segment else 0 if cont == 1: #tangent pts[changep] = obj.Proxy.modifytangentpole(pts[knot], @@ -1173,12 +1168,12 @@ class Edit(): pts[changep] = obj.Proxy.modifysymmetricpole(pts[knot],editPnt) if moveTrackers: self.trackers[obj.Name][changep].set(pts[changep]) - pts[idx]=v + pts[idx] = v - return pts #returns the list of new points, taking into account knot continuity + return pts # returns the list of new points, taking into account knot continuity def resetTrackersBezier(self, obj): - #in future move tracker definition to DraftTrackers + # in future move tracker definition to DraftTrackers from pivy import coin knotmarkers = (coin.SoMarkerSet.DIAMOND_FILLED_9_9,#sharp coin.SoMarkerSet.SQUARE_FILLED_9_9, #tangent @@ -1196,9 +1191,9 @@ class Edit(): knotmarkeri = cont[edgeindex] if len(cont) > edgeindex else 0 pointswithmarkers.append((poles[-1],knotmarkers[knotmarkeri])) for index, pwm in enumerate(pointswithmarkers): - p,marker = pwm - #if self.pl: p = self.pl.multVec(p) - self.trackers[obj.Name].append(editTracker(p,obj.Name, + p, marker = pwm + # if self.pl: p = self.pl.multVec(p) + self.trackers[obj.Name].append(trackers.editTracker(p,obj.Name, index,obj.ViewObject.LineColor,marker=marker)) def smoothBezPoint(self, obj, point, style='Symmetric'): @@ -1212,8 +1207,8 @@ class Edit(): deg = obj.Degree if deg < 2: return - if point % deg != 0: #point is a pole - if deg >=3: #allow to select poles + if point % deg != 0: # point is a pole + if deg >=3: # allow to select poles if (point % deg == 1) and (point > 2 or obj.Closed): #right pole knot = point -1 keepp = point @@ -1229,9 +1224,9 @@ class Edit(): keepp = point changep = 1 else: - App.Console.PrintWarning(translate("draft", - "Can't change Knot belonging to pole %d"%point) - + "\n") + App.Console.PrintWarning(translate("draft", + "Can't change Knot belonging to pole %d"%point) + + "\n") return if knot: if style == 'Tangent': @@ -1243,9 +1238,9 @@ class Edit(): else: #sharp pass # else: - App.Console.PrintWarning(translate("draft", - "Selection is not a Knot") - + "\n") + App.Console.PrintWarning(translate("draft", + "Selection is not a Knot") + + "\n") return else: #point is a knot if style == 'Sharp': @@ -1257,12 +1252,12 @@ class Edit(): prev, next = obj.Proxy.tangentpoles(pts[point], pts[point-1], pts[point+1]) pts[point-1] = prev pts[point+1] = next - knot = point #index for continuity + knot = point # index for continuity elif style == 'Symmetric' and point > 0 and point < len(pts)-1: prev, next = obj.Proxy.symmetricpoles(pts[point], pts[point-1], pts[point+1]) pts[point-1] = prev pts[point+1] = next - knot = point #index for continuity + knot = point # index for continuity elif obj.Closed and (style == 'Symmetric' or style == 'Tangent'): if style == 'Tangent': pts[1], pts[-1] = obj.Proxy.tangentpoles(pts[0], pts[1], pts[-1]) @@ -1274,8 +1269,8 @@ class Edit(): "Endpoint of BezCurve can't be smoothed") + "\n") return - segment = knot // deg #segment index - newcont = obj.Continuity[:] #don't edit a property inplace !!! + segment = knot // deg # segment index + newcont = obj.Continuity[:] # don't edit a property inplace !!! if not obj.Closed and (len(obj.Continuity) == segment -1 or segment == 0) : pass # open curve @@ -1284,19 +1279,19 @@ class Edit(): newcont[segment-1] = style2cont.get(style) else: #should not happen App.Console.PrintWarning('Continuity indexing error:' - + 'point:%d deg:%d len(cont):%d' % (knot,deg, + + 'point:%d deg:%d len(cont):%d' % (knot,deg, len(obj.Continuity))) obj.Points = pts obj.Continuity = newcont self.resetTrackers(obj) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Rectangle - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def getRectanglePts(self, obj): - """ - returns the list of edipoints for the given Draft Rectangle + """Return the list of edipoints for the given Draft Rectangle. + 0 : Placement.Base 1 : Length 2 : Height @@ -1316,8 +1311,8 @@ class Edit(): import DraftVecUtils delta = obj.getGlobalPlacement().inverse().multVec(v) if nodeIndex == 0: - #p = obj.getGlobalPlacement() - #p.move(delta) + # p = obj.getGlobalPlacement() + # p.move(delta) obj.Placement.move(delta) elif self.editing == 1: obj.Length = DraftVecUtils.project(delta,App.Vector(1,0,0)).Length @@ -1325,9 +1320,9 @@ class Edit(): obj.Height = DraftVecUtils.project(delta,App.Vector(0,1,0)).Length self.updateRectangleTrackers(obj) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Ellipse (# TODO: yet to be implemented) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def setEllipsePts(self): return @@ -1335,22 +1330,23 @@ class Edit(): def updateEllipse(self,v): return - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Circle/Arc - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def getCirclePts(self, obj): - """ - returns the list of edipoints for the given Draft Arc or Circle + """Return the list of edipoints for the given Draft Arc or Circle. + circle: 0 : Placement.Base or center 1 : radius + arc: 0 : Placement.Base or center 1 : first endpoint 2 : second endpoint 3 : midpoint - """ + """ editpoints = [] editpoints.append(obj.getGlobalPlacement().Base) if obj.FirstAngle == obj.LastAngle: @@ -1390,7 +1386,7 @@ class Edit(): # edit arc by 3 points import Part if nodeIndex == 0: - #center point + # center point import DraftVecUtils p1 = self.getArcStart(obj) p2 = self.getArcEnd(obj) @@ -1402,15 +1398,15 @@ class Edit(): self.setPlacement(obj) else: - if nodeIndex == 1:#first point + if nodeIndex == 1: # first point p1=v p2=self.getArcMid(obj,global_placement=True) p3=self.getArcEnd(obj,global_placement=True) - elif nodeIndex == 3:#midpoint + elif nodeIndex == 3: # midpoint p1=self.getArcStart(obj,global_placement=True) p2=v p3=self.getArcEnd(obj,global_placement=True) - elif nodeIndex == 2:#second point + elif nodeIndex == 2: # second point p1=self.getArcStart(obj,global_placement=True) p2=self.getArcMid(obj,global_placement=True) p3=v @@ -1444,7 +1440,7 @@ class Edit(): def getArcStart(self, obj, global_placement=False):#Returns object midpoint if Draft.getType(obj) == "Circle": return self.pointOnCircle(obj, obj.FirstAngle, global_placement) - + def getArcEnd(self, obj, global_placement=False):#Returns object midpoint if Draft.getType(obj) == "Circle": return self.pointOnCircle(obj, obj.LastAngle, global_placement) @@ -1473,9 +1469,9 @@ class Edit(): obj.recompute() self.updateCircleTrackers(obj) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Polygon (maybe could also rotate the polygon) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def getPolygonPts(self, obj): editpoints = [] @@ -1500,9 +1496,9 @@ class Edit(): self.obj.recompute() self.trackers[self.obj.Name][1].set(self.obj.Shape.Vertexes[0].Point) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Dimension (point on dimension line is not clickable) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def getDimensionPts(self, obj): editpoints = [] @@ -1510,7 +1506,7 @@ class Edit(): editpoints.append(obj.Start) editpoints.append(obj.End) editpoints.append(obj.Dimline) - editpoints.append(App.Vector(p[0],p[1],p[2])) + editpoints.append(App.Vector(p[0], p[1], p[2])) return editpoints def updateDimension(self, obj, nodeIndex, v): @@ -1523,15 +1519,16 @@ class Edit(): elif self.editing == 3: self.obj.ViewObject.TextPosition = v - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : ARCH Wall, Windows, Structure, Panel, etc. - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- - # SKETCH: just if it's composed by a single segment------------------------- + # SKETCH: just if it's composed by a single segment----------------------- def getSketchPts(self, obj): - """ - returns the list of edipoints for the given single line sketch (WallTrace) + """Return the list of edipoints for the given single line sketch. + + (WallTrace) 0 : startpoint 1 : endpoint """ @@ -1542,13 +1539,14 @@ class Edit(): return editpoints else: App.Console.PrintWarning(translate("draft", - "Sketch is too complex to edit: " - "it is suggested to use sketcher default editor") + "Sketch is too complex to edit: " + "it is suggested to use sketcher default editor") + "\n") return None def updateSketch(self, obj, nodeIndex, v): - """ + """Move a single line sketch vertex a certain displacement. + (single segment sketch object, node index as Int, App.Vector) move a single line sketch (WallTrace) vertex according to a given App.Vector 0 : startpoint @@ -1560,17 +1558,16 @@ class Edit(): obj.movePoint(0,2,obj.getGlobalPlacement().inverse().multVec(v)) obj.recompute() - - #WALL----------------------------------------------------------------------- + # WALL--------------------------------------------------------------------- def getWallPts(self, obj): - """ - returns the list of edipoints for the given Arch Wall object + """Return the list of edipoints for the given Arch Wall object. + 0 : height of the wall 1-to end : base object editpoints, in place with the wall """ editpoints = [] - #height of the wall + # height of the wall editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(0,0,obj.Height))) # try to add here an editpoint based on wall height (maybe should be good to associate it with a circular tracker) if obj.Base: @@ -1583,30 +1580,26 @@ class Edit(): editpoints.append(obj.Placement.multVec(point)) #works ok except if App::Part is rotated... why? return editpoints - def updateWallTrackers(self, obj): - """ - update self.trackers[obj.Name][0] to match with given object - """ + """Update self.trackers[obj.Name][0] to match with given object.""" pass def updateWall(self, obj, nodeIndex, v): import DraftVecUtils if nodeIndex == 0: delta= obj.getGlobalPlacement().inverse().multVec(v) - vz=DraftVecUtils.project(delta,App.Vector(0,0,1)) + vz=DraftVecUtils.project(delta,App.Vector(0, 0, 1)) if vz.Length > 0: obj.Height = vz.Length elif nodeIndex > 0: if obj.Base: - if Draft.getType(obj.Base) in ["Wire","Circle","Rectangle", - "Polygon", "Sketch"]: + if Draft.getType(obj.Base) in ["Wire", "Circle", "Rectangle", + "Polygon", "Sketch"]: self.update(obj.Base, nodeIndex - 1, obj.Placement.inverse().multVec(v)) obj.recompute() - - #WINDOW--------------------------------------------------------------------- + # WINDOW------------------------------------------------------------------- def getWindowPts(self, obj): import DraftGeomUtils @@ -1623,9 +1616,9 @@ class Edit(): return editpoints def updateWindow(self, obj, nodeIndex, v): - pos=self.obj.Base.Placement.Base + pos = self.obj.Base.Placement.Base if self.editing == 0: - self.obj.Base.Placement.Base=v + self.obj.Base.Placement.Base = v self.obj.Base.recompute() if self.editing == 1: self.obj.Width = pos.sub(v).Length @@ -1637,7 +1630,7 @@ class Edit(): obj.recompute() self.obj.recompute() - #STRUCTURE------------------------------------------------------------------- + # STRUCTURE---------------------------------------------------------------- def getStructurePts(self, obj): if obj.Nodes: @@ -1647,7 +1640,7 @@ class Edit(): self.originalNodes = obj.ViewObject.ShowNodes self.obj.ViewObject.DisplayMode = "Wireframe" self.obj.ViewObject.NodeSize = 1 - # self.obj.ViewObject.ShowNodes = True + # self.obj.ViewObject.ShowNodes = True for p in obj.Nodes: if self.pl: p = self.pl.multVec(p) @@ -1661,7 +1654,7 @@ class Edit(): nodes[self.editing] = self.invpl.multVec(v) self.obj.Nodes = nodes - #SPACE---------------------------------------------------------------------- + # SPACE-------------------------------------------------------------------- def getSpacePts(self, obj): try: @@ -1675,7 +1668,7 @@ class Edit(): if self.editing == 0: self.obj.ViewObject.TextPosition = v - #PANELS--------------------------------------------------------------------- + # PANELS------------------------------------------------------------------- def getPanelCutPts(self, obj): editpoints = [] @@ -1703,7 +1696,7 @@ class Edit(): else: self.obj.Group[self.editing-1].Placement.Base = self.invpl.multVec(v) - # PART::LINE---------------------------------------------------------------- + # PART::LINE-------------------------------------------------------------- def getPartLinePts(self, obj): editpoints = [] @@ -1722,14 +1715,14 @@ class Edit(): self.obj.Y2 = pt.y self.obj.Z2 = pt.z - # PART::BOX----------------------------------------------------------------- + # PART::BOX--------------------------------------------------------------- def getPartBoxPts(self, obj): editpoints = [] editpoints.append(obj.Placement.Base) - editpoints.append(self.pl.multVec(App.Vector(obj.Length,0,0))) - editpoints.append(self.pl.multVec(App.Vector(0,obj.Width,0))) - editpoints.append(self.pl.multVec(App.Vector(0,0,obj.Height))) + editpoints.append(self.pl.multVec(App.Vector(obj.Length, 0, 0))) + editpoints.append(self.pl.multVec(App.Vector(0, obj.Width, 0))) + editpoints.append(self.pl.multVec(App.Vector(0, 0, obj.Height))) return editpoints def updatePartBox(self, obj, nodeIndex, v): @@ -1739,22 +1732,22 @@ class Edit(): self.obj.Placement.Base = v self.setPlacement(self.obj) elif self.editing == 1: - xApp.Vector = DraftVecUtils.project(delta,App.Vector(1,0,0)) - self.obj.Length = xApp.Vector.Length + xApp.Vector = DraftVecUtils.project(delta, App.Vector(1, 0, 0)) + self.obj.Length = xApp.Vector.Length elif self.editing == 2: - xApp.Vector = DraftVecUtils.project(delta,App.Vector(0,1,0)) - self.obj.Width = xApp.Vector.Length + xApp.Vector = DraftVecUtils.project(delta, App.Vector(0, 1, 0)) + self.obj.Width = xApp.Vector.Length elif self.editing == 3: - xApp.Vector = DraftVecUtils.project(delta,App.Vector(0,0,1)) - self.obj.Height = xApp.Vector.Length + xApp.Vector = DraftVecUtils.project(delta, App.Vector(0, 0, 1)) + self.obj.Height = xApp.Vector.Length self.trackers[self.obj.Name][0].set(self.obj.Placement.Base) self.trackers[self.obj.Name][1].set(self.pl.multVec(App.Vector(self.obj.Length,0,0))) self.trackers[self.obj.Name][2].set(self.pl.multVec(App.Vector(0,self.obj.Width,0))) self.trackers[self.obj.Name][3].set(self.pl.multVec(App.Vector(0,0,self.obj.Height))) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------ # Context menu - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------ def display_tracker_menu(self, event): self.tracker_menu = QtGui.QMenu() @@ -1769,23 +1762,24 @@ class Edit(): actions = ["delete point"] elif Draft.getType(obj) in ["Circle"]: if obj.FirstAngle != obj.LastAngle: - if ep == 0: # user is over arc start point + if ep == 0: # user is over arc start point actions = ["move arc"] - elif ep == 1: # user is over arc start point + elif ep == 1: # user is over arc start point actions = ["set first angle"] - elif ep == 2: # user is over arc end point + elif ep == 2: # user is over arc end point actions = ["set last angle"] - elif ep == 3: # user is over arc mid point + elif ep == 3: # user is over arc mid point actions = ["set radius"] elif Draft.getType(obj) in ["BezCurve"]: - actions = ["make sharp", "make tangent", "make symmetric", "delete point"] + actions = ["make sharp", "make tangent", + "make symmetric", "delete point"] else: return else: # if user is over an edited object pos = self.event.getPosition() obj = self.get_selected_obj_at_position(pos) - if Draft.getType(obj) in ["Line", "Wire","BSpline", "BezCurve"]: + if Draft.getType(obj) in ["Line", "Wire", "BSpline", "BezCurve"]: actions = ["add point"] elif Draft.getType(obj) in ["Circle"] and obj.FirstAngle != obj.LastAngle: actions = ["invert arc"] @@ -1796,7 +1790,7 @@ class Edit(): self.tracker_menu.popup(Gui.getMainWindow().cursor().pos()) QtCore.QObject.connect(self.tracker_menu,QtCore.SIGNAL("triggered(QAction *)"),self.evaluate_menu_action) - def evaluate_menu_action(self,labelname): + def evaluate_menu_action(self, labelname): action_label = str(labelname.text()) # Bezier curve menu if action_label in ["make sharp", "make tangent", "make symmetric"]: @@ -1815,7 +1809,8 @@ class Edit(): elif action_label == "add point": self.addPoint(self.event) # arc tools - elif action_label in ["move arc","set radius", "set first angle", "set last angle"]: + elif action_label in ("move arc", "set radius", + "set first angle", "set last angle"): self.alt_edit_mode = 1 self.startEditing(self.event) elif action_label == "invert arc": @@ -1825,7 +1820,4 @@ class Edit(): del self.event - -if App.GuiUp: - # setup command - Gui.addCommand('Draft_Edit', Edit()) +Gui.addCommand('Draft_Edit', Edit()) diff --git a/src/Mod/Draft/draftguitools/gui_orthoarray.py b/src/Mod/Draft/draftguitools/gui_orthoarray.py index b7c0551f3b..07f8537dd7 100644 --- a/src/Mod/Draft/draftguitools/gui_orthoarray.py +++ b/src/Mod/Draft/draftguitools/gui_orthoarray.py @@ -1,8 +1,3 @@ -"""Provide the Draft OrthoArray tool.""" -## @package gui_orthoarray -# \ingroup DRAFT -# \brief Provide the Draft OrthoArray tool. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -25,37 +20,26 @@ # * USA * # * * # *************************************************************************** +"""Provides the Draft OrthoArray GuiCommand.""" +## @package gui_orthoarray +# \ingroup DRAFT +# \brief Provides the Draft OrthoArray GuiCommand. + +from pivy import coin +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui import Draft -import DraftGui -import Draft_rc -from . import gui_base +import Draft_rc # include resources, icons, ui files +from draftutils.messages import _msg, _log +from draftutils.translate import _tr +from draftguitools import gui_base from drafttaskpanels import task_orthoarray +import draftutils.todo as todo - -if App.GuiUp: - from PySide.QtCore import QT_TRANSLATE_NOOP - # import DraftTools - from draftutils.translate import translate - # from DraftGui import displayExternal - from pivy import coin -else: - def QT_TRANSLATE_NOOP(context, text): - return text - - def translate(context, text): - return text - - -def _tr(text): - """Translate the text with the context set.""" - return translate("Draft", text) - - -# So the resource file doesn't trigger errors from code checkers (flake8) -True if Draft_rc.__name__ else False +# The module is used to prevent complaints from code checkers (flake8) +bool(Draft_rc.__name__) class GuiCommandOrthoArray(gui_base.GuiCommandBase): @@ -63,7 +47,7 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase): def __init__(self): super().__init__() - self.command_name = "OrthoArray" + self.command_name = "Orthogonal array" # self.location = None self.mouse_event = None self.view = None @@ -74,22 +58,27 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase): def GetResources(self): """Set icon, menu and tooltip.""" - _msg = ("Creates copies of a selected object, " + _tip = ("Creates copies of a selected object, " "and places the copies in an orthogonal pattern.\n" "The properties of the array can be further modified after " "the new object is created, including turning it into " "a different type of array.") + d = {'Pixmap': 'Draft_Array', 'MenuText': QT_TRANSLATE_NOOP("Draft", "Array"), - 'ToolTip': QT_TRANSLATE_NOOP("Draft", _msg)} + 'ToolTip': QT_TRANSLATE_NOOP("Draft", _tip)} return d def Activated(self): - """Execute this when the command is called. + """Execute when the command is called. We add callbacks that connect the 3D view with the widgets of the task panel. """ + _log("GuiCommand: {}".format(_tr(self.command_name))) + _msg("{}".format(16*"-")) + _msg("GuiCommand: {}".format(_tr(self.command_name))) + # self.location = coin.SoLocation2Event.getClassTypeId() self.mouse_event = coin.SoMouseButtonEvent.getClassTypeId() self.view = Draft.get3DView() @@ -103,10 +92,10 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase): # of the interface, to be able to call a function from within it. self.ui.source_command = self # Gui.Control.showDialog(self.ui) - DraftGui.todo.delay(Gui.Control.showDialog, self.ui) + todo.ToDo.delay(Gui.Control.showDialog, self.ui) def click(self, event_cb=None): - """Run callback for when the mouse pointer clicks on the 3D view. + """Execute as a callback when the pointer clicks on the 3D view. It should act as if the Enter key was pressed, or the OK button was pressed in the task panel. @@ -123,7 +112,7 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase): self.ui.accept() def completed(self): - """Run when the command is terminated. + """Execute when the command is terminated. We should remove the callbacks that were added to the 3D view and then close the task panel. @@ -133,10 +122,8 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase): self.view.removeEventCallbackPivy(self.mouse_event, self.callback_click) if Gui.Control.activeDialog(): - Gui.Snapper.off() Gui.Control.closeDialog() super().finish() -if App.GuiUp: - Gui.addCommand('Draft_OrthoArray', GuiCommandOrthoArray()) +Gui.addCommand('Draft_OrthoArray', GuiCommandOrthoArray()) diff --git a/src/Mod/Draft/draftguitools/gui_planeproxy.py b/src/Mod/Draft/draftguitools/gui_planeproxy.py new file mode 100644 index 0000000000..270dd04032 --- /dev/null +++ b/src/Mod/Draft/draftguitools/gui_planeproxy.py @@ -0,0 +1,79 @@ +# *************************************************************************** +# * Copyright (c) 2019 Yorik van Havre * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides the Draft WorkingPlaneProxy tool.""" +## @package gui_planeproxy +# \ingroup DRAFT +# \brief This module provides the Draft WorkingPlaneProxy tool. + +from PySide.QtCore import QT_TRANSLATE_NOOP + +import FreeCAD as App +import FreeCADGui as Gui +import Draft_rc + +# The module is used to prevent complaints from code checkers (flake8) +True if Draft_rc.__name__ else False + +__title__ = "FreeCAD Draft Workbench GUI Tools - Working plane-related tools" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin") +__url__ = "https://www.freecadweb.org" + + +class Draft_WorkingPlaneProxy: + """The Draft_WorkingPlaneProxy command definition.""" + + def GetResources(self): + """Set icon, menu and tooltip.""" + _menu = "Create working plane proxy" + _tip = ("Creates a proxy object from the current working plane.\n" + "Once the object is created double click it in the tree view " + "to restore the camera position and objects' visibilities.\n" + "Then you can use it to save a different camera position " + "and objects' states any time you need.") + d = {'Pixmap': 'Draft_PlaneProxy', + 'MenuText': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", + _menu), + 'ToolTip': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", + _tip)} + return d + + def IsActive(self): + """Return True when this command should be available.""" + if Gui.ActiveDocument: + return True + else: + return False + + def Activated(self): + """Execute when the command is called.""" + if hasattr(App, "DraftWorkingPlane"): + App.ActiveDocument.openTransaction("Create WP proxy") + Gui.addModule("Draft") + _cmd = "Draft.makeWorkingPlaneProxy(" + _cmd += "FreeCAD.DraftWorkingPlane.getPlacement()" + _cmd += ")" + Gui.doCommand(_cmd) + App.ActiveDocument.commitTransaction() + App.ActiveDocument.recompute() + + +Gui.addCommand('Draft_WorkingPlaneProxy', Draft_WorkingPlaneProxy()) diff --git a/src/Mod/Draft/draftguitools/gui_polararray.py b/src/Mod/Draft/draftguitools/gui_polararray.py index e07a3b0742..f31ed2bf01 100644 --- a/src/Mod/Draft/draftguitools/gui_polararray.py +++ b/src/Mod/Draft/draftguitools/gui_polararray.py @@ -1,9 +1,3 @@ -"""This module provides the Draft PolarArray tool. -""" -## @package gui_polararray -# \ingroup DRAFT -# \brief This module provides the Draft PolarArray tool. - # *************************************************************************** # * (c) 2019 Eliud Cabrera Castillo * # * * @@ -26,45 +20,34 @@ # * USA * # * * # *************************************************************************** +"""Provides the Draft PolarArray GuiCommand.""" +## @package gui_polararray +# \ingroup DRAFT +# \brief This module provides the Draft PolarArray tool. + +from pivy import coin +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui import Draft -import DraftGui -import Draft_rc -from . import gui_base +import Draft_rc # include resources, icons, ui files +from draftutils.messages import _msg, _log +from draftutils.translate import _tr +from draftguitools import gui_base from drafttaskpanels import task_polararray +import draftutils.todo as todo - -if App.GuiUp: - from PySide.QtCore import QT_TRANSLATE_NOOP - # import DraftTools - from DraftGui import translate - # from DraftGui import displayExternal - from pivy import coin -else: - def QT_TRANSLATE_NOOP(context, text): - return text - - def translate(context, text): - return text - - -def _tr(text): - """Function to translate with the context set""" - return translate("Draft", text) - - -# So the resource file doesn't trigger errors from code checkers (flake8) -True if Draft_rc.__name__ else False +# The module is used to prevent complaints from code checkers (flake8) +bool(Draft_rc.__name__) class GuiCommandPolarArray(gui_base.GuiCommandBase): - """Gui command for the PolarArray tool""" + """Gui command for the PolarArray tool.""" def __init__(self): super().__init__() - self.command_name = "PolarArray" + self.command_name = "Polar array" self.location = None self.mouse_event = None self.view = None @@ -74,22 +57,28 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): self.point = App.Vector() def GetResources(self): - _msg = ("Creates copies of a selected object, " + """Set icon, menu and tooltip.""" + _tip = ("Creates copies of a selected object, " "and places the copies in a polar pattern.\n" "The properties of the array can be further modified after " "the new object is created, including turning it into " "a different type of array.") + d = {'Pixmap': 'Draft_PolarArray', 'MenuText': QT_TRANSLATE_NOOP("Draft", "Polar array"), - 'ToolTip': QT_TRANSLATE_NOOP("Draft", _msg)} + 'ToolTip': QT_TRANSLATE_NOOP("Draft", _tip)} return d def Activated(self): - """This is called when the command is executed. + """Execute when the command is called. We add callbacks that connect the 3D view with the widgets of the task panel. """ + _log("GuiCommand: {}".format(_tr(self.command_name))) + _msg("{}".format(16*"-")) + _msg("GuiCommand: {}".format(_tr(self.command_name))) + self.location = coin.SoLocation2Event.getClassTypeId() self.mouse_event = coin.SoMouseButtonEvent.getClassTypeId() self.view = Draft.get3DView() @@ -103,10 +92,10 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): # of the interface, to be able to call a function from within it. self.ui.source_command = self # Gui.Control.showDialog(self.ui) - DraftGui.todo.delay(Gui.Control.showDialog, self.ui) + todo.ToDo.delay(Gui.Control.showDialog, self.ui) def move(self, event_cb): - """This is a callback for when the mouse pointer moves in the 3D view. + """Execute as a callback when the pointer moves in the 3D view. It should automatically update the coordinates in the widgets of the task panel. @@ -119,7 +108,7 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): self.ui.display_point(self.point) def click(self, event_cb=None): - """This is a callback for when the mouse pointer clicks on the 3D view. + """Execute as a callback when the pointer clicks on the 3D view. It should act as if the Enter key was pressed, or the OK button was pressed in the task panel. @@ -136,7 +125,7 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): self.ui.accept() def completed(self): - """This is called when the command is terminated. + """Execute when the command is terminated. We should remove the callbacks that were added to the 3D view and then close the task panel. @@ -146,10 +135,8 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): self.view.removeEventCallbackPivy(self.mouse_event, self.callback_click) if Gui.Control.activeDialog(): - Gui.Snapper.off() Gui.Control.closeDialog() super().finish() -if App.GuiUp: - Gui.addCommand('Draft_PolarArray', GuiCommandPolarArray()) +Gui.addCommand('Draft_PolarArray', GuiCommandPolarArray()) diff --git a/src/Mod/Draft/DraftSelectPlane.py b/src/Mod/Draft/draftguitools/gui_selectplane.py similarity index 57% rename from src/Mod/Draft/DraftSelectPlane.py rename to src/Mod/Draft/draftguitools/gui_selectplane.py index 72fa0988f0..60834847cd 100644 --- a/src/Mod/Draft/DraftSelectPlane.py +++ b/src/Mod/Draft/draftguitools/gui_selectplane.py @@ -1,94 +1,107 @@ -# -*- coding: utf8 -*- -#*************************************************************************** -#* Copyright (c) 2019 Yorik van Havre * -#* * -#* This program is free software; you can redistribute it and/or modify * -#* it under the terms of the GNU Lesser General Public License (LGPL) * -#* as published by the Free Software Foundation; either version 2 of * -#* the License, or (at your option) any later version. * -#* for detail see the LICENCE text file. * -#* * -#* This program 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 program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** - -__title__="FreeCAD Draft Workbench GUI Tools - Working plane-related tools" -__author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, Dmitry Chigrin" -__url__ = "https://www.freecadweb.org" +# *************************************************************************** +# * Copyright (c) 2019 Yorik van Havre * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides the Draft SelectPlane tool.""" +## @package gui_selectplane +# \ingroup DRAFT +# \brief This module provides the Draft SelectPlane tool. +import math +from pivy import coin +from PySide import QtGui +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui -import math import Draft +import Draft_rc import DraftVecUtils -from DraftGui import todo, translate +import drafttaskpanels.task_selectplane as task_selectplane +from draftutils.todo import todo +from draftutils.messages import _msg +from draftutils.translate import translate -def QT_TRANSLATE_NOOP(ctx,txt): return txt +# The module is used to prevent complaints from code checkers (flake8) +True if Draft_rc.__name__ else False +__title__ = "FreeCAD Draft Workbench GUI Tools - Working plane-related tools" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin") +__url__ = "https://www.freecadweb.org" class Draft_SelectPlane: - - """The Draft_SelectPlane FreeCAD command definition""" + """The Draft_SelectPlane FreeCAD command definition.""" def __init__(self): - self.ac = "FreeCAD.DraftWorkingPlane.alignToPointAndAxis" self.param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") self.states = [] def GetResources(self): - - return {'Pixmap' : 'Draft_SelectPlane', - 'Accel' : "W, P", - 'MenuText': QT_TRANSLATE_NOOP("Draft_SelectPlane", "SelectPlane"), - 'ToolTip' : QT_TRANSLATE_NOOP("Draft_SelectPlane", "Select a working plane for geometry creation")} + """Set icon, menu and tooltip.""" + _msg = ("Select the face of solid body to create a working plane " + "on which to sketch Draft objects.\n" + "You may also select a three vertices or " + "a Working Plane Proxy.") + d = {'Pixmap': 'Draft_SelectPlane', + 'Accel': "W, P", + 'MenuText': QT_TRANSLATE_NOOP("Draft_SelectPlane", "SelectPlane"), + 'ToolTip': QT_TRANSLATE_NOOP("Draft_SelectPlane", _msg)} + return d def IsActive(self): - + """Return True when this command should be available.""" if FreeCADGui.ActiveDocument: return True else: return False def Activated(self): - - # reset variables + """Execute when the command is called.""" + # Reset variables self.view = Draft.get3DView() self.wpButton = FreeCADGui.draftToolBar.wplabel FreeCAD.DraftWorkingPlane.setup() - - # write current WP if states are empty + + # Write current WP if states are empty if not self.states: p = FreeCAD.DraftWorkingPlane self.states.append([p.u, p.v, p.axis, p.position]) - m = translate("draft", "Pick a face, 3 vertices or a WP Proxy to define the drawing plane") - FreeCAD.Console.PrintMessage(m+"\n") + m = translate("draft", "Pick a face, 3 vertices " + "or a WP Proxy to define the drawing plane") + _msg(m) - from PySide import QtCore,QtGui - - # create UI panel + # Create task panel FreeCADGui.Control.closeDialog() - self.taskd = SelectPlane_TaskPanel() + self.taskd = task_selectplane.SelectPlaneTaskPanel() - # fill values - self.taskd.form.checkCenter.setChecked(self.param.GetBool("CenterPlaneOnView",False)) - q = FreeCAD.Units.Quantity(self.param.GetFloat("gridSpacing",1.0),FreeCAD.Units.Length) + # Fill values + self.taskd.form.checkCenter.setChecked(self.param.GetBool("CenterPlaneOnView", False)) + q = FreeCAD.Units.Quantity(self.param.GetFloat("gridSpacing", 1.0), FreeCAD.Units.Length) self.taskd.form.fieldGridSpacing.setText(q.UserString) - self.taskd.form.fieldGridMainLine.setValue(self.param.GetInt("gridEvery",10)) - self.taskd.form.fieldSnapRadius.setValue(self.param.GetInt("snapRange",8)) + self.taskd.form.fieldGridMainLine.setValue(self.param.GetInt("gridEvery", 10)) + self.taskd.form.fieldSnapRadius.setValue(self.param.GetInt("snapRange", 8)) - # set icons + # Set icons self.taskd.form.setWindowIcon(QtGui.QIcon(":/icons/Draft_SelectPlane.svg")) self.taskd.form.buttonTop.setIcon(QtGui.QIcon(":/icons/view-top.svg")) self.taskd.form.buttonFront.setIcon(QtGui.QIcon(":/icons/view-front.svg")) @@ -99,7 +112,7 @@ class Draft_SelectPlane: self.taskd.form.buttonCenter.setIcon(QtGui.QIcon(":/icons/view-fullscreen.svg")) self.taskd.form.buttonPrevious.setIcon(QtGui.QIcon(":/icons/edit-undo.svg")) - # connect slots + # Connect slots self.taskd.form.buttonTop.clicked.connect(self.onClickTop) self.taskd.form.buttonFront.clicked.connect(self.onClickFront) self.taskd.form.buttonSide.clicked.connect(self.onClickSide) @@ -112,64 +125,64 @@ class Draft_SelectPlane: self.taskd.form.fieldGridMainLine.valueChanged.connect(self.onSetMainline) self.taskd.form.fieldSnapRadius.valueChanged.connect(self.onSetSnapRadius) - # try to find a WP from the current selection + # Try to find a WP from the current selection if self.handle(): return - # try other method + # Try another method if FreeCAD.DraftWorkingPlane.alignToSelection(): FreeCADGui.Selection.clearSelection() self.display(FreeCAD.DraftWorkingPlane.axis) self.finish() return - - # rock 'n roll! + + # Execute the actual task panel FreeCADGui.Control.showDialog(self.taskd) self.call = self.view.addEventCallback("SoEvent", self.action) - def finish(self,close=False): + def finish(self, close=False): + """Execute when the command is terminated.""" + # Store values + self.param.SetBool("CenterPlaneOnView", + self.taskd.form.checkCenter.isChecked()) - # store values - self.param.SetBool("CenterPlaneOnView",self.taskd.form.checkCenter.isChecked()) - - # terminate coin callbacks + # Terminate coin callbacks if self.call: try: - self.view.removeEventCallback("SoEvent",self.call) + self.view.removeEventCallback("SoEvent", self.call) except RuntimeError: - # the view has been deleted already + # The view has been deleted already pass self.call = None - # reset everything else + # Reset everything else FreeCADGui.Control.closeDialog() FreeCAD.DraftWorkingPlane.restore() FreeCADGui.ActiveDocument.resetEdit() return True def reject(self): - + """Execute when clicking the Cancel button.""" self.finish() return True def action(self, arg): - + """Set the callbacks for the view.""" if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE": self.finish() if arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): - # coin detection happens before the selection got a chance of being updated, so we must delay - todo.delay(self.checkSelection,None) + # Coin detection happens before the selection + # got a chance of being updated, so we must delay + todo.delay(self.checkSelection, None) def checkSelection(self): - + """Check the selection, if there is a handle, finish the command.""" if self.handle(): self.finish() def handle(self): - - """tries to build a WP. Returns True if successful""" - + """Build a working plane. Return True if successful.""" sel = FreeCADGui.Selection.getSelectionEx() if len(sel) == 1: sel = sel[0] @@ -177,18 +190,19 @@ class Draft_SelectPlane: FreeCAD.DraftWorkingPlane.alignToEdges(sel.Object.Shape.Edges) self.display(FreeCAD.DraftWorkingPlane.axis) return True - elif Draft.getType(sel.Object) in ["WorkingPlaneProxy","BuildingPart"]: - FreeCAD.DraftWorkingPlane.setFromPlacement(sel.Object.Placement,rebase=True) + elif Draft.getType(sel.Object) in ("WorkingPlaneProxy", + "BuildingPart"): + FreeCAD.DraftWorkingPlane.setFromPlacement(sel.Object.Placement, rebase=True) FreeCAD.DraftWorkingPlane.weak = False - if hasattr(sel.Object.ViewObject,"AutoWorkingPlane"): + if hasattr(sel.Object.ViewObject, "AutoWorkingPlane"): if sel.Object.ViewObject.AutoWorkingPlane: FreeCAD.DraftWorkingPlane.weak = True - if hasattr(sel.Object.ViewObject,"CutView") and hasattr(sel.Object.ViewObject,"AutoCutView"): + if hasattr(sel.Object.ViewObject, "CutView") and hasattr(sel.Object.ViewObject, "AutoCutView"): if sel.Object.ViewObject.AutoCutView: sel.Object.ViewObject.CutView = True - if hasattr(sel.Object.ViewObject,"RestoreView"): + if hasattr(sel.Object.ViewObject, "RestoreView"): if sel.Object.ViewObject.RestoreView: - if hasattr(sel.Object.ViewObject,"ViewData"): + if hasattr(sel.Object.ViewObject, "ViewData"): if len(sel.Object.ViewObject.ViewData) >= 12: d = sel.Object.ViewObject.ViewData camtype = "orthographic" @@ -196,16 +210,15 @@ class Draft_SelectPlane: if d[12] == 1: camtype = "perspective" c = FreeCADGui.ActiveDocument.ActiveView.getCameraNode() - from pivy import coin - if isinstance(c,coin.SoOrthographicCamera): + if isinstance(c, coin.SoOrthographicCamera): if camtype == "perspective": FreeCADGui.ActiveDocument.ActiveView.setCameraType("Perspective") - elif isinstance(c,coin.SoPerspectiveCamera): + elif isinstance(c, coin.SoPerspectiveCamera): if camtype == "orthographic": FreeCADGui.ActiveDocument.ActiveView.setCameraType("Orthographic") c = FreeCADGui.ActiveDocument.ActiveView.getCameraNode() - c.position.setValue([d[0],d[1],d[2]]) - c.orientation.setValue([d[3],d[4],d[5],d[6]]) + c.position.setValue([d[0], d[1], d[2]]) + c.orientation.setValue([d[3], d[4], d[5], d[6]]) c.nearDistance.setValue(d[7]) c.farDistance.setValue(d[8]) c.aspectRatio.setValue(d[9]) @@ -214,9 +227,9 @@ class Draft_SelectPlane: c.height.setValue(d[11]) else: c.heightAngle.setValue(d[11]) - if hasattr(sel.Object.ViewObject,"RestoreState"): + if hasattr(sel.Object.ViewObject, "RestoreState"): if sel.Object.ViewObject.RestoreState: - if hasattr(sel.Object.ViewObject,"VisibilityMap"): + if hasattr(sel.Object.ViewObject, "VisibilityMap"): if sel.Object.ViewObject.VisibilityMap: for k,v in sel.Object.ViewObject.VisibilityMap.items(): o = FreeCADGui.ActiveDocument.getObject(k) @@ -228,7 +241,7 @@ class Draft_SelectPlane: self.wpButton.setToolTip(translate("draft", "Current working plane")+": "+self.wpButton.text()) return True elif Draft.getType(sel.Object) == "SectionPlane": - FreeCAD.DraftWorkingPlane.setFromPlacement(sel.Object.Placement,rebase=True) + FreeCAD.DraftWorkingPlane.setFromPlacement(sel.Object.Placement, rebase=True) FreeCAD.DraftWorkingPlane.weak = False self.display(FreeCAD.DraftWorkingPlane.axis) self.wpButton.setText(sel.Object.Label) @@ -241,7 +254,7 @@ class Draft_SelectPlane: self.display(FreeCAD.DraftWorkingPlane.axis) return True elif sel.SubElementNames[0] == "Plane": - FreeCAD.DraftWorkingPlane.setFromPlacement(sel.Object.Placement,rebase=True) + FreeCAD.DraftWorkingPlane.setFromPlacement(sel.Object.Placement, rebase=True) self.display(FreeCAD.DraftWorkingPlane.axis) return True elif len(sel.SubElementNames) == 3: @@ -265,7 +278,7 @@ class Draft_SelectPlane: import Part for s in sel: for so in s.SubObjects: - if isinstance(so,Part.Vertex): + if isinstance(so, Part.Vertex): subs.append(so) if len(subs) == 3: FreeCAD.DraftWorkingPlane.alignTo3Points(subs[0].Point, @@ -276,83 +289,103 @@ class Draft_SelectPlane: return True return False - def getCenterPoint(self,x,y,z): - + def getCenterPoint(self, x, y, z): + """Get the center point.""" if not self.taskd.form.checkCenter.isChecked(): return FreeCAD.Vector() - v = FreeCAD.Vector(x,y,z) - cam1 = FreeCAD.Vector(FreeCADGui.ActiveDocument.ActiveView.getCameraNode().position.getValue().getValue()) + v = FreeCAD.Vector(x, y, z) + view = FreeCADGui.ActiveDocument.ActiveView + camera = view.getCameraNode() + cam1 = FreeCAD.Vector(camera.position.getValue().getValue()) cam2 = FreeCADGui.ActiveDocument.ActiveView.getViewDirection() - vcam1 = DraftVecUtils.project(cam1,v) + vcam1 = DraftVecUtils.project(cam1, v) a = vcam1.getAngle(cam2) if a < 0.0001: return FreeCAD.Vector() d = vcam1.Length L = d/math.cos(a) - vcam2 = DraftVecUtils.scaleTo(cam2,L) + vcam2 = DraftVecUtils.scaleTo(cam2, L) cp = cam1.add(vcam2) return cp - def tostr(self,v): - - """makes a string from a vector or tuple""" - - return "FreeCAD.Vector("+str(v[0])+","+str(v[1])+","+str(v[2])+")" + def tostr(self, v): + """Make a string from a vector or tuple.""" + string = "FreeCAD.Vector(" + string += str(v[0]) + ", " + string += str(v[1]) + ", " + string += str(v[2]) + ")" + return string def getOffset(self): - - """returns the offset value as a float in mm""" - + """Return the offset value as a float in mm.""" try: o = float(self.taskd.form.fieldOffset.text()) - except: + except Exception: o = FreeCAD.Units.Quantity(self.taskd.form.fieldOffset.text()) o = o.Value return o def onClickTop(self): - - o = str(self.getOffset()) - FreeCADGui.doCommandGui(self.ac+"("+self.tostr(self.getCenterPoint(0,0,1))+","+self.tostr((0,0,1))+","+o+")") + """Execute when pressing the top button.""" + offset = str(self.getOffset()) + _cmd = self.ac + _cmd += "(" + _cmd += self.tostr(self.getCenterPoint(0, 0, 1)) + ", " + _cmd += self.tostr((0, 0, 1)) + ", " + _cmd += offset + _cmd += ")" + FreeCADGui.doCommandGui(_cmd) self.display('Top') self.finish() def onClickFront(self): - - o = str(self.getOffset()) - FreeCADGui.doCommandGui(self.ac+"("+self.tostr(self.getCenterPoint(0,-1,0))+","+self.tostr((0,-1,0))+","+o+")") + """Execute when pressing the front button.""" + offset = str(self.getOffset()) + _cmd = self.ac + _cmd += "(" + _cmd += self.tostr(self.getCenterPoint(0, -1, 0)) + ", " + _cmd += self.tostr((0, -1, 0)) + ", " + _cmd += offset + _cmd += ")" + FreeCADGui.doCommandGui(_cmd) self.display('Front') self.finish() def onClickSide(self): - - o = str(self.getOffset()) - FreeCADGui.doCommandGui(self.ac+"("+self.tostr(self.getCenterPoint(1,0,0))+","+self.tostr((1,0,0))+","+o+")") + """Execute when pressing the side button.""" + offset = str(self.getOffset()) + _cmd = self.ac + _cmd += "(" + _cmd += self.tostr(self.getCenterPoint(1, 0, 0)) + ", " + _cmd += self.tostr((1, 0, 0)) + ", " + _cmd += offset + _cmd += ")" + FreeCADGui.doCommandGui(_cmd) self.display('Side') self.finish() def onClickAlign(self): - + """Execute when pressing the align.""" FreeCADGui.doCommandGui("FreeCAD.DraftWorkingPlane.setup(force=True)") d = self.view.getViewDirection().negative() self.display(d) self.finish() def onClickAuto(self): - + """Execute when pressing the auto button.""" FreeCADGui.doCommandGui("FreeCAD.DraftWorkingPlane.reset()") self.display('Auto') self.finish() def onClickMove(self): - + """Execute when pressing the move button.""" sel = FreeCADGui.Selection.getSelectionEx() if sel: verts = [] import Part for s in sel: for so in s.SubObjects: - if isinstance(so,Part.Vertex): + if isinstance(so, Part.Vertex): verts.append(so) if len(verts) == 1: target = verts[0].Point @@ -364,29 +397,29 @@ class Draft_SelectPlane: c = FreeCADGui.ActiveDocument.ActiveView.getCameraNode() p = FreeCAD.Vector(c.position.getValue().getValue()) d = FreeCADGui.ActiveDocument.ActiveView.getViewDirection() - pp = FreeCAD.DraftWorkingPlane.projectPoint(p,d) + pp = FreeCAD.DraftWorkingPlane.projectPoint(p, d) FreeCAD.DraftWorkingPlane.position = pp self.display(pp) self.finish() def onClickCenter(self): - + """Execute when pressing the center button.""" c = FreeCADGui.ActiveDocument.ActiveView.getCameraNode() r = FreeCAD.DraftWorkingPlane.getRotation().Rotation.Q c.orientation.setValue(r) # calculate delta p = FreeCAD.Vector(c.position.getValue().getValue()) pp = FreeCAD.DraftWorkingPlane.projectPoint(p) - delta = pp.negative() # to bring it above the (0,0) point + delta = pp.negative() # to bring it above the (0,0) point np = p.add(delta) c.position.setValue(tuple(np)) self.finish() def onClickPrevious(self): - + """Execute when pressing the previous button.""" p = FreeCAD.DraftWorkingPlane if len(self.states) > 1: - self.states.pop() # discard the last one + self.states.pop() # discard the last one s = self.states[-1] p.u = s[0] p.v = s[1] @@ -395,34 +428,32 @@ class Draft_SelectPlane: FreeCADGui.Snapper.setGrid() self.finish() - def onSetGridSize(self,text): - + def onSetGridSize(self, text): + """Execute when setting the grid size.""" try: q = FreeCAD.Units.Quantity(text) - except: + except Exception: pass else: - self.param.SetFloat("gridSpacing",q.Value) - if hasattr(FreeCADGui,"Snapper"): + self.param.SetFloat("gridSpacing", q.Value) + if hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper.setGrid() - def onSetMainline(self,i): - + def onSetMainline(self, i): + """Execute when setting main line grid spacing.""" if i > 1: - self.param.SetInt("gridEvery",i) - if hasattr(FreeCADGui,"Snapper"): + self.param.SetInt("gridEvery", i) + if hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper.setGrid() - def onSetSnapRadius(self,i): - - self.param.SetInt("snapRange",i) - if hasattr(FreeCADGui,"Snapper"): + def onSetSnapRadius(self, i): + """Execute when setting the snap radius.""" + self.param.SetInt("snapRange", i) + if hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper.showradius() - def display(self,arg): - - """sets the text of the WP button""" - + def display(self, arg): + """Set the text of the working plane button in the toolbar.""" o = self.getOffset() if o: if o > 0: @@ -431,68 +462,36 @@ class Draft_SelectPlane: suffix = ' -O' else: suffix = '' - vdir = FreeCAD.DraftWorkingPlane.axis - vdir = '('+str(vdir.x)[:4]+','+str(vdir.y)[:4]+','+str(vdir.z)[:4]+')' - vdir = " "+translate("draft","Dir")+": "+vdir - if type(arg).__name__ == 'str': - self.wpButton.setText(arg+suffix) + _vdir = FreeCAD.DraftWorkingPlane.axis + vdir = '(' + vdir += str(_vdir.x)[:4] + ',' + vdir += str(_vdir.y)[:4] + ',' + vdir += str(_vdir.z)[:4] + vdir += ')' + + vdir = " " + translate("draft", "Dir") + ": " + vdir + if type(arg).__name__ == 'str': + self.wpButton.setText(arg + suffix) if o != 0: - o = " "+translate("draft","Offset")+": "+str(o) + o = " " + translate("draft", "Offset") + ": " + str(o) else: o = "" - self.wpButton.setToolTip(translate("draft", "Current working plane")+": "+self.wpButton.text()+o+vdir) + _tool = translate("draft", "Current working plane") + ": " + _tool += self.wpButton.text() + o + vdir + self.wpButton.setToolTip(_tool) elif type(arg).__name__ == 'Vector': - plv = '('+str(arg.x)[:6]+','+str(arg.y)[:6]+','+str(arg.z)[:6]+')' - self.wpButton.setText(translate("draft","Custom")) - self.wpButton.setToolTip(translate("draft", "Current working plane")+": "+plv+vdir) + plv = '(' + plv += str(arg.x)[:6] + ',' + plv += str(arg.y)[:6] + ',' + plv += str(arg.z)[:6] + plv += ')' + self.wpButton.setText(translate("draft", "Custom")) + _tool = translate("draft", "Current working plane") + _tool += ": " + plv + vdir + self.wpButton.setToolTip(_tool) p = FreeCAD.DraftWorkingPlane self.states.append([p.u, p.v, p.axis, p.position]) FreeCADGui.doCommandGui("FreeCADGui.Snapper.setGrid()") - -class SelectPlane_TaskPanel: - - '''The editmode TaskPanel for Arch Material objects''' - - def __init__(self): - - import Draft_rc - self.form = FreeCADGui.PySideUic.loadUi(":/ui/TaskSelectPlane.ui") - - def getStandardButtons(self): - - return 2097152 #int(QtGui.QDialogButtonBox.Close) - - - -class Draft_SetWorkingPlaneProxy(): - - """The Draft_SetWorkingPlaneProxy FreeCAD command definition""" - - def GetResources(self): - - return {'Pixmap' : 'Draft_SelectPlane', - 'MenuText': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", "Create Working Plane Proxy"), - 'ToolTip': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", "Creates a proxy object from the current working plane")} - - def IsActive(self): - - if FreeCADGui.ActiveDocument: - return True - else: - return False - - def Activated(self): - - if hasattr(FreeCAD,"DraftWorkingPlane"): - FreeCAD.ActiveDocument.openTransaction("Create WP proxy") - FreeCADGui.addModule("Draft") - FreeCADGui.doCommand("Draft.makeWorkingPlaneProxy(FreeCAD.DraftWorkingPlane.getPlacement())") - FreeCAD.ActiveDocument.recompute() - FreeCAD.ActiveDocument.commitTransaction() - - - -FreeCADGui.addCommand('Draft_SelectPlane',Draft_SelectPlane()) -FreeCADGui.addCommand('Draft_SetWorkingPlaneProxy',Draft_SetWorkingPlaneProxy()) +FreeCADGui.addCommand('Draft_SelectPlane', Draft_SelectPlane()) diff --git a/src/Mod/Draft/draftguitools/gui_snapper.py b/src/Mod/Draft/draftguitools/gui_snapper.py index 189207f30a..37a4a9e211 100644 --- a/src/Mod/Draft/draftguitools/gui_snapper.py +++ b/src/Mod/Draft/draftguitools/gui_snapper.py @@ -18,26 +18,39 @@ # * USA * # * * # *************************************************************************** +"""Provide the Snapper class to control snapping in the Draft Workbench. + +This module provides tools to handle point snapping and +everything that goes with it (toolbar buttons, cursor icons, etc.). +It also creates the Draft grid, which is actually a tracker +defined by `gui_trackers.gridTracker`. +""" +## @package gui_snapper +# \ingroup DRAFT +# \brief Snapper class to control snapping in the Draft Workbench. +# +# This module provides tools to handle point snapping and +# everything that goes with it (toolbar buttons, cursor icons, etc.). + +import collections as coll +import inspect +import itertools +import math +from pivy import coin +from PySide import QtCore, QtGui + +import FreeCAD +import FreeCADGui +import Draft +import DraftVecUtils +from FreeCAD import Vector +import draftguitools.gui_trackers as trackers +from draftutils.messages import _msg, _wrn __title__ = "FreeCAD Draft Snap tools" __author__ = "Yorik van Havre" __url__ = "https://www.freecadweb.org" -## @package DraftSnap -# \ingroup DRAFT -# \brief Snapping system used by Draft & Arch workbenches -# -# This module provides tools to handle point snapping and -# everything that goes with it (toolbar buttons, cursor icons, etc) - - -import FreeCAD, FreeCADGui, math, Draft, DraftVecUtils, itertools -import draftguitools.gui_trackers as trackers -from collections import OrderedDict -from FreeCAD import Vector -from pivy import coin -from PySide import QtCore, QtGui - class Snapper: """Classes to manage snapping in Draft and Arch. @@ -63,7 +76,7 @@ class Snapper: def __init__(self): self.activeview = None - self.lastObj = [None,None] + self.lastObj = [None, None] self.maxEdges = 0 self.radius = 0 self.constraintAxis = None @@ -71,9 +84,9 @@ class Snapper: self.affinity = None self.mask = None self.cursorMode = None - if Draft.getParam("maxSnap",0): - self.maxEdges = Draft.getParam("maxSnapEdges",0) - self.snapStyle = Draft.getParam("snapStyle",0) + if Draft.getParam("maxSnap", 0): + self.maxEdges = Draft.getParam("maxSnapEdges", 0) + self.snapStyle = Draft.getParam("snapStyle", 0) # we still have no 3D view when the draft module initializes self.tracker = None @@ -90,9 +103,12 @@ class Snapper: self.active = True self.forceGridOff = False self.lastExtensions = [] - # the trackers are stored in lists because there can be several views, each with its own set - self.trackers = [[],[],[],[],[],[],[],[],[],[]] # view, grid, snap, extline, radius, dim1, dim2, trackLine, extline2, crosstrackers - self.polarAngles = [90,45] + # the trackers are stored in lists because there can be several views, + # each with its own set + # view, grid, snap, extline, radius, dim1, dim2, trackLine, + # extline2, crosstrackers + self.trackers = [[], [], [], [], [], [], [], [], [], []] + self.polarAngles = [90, 45] self.selectMode = False self.holdTracker = None self.holdPoints = [] @@ -103,49 +119,50 @@ class Snapper: # the snapmarker has "dot","circle" and "square" available styles if self.snapStyle: - self.mk = OrderedDict([('passive', 'empty'), - ('extension', 'empty'), - ('parallel', 'empty'), - ('grid', 'quad'), - ('endpoint', 'quad'), - ('midpoint', 'quad'), - ('perpendicular','quad'), - ('angle', 'quad'), - ('center', 'quad'), - ('ortho', 'quad'), - ('intersection', 'quad'), - ('special', 'quad')]) + self.mk = coll.OrderedDict([('passive', 'empty'), + ('extension', 'empty'), + ('parallel', 'empty'), + ('grid', 'quad'), + ('endpoint', 'quad'), + ('midpoint', 'quad'), + ('perpendicular', 'quad'), + ('angle', 'quad'), + ('center', 'quad'), + ('ortho', 'quad'), + ('intersection', 'quad'), + ('special', 'quad')]) else: - self.mk = OrderedDict([('passive', 'circle'), - ('extension', 'circle'), - ('parallel', 'circle'), - ('grid', 'circle'), - ('endpoint', 'dot'), - ('midpoint', 'square'), - ('perpendicular','dot'), - ('angle', 'square'), - ('center', 'dot'), - ('ortho', 'dot'), - ('intersection', 'dot'), - ('special', 'dot')]) + self.mk = coll.OrderedDict([('passive', 'circle'), + ('extension', 'circle'), + ('parallel', 'circle'), + ('grid', 'circle'), + ('endpoint', 'dot'), + ('midpoint', 'square'), + ('perpendicular', 'dot'), + ('angle', 'square'), + ('center', 'dot'), + ('ortho', 'dot'), + ('intersection', 'dot'), + ('special', 'dot')]) - self.cursors = OrderedDict([('passive', ':/icons/Snap_Near.svg'), - ('extension', ':/icons/Snap_Extension.svg'), - ('parallel', ':/icons/Snap_Parallel.svg'), - ('grid', ':/icons/Snap_Grid.svg'), - ('endpoint', ':/icons/Snap_Endpoint.svg'), - ('midpoint', ':/icons/Snap_Midpoint.svg'), - ('perpendicular', ':/icons/Snap_Perpendicular.svg'), - ('angle', ':/icons/Snap_Angle.svg'), - ('center', ':/icons/Snap_Center.svg'), - ('ortho', ':/icons/Snap_Ortho.svg'), - ('intersection', ':/icons/Snap_Intersection.svg'), - ('special', ':/icons/Snap_Special.svg')]) + self.cursors = \ + coll.OrderedDict([('passive', ':/icons/Snap_Near.svg'), + ('extension', ':/icons/Snap_Extension.svg'), + ('parallel', ':/icons/Snap_Parallel.svg'), + ('grid', ':/icons/Snap_Grid.svg'), + ('endpoint', ':/icons/Snap_Endpoint.svg'), + ('midpoint', ':/icons/Snap_Midpoint.svg'), + ('perpendicular', ':/icons/Snap_Perpendicular.svg'), + ('angle', ':/icons/Snap_Angle.svg'), + ('center', ':/icons/Snap_Center.svg'), + ('ortho', ':/icons/Snap_Ortho.svg'), + ('intersection', ':/icons/Snap_Intersection.svg'), + ('special', ':/icons/Snap_Special.svg')]) def cstr(self, lastpoint, constrain, point): - "constrains if needed" + """Return constraints if needed.""" if constrain or self.mask: - fpt = self.constrain(point,lastpoint) + fpt = self.constrain(point, lastpoint) else: self.unconstrain() fpt = point @@ -153,15 +170,21 @@ class Snapper: self.radiusTracker.update(fpt) return fpt - def snap(self,screenpos,lastpoint=None,active=True,constrain=False,noTracker=False): - """snap(screenpos,lastpoint=None,active=True,constrain=False,noTracker=False): returns a snapped - point from the given (x,y) screenpos (the position of the mouse cursor), active is to - activate active point snapping or not (passive), lastpoint is an optional - other point used to draw an imaginary segment and get additional snap locations. Constrain can - be True to constrain the point against the closest working plane axis. - Screenpos can be a list, a tuple or a coin.SbVec2s object. If noTracker is True, - the tracking line is not displayed.""" + def snap(self, screenpos, + lastpoint=None, active=True, + constrain=False, noTracker=False): + """Return a snapped point from the given (x, y) screen position. + snap(screenpos,lastpoint=None,active=True,constrain=False, + noTracker=False): returns a snapped point from the given + (x,y) screenpos (the position of the mouse cursor), active is to + activate active point snapping or not (passive), + lastpoint is an optional other point used to draw an + imaginary segment and get additional snap locations. Constrain can + be True to constrain the point against the closest working plane axis. + Screenpos can be a list, a tuple or a coin.SbVec2s object. + If noTracker is True, the tracking line is not displayed. + """ if self.running: # do not allow concurrent runs return None @@ -172,44 +195,45 @@ class Snapper: import Part, DraftGeomUtils self.spoint = None - if not hasattr(self,"toolbar"): + if not hasattr(self, "toolbar"): self.makeSnapToolBar() mw = FreeCADGui.getMainWindow() - bt = mw.findChild(QtGui.QToolBar,"Draft Snap") + bt = mw.findChild(QtGui.QToolBar, "Draft Snap") if not bt: mw.addToolBar(self.toolbar) else: - if Draft.getParam("showSnapBar",True): + if Draft.getParam("showSnapBar", True): bt.show() self.snapInfo = None - # type conversion if needed - if isinstance(screenpos,list): + # Type conversion if needed + if isinstance(screenpos, list): screenpos = tuple(screenpos) - elif isinstance(screenpos,coin.SbVec2s): + elif isinstance(screenpos, coin.SbVec2s): screenpos = tuple(screenpos.getValue()) - elif not isinstance(screenpos,tuple): - print("snap needs valid screen position (list, tuple or sbvec2s)") + elif not isinstance(screenpos, tuple): + _wrn("Snap needs valid screen position (list, tuple or sbvec2s)") self.running = False return None - # setup trackers if needed + # Setup trackers if needed self.setTrackers() - - # show the grid if it's off (new view, for ex) - if self.grid and Draft.getParam("grid",True): + + # Show the grid if it's off (new view, for ex) + if self.grid and Draft.getParam("grid", True): self.grid.on() - # getting current snap Radius - self.radius = self.getScreenDist(Draft.getParam("snapRange", 8),screenpos) + # Get current snap radius + self.radius = self.getScreenDist(Draft.getParam("snapRange", 8), + screenpos) if self.radiusTracker: self.radiusTracker.update(self.radius) self.radiusTracker.off() - # activate snap + # Activate snap oldActive = False - if Draft.getParam("alwaysSnap",True): + if Draft.getParam("alwaysSnap", True): oldActive = active active = True if not self.active: @@ -229,29 +253,33 @@ class Snapper: if self.dim2: self.dim2.off() - point = self.getApparentPoint(screenpos[0],screenpos[1]) + point = self.getApparentPoint(screenpos[0], screenpos[1]) - # setup a track line if we got a last point + # Set up a track line if we got a last point if lastpoint and self.trackLine: self.trackLine.p1(lastpoint) - # checking if parallel to one of the edges of the last objects or to a polar direction + # Check if parallel to one of the edges of the last objects + # or to a polar direction eline = None if active: - point,eline = self.snapToPolar(point,lastpoint) - point,eline = self.snapToExtensions(point,lastpoint,constrain,eline) + point, eline = self.snapToPolar(point, lastpoint) + point, eline = self.snapToExtensions(point, lastpoint, + constrain, eline) - objectsUnderCursor = Draft.get3DView().getObjectsInfo((screenpos[0],screenpos[1])) + _view = Draft.get3DView() + objectsUnderCursor = _view.getObjectsInfo((screenpos[0], screenpos[1])) if objectsUnderCursor: if self.snapObjectIndex >= len(objectsUnderCursor): self.snapObjectIndex = 0 self.snapInfo = objectsUnderCursor[self.snapObjectIndex] if self.snapInfo and "Component" in self.snapInfo: - return self.snapToObject(lastpoint, active, constrain, eline, point, oldActive) + return self.snapToObject(lastpoint, active, constrain, + eline, point, oldActive) - # nothing has been snapped - # check for grid snap and ext crossings + # Nothing has been snapped. + # Check for grid snap and ext crossings if active: epoint = self.snapToCrossExtensions(point) if epoint: @@ -262,24 +290,25 @@ class Snapper: if self.trackLine and lastpoint and (not noTracker): self.trackLine.p2(fp) self.trackLine.on() - # set the arch point tracking + # Set the arch point tracking if lastpoint: - self.setArchDims(lastpoint,fp) + self.setArchDims(lastpoint, fp) self.spoint = fp self.running = False return fp def cycleSnapObject(self): + """Increse the index of the snap object by one.""" self.snapObjectIndex = self.snapObjectIndex + 1 - def snapToObject(self, lastpoint, active, constrain, eline, point, oldActive): - # we have an object to snap to - - parent = self.snapInfo.get('ParentObject',None) + def snapToObject(self, lastpoint, active, constrain, + eline, point, oldActive): + """Snap to an object.""" + parent = self.snapInfo.get('ParentObject', None) if parent: subname = self.snapInfo['SubName'] - obj = parent.getSubObject(subname,retType=1) + obj = parent.getSubObject(subname, retType=1) else: obj = FreeCAD.ActiveDocument.getObject(self.snapInfo['Object']) parent = obj @@ -292,35 +321,34 @@ class Snapper: snaps = [] self.lastSnappedObject = obj - if hasattr(obj.ViewObject,"Selectable"): + if hasattr(obj.ViewObject, "Selectable"): if not obj.ViewObject.Selectable: self.spoint = self.cstr(lastpoint, constrain, point) self.running = False return self.spoint if not active: - # passive snapping + # Passive snapping snaps = [self.snapToVertex(self.snapInfo)] - else: - # first stick to the snapped object + # First stick to the snapped object s = self.snapToVertex(self.snapInfo) if s: point = s[0] snaps = [s] - # active snapping + # Active snapping comp = self.snapInfo['Component'] - shape = Part.getShape(parent,subname, - needSubElement=True,noElementMap=True) + shape = Part.getShape(parent, subname, + needSubElement=True, + noElementMap=True) if not shape.isNull(): - - snaps.extend(self.snapToSpecials(obj,lastpoint,eline)) + snaps.extend(self.snapToSpecials(obj, lastpoint, eline)) if Draft.getType(obj) == "Polygon": - # special snapping for polygons: add the center + # Special snapping for polygons: add the center snaps.extend(self.snapToPolygon(obj)) if (not self.maxEdges) or (len(shape.Edges) <= self.maxEdges): @@ -336,9 +364,9 @@ class Snapper: if edge: snaps.extend(self.snapToEndpoints(edge)) snaps.extend(self.snapToMidpoint(edge)) - snaps.extend(self.snapToPerpendicular(edge,lastpoint)) + snaps.extend(self.snapToPerpendicular(edge, lastpoint)) snaps.extend(self.snapToIntersection(edge)) - snaps.extend(self.snapToElines(edge,eline)) + snaps.extend(self.snapToElines(edge, eline)) et = DraftGeomUtils.geomType(edge) if et == "Circle": @@ -355,10 +383,10 @@ class Snapper: snaps.extend(self.snapToFace(face)) elif "Vertex" in comp: # directly snapped to a vertex - snaps.append(self.snapToVertex(self.snapInfo,active=True)) + snaps.append(self.snapToVertex(self.snapInfo, active=True)) elif comp == '': # workaround for the new view provider - snaps.append(self.snapToVertex(self.snapInfo,active=True)) + snaps.append(self.snapToVertex(self.snapInfo, active=True)) else: # all other cases (face, etc...) default to passive snap snapArray = [self.snapToVertex(self.snapInfo)] @@ -380,9 +408,10 @@ class Snapper: # for points we only snap to points snaps.extend(self.snapToEndpoints(obj.Points)) - elif Draft.getType(obj) in ["WorkingPlaneProxy","BuildingPart"]: + elif Draft.getType(obj) in ("WorkingPlaneProxy", "BuildingPart"): # snap to the center of WPProxies and BuildingParts - snaps.append([obj.Placement.Base,'endpoint',self.toWP(obj.Placement.Base)]) + snaps.append([obj.Placement.Base, 'endpoint', + self.toWP(obj.Placement.Base)]) elif Draft.getType(obj) == "SectionPlane": # snap to corners of section planes @@ -405,13 +434,15 @@ class Snapper: # calculating the nearest snap point shortest = 1000000000000000000 - origin = Vector(self.snapInfo['x'],self.snapInfo['y'],self.snapInfo['z']) + origin = Vector(self.snapInfo['x'], + self.snapInfo['y'], + self.snapInfo['z']) winner = None fp = point for snap in snaps: if (not snap) or (snap[0] is None): pass - #print("debug: Snapper: invalid snap point: ",snaps) + # print("debug: Snapper: invalid snap point: ",snaps) else: delta = snap[0].sub(origin) if delta.Length < shortest: @@ -441,46 +472,48 @@ class Snapper: # set the arch point tracking if lastpoint: - self.setArchDims(lastpoint,fp) + self.setArchDims(lastpoint, fp) # return the final point self.spoint = fp self.running = False return self.spoint - def toWP(self,point): - "projects the given point on the working plane, if needed" + def toWP(self, point): + """Project the given point on the working plane, if needed.""" if self.isEnabled("WorkingPlane"): - if hasattr(FreeCAD,"DraftWorkingPlane"): + if hasattr(FreeCAD, "DraftWorkingPlane"): return FreeCAD.DraftWorkingPlane.projectPoint(point) return point - def getApparentPoint(self,x,y): - "returns a 3D point, projected on the current working plane" + def getApparentPoint(self, x, y): + """Return a 3D point, projected on the current working plane.""" view = Draft.get3DView() - pt = view.getPoint(x,y) + pt = view.getPoint(x, y) if self.mask != "z": - if hasattr(FreeCAD,"DraftWorkingPlane"): + if hasattr(FreeCAD, "DraftWorkingPlane"): if view.getCameraType() == "Perspective": camera = view.getCameraNode() p = camera.getField("position").getValue() - dv = pt.sub(Vector(p[0],p[1],p[2])) + dv = pt.sub(Vector(p[0], p[1], p[2])) else: dv = view.getViewDirection() - return FreeCAD.DraftWorkingPlane.projectPoint(pt,dv) + return FreeCAD.DraftWorkingPlane.projectPoint(pt, dv) return pt - def snapToDim(self,obj): + def snapToDim(self, obj): snaps = [] if obj.ViewObject: - if hasattr(obj.ViewObject.Proxy,"p2") and hasattr(obj.ViewObject.Proxy,"p3"): - snaps.append([obj.ViewObject.Proxy.p2,'endpoint',self.toWP(obj.ViewObject.Proxy.p2)]) - snaps.append([obj.ViewObject.Proxy.p3,'endpoint',self.toWP(obj.ViewObject.Proxy.p3)]) + if hasattr(obj.ViewObject.Proxy, "p2") and hasattr(obj.ViewObject.Proxy, "p3"): + snaps.append([obj.ViewObject.Proxy.p2, 'endpoint', self.toWP(obj.ViewObject.Proxy.p2)]) + snaps.append([obj.ViewObject.Proxy.p3, 'endpoint', self.toWP(obj.ViewObject.Proxy.p3)]) return snaps - def snapToExtensions(self,point,last,constrain,eline): - "returns a point snapped to extension or parallel line to last object, if any" + def snapToExtensions(self, point, last, constrain, eline): + """Return a point snapped to extension or parallel line. + The parallel line of the last object, if any. + """ tsnap = self.snapToHold(point) if tsnap: if self.tracker and not self.selectMode: @@ -492,9 +525,9 @@ class Snapper: self.extLine.p2(tsnap[2]) self.extLine.on() self.setCursor(tsnap[1]) - return tsnap[2],eline + return tsnap[2], eline if self.isEnabled("extension"): - tsnap = self.snapToExtOrtho(last,constrain,eline) + tsnap = self.snapToExtOrtho(last, constrain, eline) if tsnap: if (tsnap[0].sub(point)).Length < self.radius: if self.tracker and not self.selectMode: @@ -505,7 +538,7 @@ class Snapper: self.extLine.p2(tsnap[2]) self.extLine.on() self.setCursor(tsnap[1]) - return tsnap[2],eline + return tsnap[2], eline else: tsnap = self.snapToExtPerpendicular(last) if tsnap: @@ -518,10 +551,11 @@ class Snapper: self.extLine.p2(tsnap[2]) self.extLine.on() self.setCursor(tsnap[1]) - return tsnap[2],eline + return tsnap[2], eline - for o in [self.lastObj[1],self.lastObj[0]]: - if o and (self.isEnabled('extension') or self.isEnabled('parallel')): + for o in (self.lastObj[1], self.lastObj[0]): + if o and (self.isEnabled('extension') + or self.isEnabled('parallel')): ob = FreeCAD.ActiveDocument.getObject(o) if ob: if ob.isDerivedFrom("Part::Feature"): @@ -584,11 +618,11 @@ class Snapper: return np,de return point,eline - def snapToCrossExtensions(self,point): - "snaps to the intersection of the last 2 extension lines" + def snapToCrossExtensions(self, point): + """Snap to the intersection of the last 2 extension lines.""" if self.isEnabled('extension'): if len(self.lastExtensions) == 2: - np = DraftGeomUtils.findIntersection(self.lastExtensions[0],self.lastExtensions[1],True,True) + np = DraftGeomUtils.findIntersection(self.lastExtensions[0], self.lastExtensions[1], True, True) if np: for p in np: dv = point.sub(p) @@ -599,7 +633,7 @@ class Snapper: self.tracker.on() self.setCursor('intersection') if self.extLine and self.extLine2: - if DraftVecUtils.equals(self.extLine.p1(),self.lastExtensions[0].Vertexes[0].Point): + if DraftVecUtils.equals(self.extLine.p1(), self.lastExtensions[0].Vertexes[0].Point): p0 = self.lastExtensions[1].Vertexes[0].Point else: p0 = self.lastExtensions[0].Vertexes[0].Point @@ -614,35 +648,35 @@ class Snapper: return p return None - def snapToPolar(self,point,last): - "snaps to polar lines from the given point" + def snapToPolar(self, point, last): + """Snap to polar lines from the given point.""" if self.isEnabled('ortho') and (not self.mask): if last: vecs = [] - if hasattr(FreeCAD,"DraftWorkingPlane"): + if hasattr(FreeCAD, "DraftWorkingPlane"): ax = [FreeCAD.DraftWorkingPlane.u, - FreeCAD.DraftWorkingPlane.v, - FreeCAD.DraftWorkingPlane.axis] + FreeCAD.DraftWorkingPlane.v, + FreeCAD.DraftWorkingPlane.axis] else: - ax = [FreeCAD.Vector(1,0,0), - FreeCAD.Vector(0,1,0), - FreeCAD.Vector(0,0,1)] + ax = [FreeCAD.Vector(1, 0, 0), + FreeCAD.Vector(0, 1, 0), + FreeCAD.Vector(0, 0, 1)] for a in self.polarAngles: - if a == 90: - vecs.extend([ax[0],ax[0].negative()]) - vecs.extend([ax[1],ax[1].negative()]) - else: - v = DraftVecUtils.rotate(ax[0],math.radians(a),ax[2]) - vecs.extend([v,v.negative()]) - v = DraftVecUtils.rotate(ax[1],math.radians(a),ax[2]) - vecs.extend([v,v.negative()]) + if a == 90: + vecs.extend([ax[0], ax[0].negative()]) + vecs.extend([ax[1], ax[1].negative()]) + else: + v = DraftVecUtils.rotate(ax[0], math.radians(a), ax[2]) + vecs.extend([v, v.negative()]) + v = DraftVecUtils.rotate(ax[1], math.radians(a), ax[2]) + vecs.extend([v, v.negative()]) for v in vecs: if not DraftVecUtils.isNull(v): try: - de = Part.LineSegment(last,last.add(v)).toShape() + de = Part.LineSegment(last, last.add(v)).toShape() except Part.OCCError: - return point,None - np = self.getPerpendicular(de,point) + return point, None + np = self.getPerpendicular(de, point) if ((self.radius == 0) and (point.sub(last).getAngle(v) < 0.087)) \ or ((np.sub(point)).Length < self.radius): if self.tracker and not self.selectMode: @@ -651,10 +685,10 @@ class Snapper: self.tracker.on() self.setCursor('ortho') return np,de - return point,None + return point, None - def snapToGrid(self,point): - "returns a grid snap point if available" + def snapToGrid(self, point): + """Return a grid snap point if available.""" if self.grid: if self.grid.Visible: if self.isEnabled("grid"): @@ -670,203 +704,216 @@ class Snapper: return np return point - def snapToEndpoints(self,shape): - "returns a list of endpoints snap locations" + def snapToEndpoints(self, shape): + """Return a list of endpoints snap locations.""" snaps = [] if self.isEnabled("endpoint"): - if hasattr(shape,"Vertexes"): + if hasattr(shape, "Vertexes"): for v in shape.Vertexes: - snaps.append([v.Point,'endpoint',self.toWP(v.Point)]) - elif hasattr(shape,"Point"): - snaps.append([shape.Point,'endpoint',self.toWP(shape.Point)]) - elif hasattr(shape,"Points"): - if len(shape.Points) and hasattr(shape.Points[0],"Vector"): + snaps.append([v.Point, 'endpoint', self.toWP(v.Point)]) + elif hasattr(shape, "Point"): + snaps.append([shape.Point, 'endpoint', self.toWP(shape.Point)]) + elif hasattr(shape, "Points"): + if len(shape.Points) and hasattr(shape.Points[0], "Vector"): for v in shape.Points: - snaps.append([v.Vector,'endpoint',self.toWP(v.Vector)]) + snaps.append([v.Vector, 'endpoint', self.toWP(v.Vector)]) else: for v in shape.Points: - snaps.append([v,'endpoint',self.toWP(v)]) + snaps.append([v, 'endpoint', self.toWP(v)]) return snaps - def snapToMidpoint(self,shape): - "returns a list of midpoints snap locations" + def snapToMidpoint(self, shape): + """Return a list of midpoints snap locations.""" snaps = [] if self.isEnabled("midpoint"): - if isinstance(shape,Part.Edge): + if isinstance(shape, Part.Edge): mp = DraftGeomUtils.findMidpoint(shape) if mp: - snaps.append([mp,'midpoint',self.toWP(mp)]) + snaps.append([mp, 'midpoint', self.toWP(mp)]) return snaps - def snapToPerpendicular(self,shape,last): - "returns a list of perpendicular snap locations" + def snapToPerpendicular(self, shape, last): + """Return a list of perpendicular snap locations.""" snaps = [] if self.isEnabled("perpendicular"): if last: - if isinstance(shape,Part.Edge): + if isinstance(shape, Part.Edge): if DraftGeomUtils.geomType(shape) == "Line": - np = self.getPerpendicular(shape,last) + np = self.getPerpendicular(shape, last) elif DraftGeomUtils.geomType(shape) == "Circle": dv = last.sub(shape.Curve.Center) - dv = DraftVecUtils.scaleTo(dv,shape.Curve.Radius) + dv = DraftVecUtils.scaleTo(dv, shape.Curve.Radius) np = (shape.Curve.Center).add(dv) elif DraftGeomUtils.geomType(shape) == "BSplineCurve": try: pr = shape.Curve.parameter(last) np = shape.Curve.value(pr) - except: + except Exception: return snaps else: return snaps - snaps.append([np,'perpendicular',self.toWP(np)]) + snaps.append([np, 'perpendicular', self.toWP(np)]) return snaps - def snapToOrtho(self,shape,last,constrain): - "returns a list of ortho snap locations" + def snapToOrtho(self, shape, last, constrain): + """Return a list of ortho snap locations.""" snaps = [] if self.isEnabled("ortho"): if constrain: - if isinstance(shape,Part.Edge): + if isinstance(shape, Part.Edge): if last: if DraftGeomUtils.geomType(shape) == "Line": if self.constraintAxis: - tmpEdge = Part.LineSegment(last,last.add(self.constraintAxis)).toShape() + tmpEdge = Part.LineSegment(last, last.add(self.constraintAxis)).toShape() # get the intersection points - pt = DraftGeomUtils.findIntersection(tmpEdge,shape,True,True) + pt = DraftGeomUtils.findIntersection(tmpEdge, shape, True, True) if pt: for p in pt: - snaps.append([p,'ortho',self.toWP(p)]) + snaps.append([p, 'ortho', self.toWP(p)]) return snaps - def snapToExtOrtho(self,last,constrain,eline): - "returns an ortho X extension snap location" + def snapToExtOrtho(self, last, constrain, eline): + """Return an ortho X extension snap location.""" if self.isEnabled("extension") and self.isEnabled("ortho"): if constrain and last and self.constraintAxis and self.extLine: - tmpEdge1 = Part.LineSegment(last,last.add(self.constraintAxis)).toShape() - tmpEdge2 = Part.LineSegment(self.extLine.p1(),self.extLine.p2()).toShape() + tmpEdge1 = Part.LineSegment(last, last.add(self.constraintAxis)).toShape() + tmpEdge2 = Part.LineSegment(self.extLine.p1(), self.extLine.p2()).toShape() # get the intersection points - pt = DraftGeomUtils.findIntersection(tmpEdge1,tmpEdge2,True,True) + pt = DraftGeomUtils.findIntersection(tmpEdge1, tmpEdge2, True, True) if pt: - return [pt[0],'ortho',pt[0]] + return [pt[0], 'ortho', pt[0]] if eline: try: - tmpEdge2 = Part.LineSegment(self.extLine.p1(),self.extLine.p2()).toShape() + tmpEdge2 = Part.LineSegment(self.extLine.p1(), self.extLine.p2()).toShape() # get the intersection points - pt = DraftGeomUtils.findIntersection(eline,tmpEdge2,True,True) + pt = DraftGeomUtils.findIntersection(eline, tmpEdge2, True, True) if pt: - return [pt[0],'ortho',pt[0]] - except: + return [pt[0], 'ortho', pt[0]] + except Exception: return None return None - def snapToHold(self,point): - "returns a snap location that is orthogonal to hold points or, if possible, at crossings" + def snapToHold(self, point): + """Return a snap location that is orthogonal to hold points. + + Or if possible at crossings. + """ if not self.holdPoints: return None - if hasattr(FreeCAD,"DraftWorkingPlane"): + if hasattr(FreeCAD, "DraftWorkingPlane"): u = FreeCAD.DraftWorkingPlane.u v = FreeCAD.DraftWorkingPlane.v else: - u = FreeCAD.Vector(1,0,0) - v = FreeCAD.Vector(0,1,0) + u = FreeCAD.Vector(1, 0, 0) + v = FreeCAD.Vector(0, 1, 0) if len(self.holdPoints) > 1: # first try mid points if self.isEnabled("midpoint"): l = list(self.holdPoints) - for p1,p2 in itertools.combinations(l,2): + for p1, p2 in itertools.combinations(l, 2): p3 = p1.add((p2.sub(p1)).multiply(0.5)) if (p3.sub(point)).Length < self.radius: - return [p1,'midpoint',p3] + return [p1, 'midpoint', p3] # then try int points ipoints = [] l = list(self.holdPoints) while len(l) > 1: p1 = l.pop() for p2 in l: - i1 = DraftGeomUtils.findIntersection(p1,p1.add(u),p2,p2.add(v),True,True) + i1 = DraftGeomUtils.findIntersection(p1, p1.add(u), p2, p2.add(v), True, True) if i1: - ipoints.append([p1,i1[0]]) - i2 = DraftGeomUtils.findIntersection(p1,p1.add(v),p2,p2.add(u),True,True) + ipoints.append([p1, i1[0]]) + i2 = DraftGeomUtils.findIntersection(p1, p1.add(v), p2, p2.add(u), True, True) if i2: - ipoints.append([p1,i2[0]]) + ipoints.append([p1, i2[0]]) for p in ipoints: if (p[1].sub(point)).Length < self.radius: - return [p[0],'ortho',p[1]] + return [p[0], 'ortho', p[1]] # then try to stick to a line for p in self.holdPoints: - d = DraftGeomUtils.findDistance(point,[p,p.add(u)]) + d = DraftGeomUtils.findDistance(point, [p, p.add(u)]) if d: if d.Length < self.radius: fp = point.add(d) - return [p,'extension',fp] - d = DraftGeomUtils.findDistance(point,[p,p.add(v)]) + return [p, 'extension', fp] + d = DraftGeomUtils.findDistance(point, [p, p.add(v)]) if d: if d.Length < self.radius: fp = point.add(d) - return [p,'extension',fp] + return [p, 'extension', fp] return None - def snapToExtPerpendicular(self,last): - "returns a perpendicular X extension snap location" + def snapToExtPerpendicular(self, last): + """Return a perpendicular X extension snap location.""" if self.isEnabled("extension") and self.isEnabled("perpendicular"): if last and self.extLine: if self.extLine.p1() != self.extLine.p2(): - tmpEdge = Part.LineSegment(self.extLine.p1(),self.extLine.p2()).toShape() - np = self.getPerpendicular(tmpEdge,last) - return [np,'perpendicular',np] + tmpEdge = Part.LineSegment(self.extLine.p1(), self.extLine.p2()).toShape() + np = self.getPerpendicular(tmpEdge, last) + return [np, 'perpendicular', np] return None - def snapToElines(self,e1,e2): - "returns a snap location at the infinite intersection of the given edges" + def snapToElines(self, e1, e2): + """Return a snap at the infinite intersection of the given edges.""" snaps = [] if self.isEnabled("intersection") and self.isEnabled("extension"): if e1 and e2: # get the intersection points - pts = DraftGeomUtils.findIntersection(e1,e2,True,True) + pts = DraftGeomUtils.findIntersection(e1, e2, True, True) if pts: for p in pts: - snaps.append([p,'intersection',self.toWP(p)]) + snaps.append([p, 'intersection', self.toWP(p)]) return snaps - def snapToAngles(self,shape): - "returns a list of angle snap locations" + def snapToAngles(self, shape): + """Return a list of angle snap locations.""" snaps = [] if self.isEnabled("angle"): rad = shape.Curve.Radius pos = shape.Curve.Center - for i in [0,30,45,60,90,120,135,150,180,210,225,240,270,300,315,330]: + for i in (0, 30, 45, 60, 90, + 120, 135, 150, 180, + 210, 225, 240, 270, + 300, 315, 330): ang = math.radians(i) - cur = Vector(math.sin(ang)*rad+pos.x,math.cos(ang)*rad+pos.y,pos.z) - snaps.append([cur,'angle',self.toWP(cur)]) + cur = Vector(math.sin(ang) * rad + pos.x, + math.cos(ang) * rad + pos.y, + pos.z) + snaps.append([cur, 'angle', self.toWP(cur)]) return snaps - def snapToCenter(self,shape): - "returns a list of center snap locations" + def snapToCenter(self, shape): + """Return a list of center snap locations.""" snaps = [] if self.isEnabled("center"): pos = shape.Curve.Center c = self.toWP(pos) - if hasattr(shape.Curve,"Radius"): + if hasattr(shape.Curve, "Radius"): rad = shape.Curve.Radius - for i in [15,37.5,52.5,75,105,127.5,142.5,165,195,217.5,232.5,255,285,307.5,322.5,345]: + for i in (15, 37.5, 52.5, 75, + 105, 127.5, 142.5, 165, + 195, 217.5, 232.5, 255, + 285, 307.5, 322.5, 345): ang = math.radians(i) - cur = Vector(math.sin(ang)*rad+pos.x,math.cos(ang)*rad+pos.y,pos.z) - snaps.append([cur,'center',c]) + cur = Vector(math.sin(ang) * rad + pos.x, + math.cos(ang) * rad + pos.y, + pos.z) + snaps.append([cur, 'center', c]) else: - snaps.append([c,'center',c]) + snaps.append([c, 'center', c]) return snaps - def snapToFace(self,shape): - "returns a face center snap location" + def snapToFace(self, shape): + """Return a face center snap location.""" snaps = [] if self.isEnabled("center"): pos = shape.CenterOfMass c = self.toWP(pos) - snaps.append([pos,'center',c]) + snaps.append([pos, 'center', c]) return snaps - def snapToIntersection(self,shape): - "returns a list of intersection snap locations" + def snapToIntersection(self, shape): + """Return a list of intersection snap locations.""" snaps = [] if self.isEnabled("intersection"): # get the stored objects to calculate intersections @@ -885,45 +932,47 @@ class Snapper: p2 = self.toWP(e.Vertexes[-1].Point) p3 = self.toWP(shape.Vertexes[0].Point) p4 = self.toWP(shape.Vertexes[-1].Point) - pt = DraftGeomUtils.findIntersection(p1,p2,p3,p4,True,True) + pt = DraftGeomUtils.findIntersection(p1, p2, p3, p4, True, True) else: - pt = DraftGeomUtils.findIntersection(e,shape) + pt = DraftGeomUtils.findIntersection(e, shape) if pt: for p in pt: - snaps.append([p,'intersection',self.toWP(p)]) + snaps.append([p, 'intersection', self.toWP(p)]) except: pass - # some curve types yield an error when trying to read their types... + # some curve types yield an error + # when trying to read their types return snaps - def snapToPolygon(self,obj): - "returns a list of polygon center snap locations" + def snapToPolygon(self, obj): + """Return a list of polygon center snap locations.""" snaps = [] if self.isEnabled("center"): c = obj.Placement.Base for edge in obj.Shape.Edges: p1 = edge.Vertexes[0].Point p2 = edge.Vertexes[-1].Point - v1 = p1.add((p2-p1).scale(.25,.25,.25)) - v2 = p1.add((p2-p1).scale(.75,.75,.75)) - snaps.append([v1,'center',self.toWP(c)]) - snaps.append([v2,'center',self.toWP(c)]) + v1 = p1.add((p2 - p1).scale(0.25, 0.25, 0.25)) + v2 = p1.add((p2 - p1).scale(0.75, 0.75, 0.75)) + snaps.append([v1, 'center', self.toWP(c)]) + snaps.append([v2, 'center', self.toWP(c)]) return snaps - def snapToVertex(self,info,active=False): - p = Vector(info['x'],info['y'],info['z']) + def snapToVertex(self, info, active=False): + """Return a vertex snap location.""" + p = Vector(info['x'], info['y'], info['z']) if active: if self.isEnabled("passive"): - return [p,'endpoint',self.toWP(p)] + return [p, 'endpoint', self.toWP(p)] else: return [] elif self.isEnabled("passive"): - return [p,'passive',p] + return [p, 'passive', p] else: return [] - def snapToSpecials(self,obj,lastpoint=None,eline=None): - "returns special snap locations, if any" + def snapToSpecials(self, obj, lastpoint=None, eline=None): + """Return special snap locations, if any.""" snaps = [] if self.isEnabled("special"): @@ -932,48 +981,48 @@ class Snapper: if obj.Base: if not obj.Base.Shape.Solids: for v in obj.Base.Shape.Vertexes: - snaps.append([v.Point,'special',self.toWP(v.Point)]) + snaps.append([v.Point, 'special', self.toWP(v.Point)]) elif (Draft.getType(obj) == "Structure"): # special snapping for struct: only to its base point if obj.Base: if not obj.Base.Shape.Solids: for v in obj.Base.Shape.Vertexes: - snaps.append([v.Point,'special',self.toWP(v.Point)]) + snaps.append([v.Point, 'special', self.toWP(v.Point)]) else: b = obj.Placement.Base - snaps.append([b,'special',self.toWP(b)]) + snaps.append([b, 'special', self.toWP(b)]) if obj.ViewObject.ShowNodes: for edge in obj.Proxy.getNodeEdges(obj): snaps.extend(self.snapToEndpoints(edge)) snaps.extend(self.snapToMidpoint(edge)) - snaps.extend(self.snapToPerpendicular(edge,lastpoint)) + snaps.extend(self.snapToPerpendicular(edge, lastpoint)) snaps.extend(self.snapToIntersection(edge)) - snaps.extend(self.snapToElines(edge,eline)) + snaps.extend(self.snapToElines(edge, eline)) - elif hasattr(obj,"SnapPoints"): + elif hasattr(obj, "SnapPoints"): for p in obj.SnapPoints: p2 = obj.Placement.multVec(p) - snaps.append([p2,'special',p2]) + snaps.append([p2, 'special', p2]) return snaps - def getScreenDist(self,dist,cursor): - "returns a distance in 3D space from a screen pixels distance" + def getScreenDist(self, dist, cursor): + """Return a distance in 3D space from a screen pixels distance.""" view = Draft.get3DView() p1 = view.getPoint(cursor) - p2 = view.getPoint((cursor[0]+dist,cursor[1])) + p2 = view.getPoint((cursor[0] + dist, cursor[1])) return (p2.sub(p1)).Length - def getPerpendicular(self,edge,pt): - "returns a point on an edge, perpendicular to the given point" + def getPerpendicular(self, edge, pt): + """Return a point on an edge, perpendicular to the given point.""" dv = pt.sub(edge.Vertexes[0].Point) - nv = DraftVecUtils.project(dv,DraftGeomUtils.vec(edge)) + nv = DraftVecUtils.project(dv, DraftGeomUtils.vec(edge)) np = (edge.Vertexes[0].Point).add(nv) return np - def setArchDims(self,p1,p2): - "show arch dimensions between 2 points" + def setArchDims(self, p1, p2): + """Show arc dimensions between 2 points.""" if self.isEnabled("Dimensions"): if not self.dim1: self.dim1 = trackers.archDimTracker(mode=2) @@ -988,8 +1037,8 @@ class Snapper: if self.dim2.Distance: self.dim2.on() - def setCursor(self,mode=None): - "setCursor(self,mode=None): sets or resets the cursor to the given mode or resets" + def setCursor(self, mode=None): + """Set or reset the cursor to the given mode or resets.""" if self.selectMode: mw = FreeCADGui.getMainWindow() for w in mw.findChild(QtGui.QMdiArea).findChildren(QtGui.QWidget): @@ -1005,16 +1054,16 @@ class Snapper: else: if mode != self.cursorMode: baseicon = QtGui.QPixmap(":/icons/Draft_Cursor.svg") - newicon = QtGui.QPixmap(32,24) + newicon = QtGui.QPixmap(32, 24) newicon.fill(QtCore.Qt.transparent) qp = QtGui.QPainter() qp.begin(newicon) - qp.drawPixmap(0,0,baseicon) + qp.drawPixmap(0, 0, baseicon) if not (mode == 'passive'): tp = QtGui.QPixmap(self.cursors[mode]).scaledToWidth(16) - qp.drawPixmap(QtCore.QPoint(16, 8), tp); + qp.drawPixmap(QtCore.QPoint(16, 8), tp) qp.end() - cur = QtGui.QCursor(newicon,8,8) + cur = QtGui.QCursor(newicon, 8, 8) mw = FreeCADGui.getMainWindow() for w in mw.findChild(QtGui.QMdiArea).findChildren(QtGui.QWidget): if w.metaObject().className() == "SIM::Coin3D::Quarter::QuarterWidget": @@ -1022,11 +1071,12 @@ class Snapper: self.cursorMode = mode def restack(self): + """Lower the grid tracker so it doesn't obscure other objects.""" if self.grid: self.grid.lowerTracker() def off(self, hideSnapBar=False): - "finishes snapping" + """Finish snapping.""" if self.tracker: self.tracker.off() if self.trackLine: @@ -1042,7 +1092,7 @@ class Snapper: if self.dim2: self.dim2.off() if self.grid: - if not Draft.getParam("alwaysShowGrid",True): + if not Draft.getParam("alwaysShowGrid", True): self.grid.off() if self.holdTracker: self.holdTracker.clear() @@ -1050,16 +1100,16 @@ class Snapper: self.unconstrain() self.radius = 0 self.setCursor() - if hideSnapBar or Draft.getParam("hideSnapBar",False): - if hasattr(self,"toolbar") and self.toolbar: + if hideSnapBar or Draft.getParam("hideSnapBar", False): + if hasattr(self, "toolbar") and self.toolbar: self.toolbar.hide() self.mask = None self.selectMode = False self.running = False self.holdPoints = [] - def setSelectMode(self,mode): - "sets the snapper into select mode (hides snapping temporarily)" + def setSelectMode(self, mode): + """Set the snapper into select mode (hides snapping temporarily).""" self.selectMode = mode if not mode: self.setCursor() @@ -1067,26 +1117,28 @@ class Snapper: if self.trackLine: self.trackLine.off() - def setAngle(self,delta=None): - "keeps the current angle" + def setAngle(self, delta=None): + """Keep the current angle.""" if delta: self.mask = delta - elif isinstance(self.mask,FreeCAD.Vector): + elif isinstance(self.mask, FreeCAD.Vector): self.mask = None elif self.trackLine: if self.trackLine.Visible: self.mask = self.trackLine.p2().sub(self.trackLine.p1()) - def constrain(self,point,basepoint=None,axis=None): - '''constrain(point,basepoint=None,axis=None: Returns a + def constrain(self, point, basepoint=None, axis=None): + """Return a constrained point. + + constrain(point,basepoint=None,axis=None: Returns a constrained point. Axis can be "x","y" or "z" or a custom vector. If None, the closest working plane axis will be picked. Basepoint is the base point used to figure out from where the point must be constrained. If no basepoint is given, the current point is - used as basepoint.''' - + used as basepoint. + """ # without the Draft module fully loaded, no axes system!" - if not hasattr(FreeCAD,"DraftWorkingPlane"): + if not hasattr(FreeCAD, "DraftWorkingPlane"): return point point = Vector(point) @@ -1111,7 +1163,7 @@ class Snapper: self.affinity = self.mask if not self.affinity: self.affinity = FreeCAD.DraftWorkingPlane.getClosestAxis(delta) - if isinstance(axis,FreeCAD.Vector): + if isinstance(axis, FreeCAD.Vector): self.constraintAxis = axis elif axis == "x": self.constraintAxis = FreeCAD.DraftWorkingPlane.u @@ -1126,7 +1178,7 @@ class Snapper: self.constraintAxis = FreeCAD.DraftWorkingPlane.v elif self.affinity == "z": self.constraintAxis = FreeCAD.DraftWorkingPlane.axis - elif isinstance(self.affinity,FreeCAD.Vector): + elif isinstance(self.affinity, FreeCAD.Vector): self.constraintAxis = self.affinity else: self.constraintAxis = None @@ -1135,7 +1187,7 @@ class Snapper: return point # calculating constrained point - cdelta = DraftVecUtils.project(delta,self.constraintAxis) + cdelta = DraftVecUtils.project(delta, self.constraintAxis) npoint = self.basepoint.add(cdelta) # setting constrain line @@ -1150,22 +1202,26 @@ class Snapper: return npoint def unconstrain(self): + """Unset the basepoint and the constrain line.""" self.basepoint = None self.affinity = None if self.constrainLine: self.constrainLine.off() - def getPoint(self,last=None,callback=None,movecallback=None,extradlg=None,title=None,mode="point"): + def getPoint(self, last=None, callback=None, movecallback=None, + extradlg=None, title=None, mode="point"): + """Get a 3D point from the screen. - """ - getPoint([last],[callback],[movecallback],[extradlg],[title]) : gets a 3D point - from the screen. You can provide an existing point, in that case additional - snap options and a tracker are available. + getPoint([last],[callback],[movecallback],[extradlg],[title]): + gets a 3D point from the screen. You can provide an existing point, + in that case additional snap options and a tracker are available. You can also pass a function as callback, which will get called - with the resulting point as argument, when a point is clicked, and optionally - another callback which gets called when the mouse is moved. + with the resulting point as argument, when a point is clicked, + and optionally another callback which gets called when + the mouse is moved. - If the operation gets cancelled (the user pressed Escape), no point is returned. + If the operation gets cancelled (the user pressed Escape), + no point is returned. Example: @@ -1175,17 +1231,15 @@ class Snapper: FreeCADGui.Snapper.getPoint(callback=cb) - If the callback function accepts more than one argument, it will also receive - the last snapped object. Finally, a qt widget can be passed as an extra taskbox. - title is the title of the point task box - mode is the dialog box you want (default is point, you can also use wire and line) + If the callback function accepts more than one argument, + it will also receive the last snapped object. Finally, a qt widget + can be passed as an extra taskbox. + title is the title of the point task box mode is the dialog box + you want (default is point, you can also use wire and line) - If getPoint() is invoked without any argument, nothing is done but the callbacks - are removed, so it can be used as a cancel function. + If getPoint() is invoked without any argument, nothing is done + but the callbacks are removed, so it can be used as a cancel function. """ - - import inspect - self.pt = None self.lastSnappedObject = None self.holdPoints = [] @@ -1194,9 +1248,9 @@ class Snapper: # remove any previous leftover callbacks if self.callbackClick: - self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),self.callbackClick) + self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.callbackClick) if self.callbackMove: - self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(),self.callbackMove) + self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.callbackMove) self.callbackClick = None self.callbackMove = None @@ -1205,15 +1259,19 @@ class Snapper: mousepos = event.getPosition() ctrl = event.wasCtrlDown() shift = event.wasShiftDown() - self.pt = FreeCADGui.Snapper.snap(mousepos,lastpoint=last,active=ctrl,constrain=shift) - if hasattr(FreeCAD,"DraftWorkingPlane"): - self.ui.displayPoint(self.pt,last,plane=FreeCAD.DraftWorkingPlane,mask=FreeCADGui.Snapper.affinity) + self.pt = FreeCADGui.Snapper.snap(mousepos, lastpoint=last, + active=ctrl, constrain=shift) + if hasattr(FreeCAD, "DraftWorkingPlane"): + self.ui.displayPoint(self.pt, last, + plane=FreeCAD.DraftWorkingPlane, + mask=FreeCADGui.Snapper.affinity) if movecallback: - movecallback(self.pt,self.snapInfo) + movecallback(self.pt, self.snapInfo) - def getcoords(point,relative=False): + def getcoords(point, relative=False): + """Get the global coordinates from a point.""" self.pt = point - if relative and last and hasattr(FreeCAD,"DraftWorkingPlane"): + if relative and last and hasattr(FreeCAD, "DraftWorkingPlane"): v = FreeCAD.DraftWorkingPlane.getGlobalCoords(point) self.pt = last.add(v) accept() @@ -1226,9 +1284,9 @@ class Snapper: def accept(): if self.callbackClick: - self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),self.callbackClick) + self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.callbackClick) if self.callbackMove: - self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(),self.callbackMove) + self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.callbackMove) self.callbackClick = None self.callbackMove = None obj = FreeCADGui.Snapper.lastSnappedObject @@ -1236,23 +1294,23 @@ class Snapper: self.ui.offUi() if callback: if len(inspect.getargspec(callback).args) > 1: - callback(self.pt,obj) + callback(self.pt, obj) else: callback(self.pt) self.pt = None def cancel(): if self.callbackClick: - self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),self.callbackClick) + self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.callbackClick) if self.callbackMove: - self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(),self.callbackMove) + self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.callbackMove) self.callbackClick = None self.callbackMove = None FreeCADGui.Snapper.off() self.ui.offUi() if callback: if len(inspect.getargspec(callback).args) > 1: - callback(None,None) + callback(None, None) else: callback(None) @@ -1265,111 +1323,121 @@ class Snapper: interface = self.ui.pointUi if callback: if title: - interface(title=title,cancel=cancel,getcoords=getcoords,extra=extradlg,rel=bool(last)) + interface(title=title, cancel=cancel, getcoords=getcoords, + extra=extradlg, rel=bool(last)) else: interface(cancel=cancel,getcoords=getcoords,extra=extradlg,rel=bool(last)) self.callbackClick = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),click) self.callbackMove = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(),move) def makeSnapToolBar(self): - "builds the Snap toolbar" + """Build the Snap toolbar.""" mw = FreeCADGui.getMainWindow() self.toolbar = QtGui.QToolBar(mw) mw.addToolBar(QtCore.Qt.TopToolBarArea, self.toolbar) self.toolbar.setObjectName("Draft Snap") self.toolbar.setWindowTitle(QtCore.QCoreApplication.translate("Workbench", "Draft Snap")) self.toolbarButtons = [] + # grid button self.gridbutton = QtGui.QAction(mw) self.gridbutton.setIcon(QtGui.QIcon.fromTheme("Draft_Grid", QtGui.QIcon(":/icons/Draft_Grid.svg"))) - self.gridbutton.setText(QtCore.QCoreApplication.translate("Draft_ToggleGrid","Grid")) - self.gridbutton.setToolTip(QtCore.QCoreApplication.translate("Draft_ToggleGrid","Toggles the Draft grid On/Off")) + self.gridbutton.setText(QtCore.QCoreApplication.translate("Draft_ToggleGrid", "Grid")) + self.gridbutton.setToolTip(QtCore.QCoreApplication.translate("Draft_ToggleGrid", "Toggles the Draft grid On/Off")) self.gridbutton.setObjectName("GridButton") self.gridbutton.setWhatsThis("Draft_ToggleGrid") - QtCore.QObject.connect(self.gridbutton,QtCore.SIGNAL("triggered()"),self.toggleGrid) + QtCore.QObject.connect(self.gridbutton, QtCore.SIGNAL("triggered()"), self.toggleGrid) self.toolbar.addAction(self.gridbutton) + # master button self.masterbutton = QtGui.QAction(mw) self.masterbutton.setIcon(QtGui.QIcon.fromTheme("Snap_Lock", QtGui.QIcon(":/icons/Snap_Lock.svg"))) - self.masterbutton.setText(QtCore.QCoreApplication.translate("Draft_Snap_Lock","Lock")) - self.masterbutton.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_Lock","Toggle On/Off")) + self.masterbutton.setText(QtCore.QCoreApplication.translate("Draft_Snap_Lock", "Lock")) + self.masterbutton.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_Lock", "Toggle On/Off")) self.masterbutton.setObjectName("SnapButtonMain") self.masterbutton.setWhatsThis("Draft_ToggleSnap") self.masterbutton.setCheckable(True) self.masterbutton.setChecked(True) - QtCore.QObject.connect(self.masterbutton,QtCore.SIGNAL("toggled(bool)"),self.toggle) + QtCore.QObject.connect(self.masterbutton, + QtCore.SIGNAL("toggled(bool)"), self.toggle) self.toolbar.addAction(self.masterbutton) for c,i in self.cursors.items(): if i: b = QtGui.QAction(mw) b.setIcon(QtGui.QIcon.fromTheme(i.replace(':/icons/', '').replace('.svg', ''), QtGui.QIcon(i))) if c == "passive": - b.setText(QtCore.QCoreApplication.translate("Draft_Snap_Near","Nearest")) - b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_Near","Nearest")) + b.setText(QtCore.QCoreApplication.translate("Draft_Snap_Near", "Nearest")) + b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_Near", "Nearest")) else: - b.setText(QtCore.QCoreApplication.translate("Draft_Snap_"+c.capitalize(),c.capitalize())) - b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_"+c.capitalize(),c.capitalize())) - b.setObjectName("SnapButton"+c) - b.setWhatsThis("Draft_"+c.capitalize()) + b.setText(QtCore.QCoreApplication.translate("Draft_Snap_"+c.capitalize(), c.capitalize())) + b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_"+c.capitalize(), c.capitalize())) + b.setObjectName("SnapButton" + c) + b.setWhatsThis("Draft_" + c.capitalize()) b.setCheckable(True) b.setChecked(True) self.toolbar.addAction(b) self.toolbarButtons.append(b) - QtCore.QObject.connect(b,QtCore.SIGNAL("toggled(bool)"),self.saveSnapModes) + QtCore.QObject.connect(b, QtCore.SIGNAL("toggled(bool)"), + self.saveSnapModes) + # adding non-snap button - for n in ["Dimensions","WorkingPlane"]: + for n in ("Dimensions", "WorkingPlane"): b = QtGui.QAction(mw) b.setIcon(QtGui.QIcon.fromTheme("Snap_" + n, QtGui.QIcon(":/icons/Snap_"+n+".svg"))) - b.setText(QtCore.QCoreApplication.translate("Draft_Snap_"+n,n)) - b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_"+n,n)) - b.setObjectName("SnapButton"+n) - b.setWhatsThis("Draft_"+n) + b.setText(QtCore.QCoreApplication.translate("Draft_Snap_" + n,n)) + b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_" + n,n)) + b.setObjectName("SnapButton" + n) + b.setWhatsThis("Draft_" + n) b.setCheckable(True) b.setChecked(True) self.toolbar.addAction(b) - QtCore.QObject.connect(b,QtCore.SIGNAL("toggled(bool)"),self.saveSnapModes) + QtCore.QObject.connect(b, QtCore.SIGNAL("toggled(bool)"), + self.saveSnapModes) self.toolbarButtons.append(b) + # set status tip where needed for b in self.toolbar.actions(): if len(b.statusTip()) == 0: b.setStatusTip(b.toolTip()) + # restoring states - t = Draft.getParam("snapModes","111111111101111") + t = Draft.getParam("snapModes", "111111111101111") if t: c = 0 - for b in [self.masterbutton]+self.toolbarButtons: + for b in [self.masterbutton] + self.toolbarButtons: if len(t) > c: state = bool(int(t[c])) b.setChecked(state) if state: - b.setToolTip(b.toolTip()+" (ON)") + b.setToolTip(b.toolTip() + " (ON)") else: - b.setToolTip(b.toolTip()+" (OFF)") + b.setToolTip(b.toolTip() + " (OFF)") c += 1 - if not Draft.getParam("showSnapBar",True): + if not Draft.getParam("showSnapBar", True): self.toolbar.hide() def toggleGrid(self): + """Run Draft_ToggleGrid.""" FreeCADGui.runCommand("Draft_ToggleGrid") def saveSnapModes(self): - "saves the snap modes for next sessions" + """Save the snap modes for next sessions.""" t = '' - for b in [self.masterbutton]+self.toolbarButtons: + for b in [self.masterbutton] + self.toolbarButtons: t += str(int(b.isChecked())) if b.isChecked(): - b.setToolTip(b.toolTip().replace("OFF","ON")) + b.setToolTip(b.toolTip().replace("OFF", "ON")) else: - b.setToolTip(b.toolTip().replace("ON","OFF")) - Draft.setParam("snapModes",t) + b.setToolTip(b.toolTip().replace("ON", "OFF")) + Draft.setParam("snapModes", t) - def toggle(self,checked=None): - "toggles the snap mode" - if hasattr(self,"toolbarButtons"): + def toggle(self, checked=None): + """Toggle the snap mode.""" + if hasattr(self, "toolbarButtons"): if checked is None: self.masterbutton.toggle() elif checked: - if hasattr(self,"savedButtonStates"): + if hasattr(self, "savedButtonStates"): for i in range(len(self.toolbarButtons)): self.toolbarButtons[i].setEnabled(True) self.toolbarButtons[i].setChecked(self.savedButtonStates[i]) @@ -1381,25 +1449,26 @@ class Snapper: self.saveSnapModes() def showradius(self): - "shows the snap radius indicator" - self.radius = self.getScreenDist(Draft.getParam("snapRange", 8),(400,300)) + """Show the snap radius indicator.""" + self.radius = self.getScreenDist(Draft.getParam("snapRange", 8), + (400, 300)) if self.radiusTracker: self.radiusTracker.update(self.radius) self.radiusTracker.on() - def isEnabled(self,but): - "returns true if the given button is turned on" + def isEnabled(self, but): + """Return true if the given button is turned on.""" for b in self.toolbarButtons: if str(b.objectName()) == "SnapButton" + but: return (b.isEnabled() and b.isChecked()) return False def show(self): - "shows the toolbar and the grid" - if not hasattr(self,"toolbar"): + """Show the toolbar and the grid.""" + if not hasattr(self, "toolbar"): self.makeSnapToolBar() mw = FreeCADGui.getMainWindow() - bt = mw.findChild(QtGui.QToolBar,"Draft Snap") + bt = mw.findChild(QtGui.QToolBar, "Draft Snap") if not bt: mw.addToolBar(self.toolbar) self.toolbar.setParent(mw) @@ -1418,18 +1487,20 @@ class Snapper: c.height.setValue(h) def hide(self): - if hasattr(self,"toolbar"): + """Hide the toolbar.""" + if hasattr(self, "toolbar"): self.toolbar.hide() self.toolbar.toggleViewAction().setVisible(True) def setGrid(self): - "sets the grid, if visible" + """Set the grid, if visible.""" self.setTrackers() if self.grid and (not self.forceGridOff): if self.grid.Visible: self.grid.set() def setTrackers(self): + """Set the trackers.""" v = Draft.get3DView() if v != self.activeview: if v in self.trackers[0]: @@ -1444,7 +1515,7 @@ class Snapper: self.extLine2 = self.trackers[8][i] self.holdTracker = self.trackers[9][i] else: - if Draft.getParam("grid",True): + if Draft.getParam("grid", True): self.grid = trackers.gridTracker() self.grid.on() else: @@ -1454,7 +1525,7 @@ class Snapper: if self.snapStyle: c = FreeCADGui.draftToolBar.getDefaultColor("snap") self.extLine = trackers.lineTracker(scolor=c) - self.extLine2 = trackers.lineTracker(scolor = c) + self.extLine2 = trackers.lineTracker(scolor=c) else: self.extLine = trackers.lineTracker(dotted=True) self.extLine2 = trackers.lineTracker(dotted=True) @@ -1479,7 +1550,8 @@ class Snapper: self.grid.set() def addHoldPoint(self): - if self.spoint and not(self.spoint in self.holdPoints): + """Add hold snap point to list of hold points.""" + if self.spoint and self.spoint not in self.holdPoints: if self.holdTracker: self.holdTracker.addCoords(self.spoint) self.holdTracker.on() diff --git a/src/Mod/Draft/draftguitools/gui_snaps.py b/src/Mod/Draft/draftguitools/gui_snaps.py index 233fc754b2..019d93c47a 100644 --- a/src/Mod/Draft/draftguitools/gui_snaps.py +++ b/src/Mod/Draft/draftguitools/gui_snaps.py @@ -1,9 +1,3 @@ -"""Provide the Draft_Snap commands used by the snapping mechanism in Draft.""" -## @package gui_snaps -# \ingroup DRAFT -# \brief Provide the Draft_Snap commands used by the snapping mechanism -# in Draft. - # *************************************************************************** # * (c) 2009, 2010 Yorik van Havre * # * (c) 2009, 2010 Ken Cline * @@ -28,9 +22,16 @@ # * USA * # * * # *************************************************************************** -import FreeCADGui +"""Provide the Draft_Snap commands used by the snapping mechanism in Draft.""" +## @package gui_snaps +# \ingroup DRAFT +# \brief Provide the Draft_Snap commands used by the snapping mechanism +# in Draft. + from PySide.QtCore import QT_TRANSLATE_NOOP +import FreeCADGui as Gui + class Draft_Snap_Lock: """Command to activate or deactivate all snap commands.""" @@ -47,12 +48,12 @@ class Draft_Snap_Lock: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "masterbutton"): - FreeCADGui.Snapper.masterbutton.toggle() + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "masterbutton"): + Gui.Snapper.masterbutton.toggle() -FreeCADGui.addCommand('Draft_Snap_Lock', Draft_Snap_Lock()) +Gui.addCommand('Draft_Snap_Lock', Draft_Snap_Lock()) class Draft_Snap_Midpoint: @@ -68,14 +69,14 @@ class Draft_Snap_Midpoint: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonmidpoint": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Midpoint', Draft_Snap_Midpoint()) +Gui.addCommand('Draft_Snap_Midpoint', Draft_Snap_Midpoint()) class Draft_Snap_Perpendicular: @@ -93,14 +94,14 @@ class Draft_Snap_Perpendicular: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonperpendicular": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Perpendicular', Draft_Snap_Perpendicular()) +Gui.addCommand('Draft_Snap_Perpendicular', Draft_Snap_Perpendicular()) class Draft_Snap_Grid: @@ -115,14 +116,14 @@ class Draft_Snap_Grid: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtongrid": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Grid', Draft_Snap_Grid()) +Gui.addCommand('Draft_Snap_Grid', Draft_Snap_Grid()) class Draft_Snap_Intersection: @@ -140,14 +141,14 @@ class Draft_Snap_Intersection: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonintersection": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Intersection', Draft_Snap_Intersection()) +Gui.addCommand('Draft_Snap_Intersection', Draft_Snap_Intersection()) class Draft_Snap_Parallel: @@ -163,14 +164,14 @@ class Draft_Snap_Parallel: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonparallel": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Parallel', Draft_Snap_Parallel()) +Gui.addCommand('Draft_Snap_Parallel', Draft_Snap_Parallel()) class Draft_Snap_Endpoint: @@ -186,14 +187,14 @@ class Draft_Snap_Endpoint: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonendpoint": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Endpoint', Draft_Snap_Endpoint()) +Gui.addCommand('Draft_Snap_Endpoint', Draft_Snap_Endpoint()) class Draft_Snap_Angle: @@ -208,14 +209,14 @@ class Draft_Snap_Angle: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonangle": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Angle', Draft_Snap_Angle()) +Gui.addCommand('Draft_Snap_Angle', Draft_Snap_Angle()) class Draft_Snap_Center: @@ -230,14 +231,14 @@ class Draft_Snap_Center: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtoncenter": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Center', Draft_Snap_Center()) +Gui.addCommand('Draft_Snap_Center', Draft_Snap_Center()) class Draft_Snap_Extension: @@ -253,14 +254,14 @@ class Draft_Snap_Extension: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonextension": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Extension', Draft_Snap_Extension()) +Gui.addCommand('Draft_Snap_Extension', Draft_Snap_Extension()) class Draft_Snap_Near: @@ -275,14 +276,14 @@ class Draft_Snap_Near: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonpassive": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Near', Draft_Snap_Near()) +Gui.addCommand('Draft_Snap_Near', Draft_Snap_Near()) class Draft_Snap_Ortho: @@ -297,14 +298,14 @@ class Draft_Snap_Ortho: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonortho": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Ortho', Draft_Snap_Ortho()) +Gui.addCommand('Draft_Snap_Ortho', Draft_Snap_Ortho()) class Draft_Snap_Special: @@ -320,14 +321,14 @@ class Draft_Snap_Special: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonspecial": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Special', Draft_Snap_Special()) +Gui.addCommand('Draft_Snap_Special', Draft_Snap_Special()) class Draft_Snap_Dimensions: @@ -343,14 +344,14 @@ class Draft_Snap_Dimensions: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonDimensions": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Dimensions', Draft_Snap_Dimensions()) +Gui.addCommand('Draft_Snap_Dimensions', Draft_Snap_Dimensions()) class Draft_Snap_WorkingPlane: @@ -368,11 +369,11 @@ class Draft_Snap_WorkingPlane: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonWorkingPlane": b.toggle() -FreeCADGui.addCommand('Draft_Snap_WorkingPlane', Draft_Snap_WorkingPlane()) +Gui.addCommand('Draft_Snap_WorkingPlane', Draft_Snap_WorkingPlane()) diff --git a/src/Mod/Draft/draftguitools/gui_trackers.py b/src/Mod/Draft/draftguitools/gui_trackers.py index 1e952cf58b..6d2d2b87cd 100644 --- a/src/Mod/Draft/draftguitools/gui_trackers.py +++ b/src/Mod/Draft/draftguitools/gui_trackers.py @@ -1,44 +1,59 @@ -#*************************************************************************** -#* Copyright (c) 2011 Yorik van Havre * -#* * -#* This program is free software; you can redistribute it and/or modify * -#* it under the terms of the GNU Lesser General Public License (LGPL) * -#* as published by the Free Software Foundation; either version 2 of * -#* the License, or (at your option) any later version. * -#* for detail see the LICENCE text file. * -#* * -#* This program 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 program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** +# *************************************************************************** +# * Copyright (c) 2011 Yorik van Havre * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provide Coin based objects used for previews in the Draft Workbench. -__title__="FreeCAD Draft Trackers" +This module provides Coin (pivy) based objects +that are used by the Draft Workbench to draw temporary geometry, +that is, previews, of the real objects that will be created on the 3D view. +""" +## @package DraftTrackers +# \ingroup DRAFT +# \brief Provide Coin based objects used for previews in the Draft Workbench. +# +# This module provides Coin (pivy) based objects +# that are used by the Draft Workbench to draw temporary geometry, +# that is, previews, of the real objects that will be created on the 3D view. + +import math +from pivy import coin +import re + +import FreeCAD +import FreeCADGui +import Draft +import DraftVecUtils +from FreeCAD import Vector +from draftutils.todo import ToDo +from draftutils.messages import _msg + +__title__ = "FreeCAD Draft Trackers" __author__ = "Yorik van Havre" __url__ = "https://www.freecadweb.org" -## @package DraftTrackers -# \ingroup DRAFT -# \brief Custom Pivy-based objects used by the Draft workbench -# -# This module contains a collection of Coin3D (pivy)-based objects -# that are used by the Draft workbench to draw temporary geometry -# on the 3D view - -import FreeCAD,FreeCADGui,math,Draft, DraftVecUtils -from FreeCAD import Vector -from pivy import coin - class Tracker: - """A generic Draft Tracker, to be used by other specific trackers""" - def __init__(self,dotted=False,scolor=None,swidth=None,children=[],ontop=False,name=None): + """A generic Draft Tracker, to be used by other specific trackers.""" + + def __init__(self, dotted=False, scolor=None, swidth=None, + children=[], ontop=False, name=None): global Part, DraftGeomUtils import Part, DraftGeomUtils self.ontop = ontop @@ -50,163 +65,196 @@ class Tracker: if dotted: drawstyle.style = coin.SoDrawStyle.LINES drawstyle.lineWeight = 3 - drawstyle.linePattern = 0x0f0f #0xaa + drawstyle.linePattern = 0x0f0f # 0xaa node = coin.SoSeparator() for c in [drawstyle, color] + children: node.addChild(c) - self.switch = coin.SoSwitch() # this is the on/off switch + self.switch = coin.SoSwitch() # this is the on/off switch if name: self.switch.setName(name) self.switch.addChild(node) self.switch.whichChild = -1 self.Visible = False - from DraftGui import todo - todo.delay(self._insertSwitch, self.switch) + ToDo.delay(self._insertSwitch, self.switch) def finalize(self): - from DraftGui import todo - todo.delay(self._removeSwitch, self.switch) + """Finish the command by removing the switch.""" + ToDo.delay(self._removeSwitch, self.switch) self.switch = None def _insertSwitch(self, switch): - '''insert self.switch into the scene graph. Must not be called - from an event handler (or other scene graph traversal).''' - sg=Draft.get3DView().getSceneGraph() + """Insert self.switch into the scene graph. + + Must not be called + from an event handler (or other scene graph traversal). + """ + sg = Draft.get3DView().getSceneGraph() if self.ontop: - sg.insertChild(switch,0) + sg.insertChild(switch, 0) else: sg.addChild(switch) def _removeSwitch(self, switch): - '''remove self.switch from the scene graph. As with _insertSwitch, - must not be called during scene graph traversal).''' - sg=Draft.get3DView().getSceneGraph() + """Remove self.switch from the scene graph. + + As with _insertSwitch, + must not be called during scene graph traversal). + """ + sg = Draft.get3DView().getSceneGraph() if sg.findChild(switch) >= 0: sg.removeChild(switch) def on(self): + """Set the visibility to True.""" self.switch.whichChild = 0 self.Visible = True def off(self): + """Set the visibility to False.""" self.switch.whichChild = -1 self.Visible = False def lowerTracker(self): - '''lowers the tracker to the bottom of the scenegraph, so - it doesn't obscure the other objects''' + """Lower the tracker to the bottom of the scenegraph. + + So it doesn't obscure the other objects. + """ if self.switch: - sg=Draft.get3DView().getSceneGraph() + sg = Draft.get3DView().getSceneGraph() sg.removeChild(self.switch) sg.addChild(self.switch) def raiseTracker(self): - '''raises the tracker to the top of the scenegraph, so - it obscures the other objects''' + """Raise the tracker to the top of the scenegraph. + + So it obscures the other objects. + """ if self.switch: - sg=Draft.get3DView().getSceneGraph() + sg = Draft.get3DView().getSceneGraph() sg.removeChild(self.switch) - sg.insertChild(self.switch,0) - + sg.insertChild(self.switch, 0) + + class snapTracker(Tracker): - """A Snap Mark tracker, used by tools that support snapping""" + """Define Snap Mark tracker, used by tools that support snapping.""" + def __init__(self): color = coin.SoBaseColor() color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") - self.marker = coin.SoMarkerSet() # this is the marker symbol + self.marker = coin.SoMarkerSet() # this is the marker symbol self.marker.markerIndex = FreeCADGui.getMarkerIndex("", 9) - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValue((0,0,0)) + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValue((0, 0, 0)) node = coin.SoAnnotation() node.addChild(self.coords) node.addChild(color) node.addChild(self.marker) - Tracker.__init__(self,children=[node],name="snapTracker") + Tracker.__init__(self, children=[node], name="snapTracker") - def setMarker(self,style): + def setMarker(self, style): + """Set the marker index.""" self.marker.markerIndex = FreeCADGui.getMarkerIndex(style, 9) - def setCoords(self,point): - self.coords.point.setValue((point.x,point.y,point.z)) - - def addCoords(self,point): + def setCoords(self, point): + """Set the coordinates to the point.""" + self.coords.point.setValue((point.x, point.y, point.z)) + + def addCoords(self, point): + """Add the point to the current point.""" l = self.coords.point.getValues() - l.append(coin.SbVec3f(point.x,point.y,point.z)) + l.append(coin.SbVec3f(point.x, point.y, point.z)) self.coords.point.setValues(l) - + def clear(self): + """Delete the values of the point.""" self.coords.point.deleteValues(0) - + class lineTracker(Tracker): """A Line tracker, used by the tools that need to draw temporary lines""" - def __init__(self,dotted=False,scolor=None,swidth=None,ontop=False): + + def __init__(self, dotted=False, scolor=None, swidth=None, ontop=False): line = coin.SoLineSet() line.numVertices.setValue(2) - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValues(0,2,[[0,0,0],[1,0,0]]) - Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line],ontop,name="lineTracker") + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValues(0, 2, [[0, 0, 0], [1, 0, 0]]) + Tracker.__init__(self, dotted, scolor, swidth, + [self.coords, line], + ontop, name="lineTracker") - def p1(self,point=None): - """sets or gets the first point of the line""" + def p1(self, point=None): + """Set or get the first point of the line.""" if point: if self.coords.point.getValues()[0].getValue() != tuple(point): - self.coords.point.set1Value(0,point.x,point.y,point.z) + self.coords.point.set1Value(0, point.x, point.y, point.z) else: return Vector(self.coords.point.getValues()[0].getValue()) - def p2(self,point=None): - """sets or gets the second point of the line""" + def p2(self, point=None): + """Set or get the second point of the line.""" if point: if self.coords.point.getValues()[-1].getValue() != tuple(point): - self.coords.point.set1Value(1,point.x,point.y,point.z) + self.coords.point.set1Value(1, point.x, point.y, point.z) else: return Vector(self.coords.point.getValues()[-1].getValue()) - + def getLength(self): - """returns the length of the line""" + """Return the length of the line.""" p1 = Vector(self.coords.point.getValues()[0].getValue()) p2 = Vector(self.coords.point.getValues()[-1].getValue()) return (p2.sub(p1)).Length + class rectangleTracker(Tracker): - """A Rectangle tracker, used by the rectangle tool""" - def __init__(self,dotted=False,scolor=None,swidth=None,face=False): - self.origin = Vector(0,0,0) + """A Rectangle tracker, used by the rectangle tool.""" + + def __init__(self, dotted=False, scolor=None, swidth=None, face=False): + self.origin = Vector(0, 0, 0) line = coin.SoLineSet() line.numVertices.setValue(5) - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValues(0,50,[[0,0,0],[2,0,0],[2,2,0],[0,2,0],[0,0,0]]) + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValues(0, 50, [[0, 0, 0], + [2, 0, 0], + [2, 2, 0], + [0, 2, 0], + [0, 0, 0]]) if face: m1 = coin.SoMaterial() m1.transparency.setValue(0.5) - m1.diffuseColor.setValue([0.5,0.5,1.0]) + m1.diffuseColor.setValue([0.5, 0.5, 1.0]) f = coin.SoIndexedFaceSet() - f.coordIndex.setValues([0,1,2,3]) - Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line,m1,f],name="rectangleTracker") + f.coordIndex.setValues([0, 1, 2, 3]) + Tracker.__init__(self, dotted, scolor, swidth, + [self.coords, line, m1, f], + name="rectangleTracker") else: - Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line],name="rectangleTracker") + Tracker.__init__(self, dotted, scolor, swidth, + [self.coords, line], + name="rectangleTracker") self.u = FreeCAD.DraftWorkingPlane.u self.v = FreeCAD.DraftWorkingPlane.v - def setorigin(self,point): - """sets the base point of the rectangle""" - self.coords.point.set1Value(0,point.x,point.y,point.z) - self.coords.point.set1Value(4,point.x,point.y,point.z) + def setorigin(self, point): + """Set the base point of the rectangle.""" + self.coords.point.set1Value(0, point.x, point.y, point.z) + self.coords.point.set1Value(4, point.x, point.y, point.z) self.origin = point - def update(self,point): - """sets the opposite (diagonal) point of the rectangle""" + def update(self, point): + """Set the opposite (diagonal) point of the rectangle.""" diagonal = point.sub(self.origin) - inpoint1 = self.origin.add(DraftVecUtils.project(diagonal,self.v)) - inpoint2 = self.origin.add(DraftVecUtils.project(diagonal,self.u)) - self.coords.point.set1Value(1,inpoint1.x,inpoint1.y,inpoint1.z) - self.coords.point.set1Value(2,point.x,point.y,point.z) - self.coords.point.set1Value(3,inpoint2.x,inpoint2.y,inpoint2.z) + inpoint1 = self.origin.add(DraftVecUtils.project(diagonal, self.v)) + inpoint2 = self.origin.add(DraftVecUtils.project(diagonal, self.u)) + self.coords.point.set1Value(1, inpoint1.x, inpoint1.y, inpoint1.z) + self.coords.point.set1Value(2, point.x, point.y, point.z) + self.coords.point.set1Value(3, inpoint2.x, inpoint2.y, inpoint2.z) - def setPlane(self,u,v=None): - '''sets given (u,v) vectors as working plane. You can give only u - and v will be deduced automatically given current workplane''' + def setPlane(self, u, v=None): + """Set given (u,v) vectors as working plane. + + You can give only `u` and `v` will be deduced automatically + given the current working plane. + """ self.u = u if v: self.v = v @@ -214,64 +262,73 @@ class rectangleTracker(Tracker): norm = FreeCAD.DraftWorkingPlane.u.cross(FreeCAD.DraftWorkingPlane.v) self.v = self.u.cross(norm) - def p1(self,point=None): - """sets or gets the base point of the rectangle""" + def p1(self, point=None): + """Set or get the base point of the rectangle.""" if point: self.setorigin(point) else: return Vector(self.coords.point.getValues()[0].getValue()) def p2(self): - """gets the second point (on u axis) of the rectangle""" + """Get the second point (on u axis) of the rectangle.""" return Vector(self.coords.point.getValues()[3].getValue()) - def p3(self,point=None): - """sets or gets the opposite (diagonal) point of the rectangle""" + def p3(self, point=None): + """Set or get the opposite (diagonal) point of the rectangle.""" if point: self.update(point) else: return Vector(self.coords.point.getValues()[2].getValue()) def p4(self): - """gets the fourth point (on v axis) of the rectangle""" + """Get the fourth point (on v axis) of the rectangle.""" return Vector(self.coords.point.getValues()[1].getValue()) - + def getSize(self): - """returns (length,width) of the rectangle""" + """Return (length, width) of the rectangle.""" p1 = Vector(self.coords.point.getValues()[0].getValue()) p2 = Vector(self.coords.point.getValues()[2].getValue()) diag = p2.sub(p1) - return ((DraftVecUtils.project(diag,self.u)).Length,(DraftVecUtils.project(diag,self.v)).Length) + return ((DraftVecUtils.project(diag, self.u)).Length, + (DraftVecUtils.project(diag, self.v)).Length) def getNormal(self): - """returns the normal of the rectangle""" + """Return the normal of the rectangle.""" return (self.u.cross(self.v)).normalize() - - def isInside(self,point): - """returns True if the given point is inside the rectangle""" + + def isInside(self, point): + """Return True if the given point is inside the rectangle.""" vp = point.sub(self.p1()) uv = self.p2().sub(self.p1()) vv = self.p4().sub(self.p1()) - uvp = DraftVecUtils.project(vp,uv) - vvp = DraftVecUtils.project(vp,vv) + uvp = DraftVecUtils.project(vp, uv) + vvp = DraftVecUtils.project(vp, vv) if uvp.getAngle(uv) < 1: if vvp.getAngle(vv) < 1: if uvp.Length <= uv.Length: if vvp.Length <= vv.Length: return True return False - + + class dimTracker(Tracker): - """A Dimension tracker, used by the dimension tool""" - def __init__(self,dotted=False,scolor=None,swidth=None): + """A Dimension tracker, used by the dimension tool.""" + + def __init__(self, dotted=False, scolor=None, swidth=None): line = coin.SoLineSet() line.numVertices.setValue(4) - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValues(0,4,[[0,0,0],[0,0,0],[0,0,0],[0,0,0]]) - Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line],name="dimTracker") + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValues(0, 4, + [[0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0]]) + Tracker.__init__(self, dotted, scolor, swidth, + [self.coords, line], name="dimTracker") self.p1 = self.p2 = self.p3 = None - def update(self,pts): + def update(self, pts): + """Update the points and calculate.""" if not pts: return elif len(pts) == 1: @@ -282,50 +339,62 @@ class dimTracker(Tracker): if len(pts) > 2: self.p3 = pts[2] self.calc() - + def calc(self): + """Calculate the new points from p1 and p2.""" import Part - if (self.p1 != None) and (self.p2 != None): - points = [DraftVecUtils.tup(self.p1,True),DraftVecUtils.tup(self.p2,True),\ - DraftVecUtils.tup(self.p1,True),DraftVecUtils.tup(self.p2,True)] - if self.p3 != None: + if (self.p1 is not None) and (self.p2 is not None): + points = [DraftVecUtils.tup(self.p1, True), + DraftVecUtils.tup(self.p2, True), + DraftVecUtils.tup(self.p1, True), + DraftVecUtils.tup(self.p2, True)] + if self.p3 is not None: p1 = self.p1 p4 = self.p2 - if DraftVecUtils.equals(p1,p4): + if DraftVecUtils.equals(p1, p4): proj = None else: - base = Part.LineSegment(p1,p4).toShape() - proj = DraftGeomUtils.findDistance(self.p3,base) + base = Part.LineSegment(p1, p4).toShape() + proj = DraftGeomUtils.findDistance(self.p3, base) if not proj: p2 = p1 p3 = p4 else: p2 = p1.add(proj.negative()) p3 = p4.add(proj.negative()) - points = [DraftVecUtils.tup(p1),DraftVecUtils.tup(p2),DraftVecUtils.tup(p3),DraftVecUtils.tup(p4)] - self.coords.point.setValues(0,4,points) + points = [DraftVecUtils.tup(p1), + DraftVecUtils.tup(p2), + DraftVecUtils.tup(p3), + DraftVecUtils.tup(p4)] + self.coords.point.setValues(0, 4, points) + class bsplineTracker(Tracker): - """A bspline tracker""" - def __init__(self,dotted=False,scolor=None,swidth=None,points = []): + """A bspline tracker.""" + + def __init__(self, dotted=False, scolor=None, swidth=None, points=[]): self.bspline = None self.points = points self.trans = coin.SoTransform() self.sep = coin.SoSeparator() self.recompute() - Tracker.__init__(self,dotted,scolor,swidth,[self.trans,self.sep],name="bsplineTracker") - + Tracker.__init__(self, dotted, scolor, swidth, + [self.trans, self.sep], name="bsplineTracker") + def update(self, points): + """Update the points and recompute.""" self.points = points self.recompute() - + def recompute(self): - if (len(self.points) >= 2): - if self.bspline: self.sep.removeChild(self.bspline) + """Recompute the tracker.""" + if len(self.points) >= 2: + if self.bspline: + self.sep.removeChild(self.bspline) self.bspline = None c = Part.BSplineCurve() # DNC: allows to close the curve by placing ends close to each other - if ( len(self.points) >= 3 ) and ( (self.points[0] - self.points[-1]).Length < Draft.tolerance() ): + if len(self.points) >= 3 and ( (self.points[0] - self.points[-1]).Length < Draft.tolerance() ): # YVH: Added a try to bypass some hazardous situations try: c.interpolate(self.points[:-1], True) @@ -337,26 +406,25 @@ class bsplineTracker(Tracker): except Part.OCCError: pass c = c.toShape() - buf=c.writeInventor(2,0.01) - #fp=open("spline.iv","w") - #fp.write(buf) - #fp.close() + buf = c.writeInventor(2, 0.01) + # fp = open("spline.iv", "w") + # fp.write(buf) + # fp.close() try: ivin = coin.SoInput() ivin.setBuffer(buf) ivob = coin.SoDB.readAll(ivin) - except: + except Exception: # workaround for pivy SoInput.setBuffer() bug - import re - buf = buf.replace("\n","") - pts = re.findall("point \[(.*?)\]",buf)[0] + buf = buf.replace("\n", "") + pts = re.findall("point \[(.*?)\]", buf)[0] pts = pts.split(",") pc = [] for p in pts: v = p.strip().split() - pc.append([float(v[0]),float(v[1]),float(v[2])]) + pc.append([float(v[0]), float(v[1]), float(v[2])]) coords = coin.SoCoordinate3() - coords.point.setValues(0,len(pc),pc) + coords.point.setValues(0, len(pc), pc) line = coin.SoLineSet() line.numVertices.setValue(-1) self.bspline = coin.SoSeparator() @@ -371,73 +439,70 @@ class bsplineTracker(Tracker): self.sep.addChild(self.bspline) else: FreeCAD.Console.PrintWarning("bsplineTracker.recompute() failed to read-in Inventor string\n") -####################################### + + class bezcurveTracker(Tracker): - """A bezcurve tracker""" - def __init__(self,dotted=False,scolor=None,swidth=None,points = []): + """A bezcurve tracker.""" + + def __init__(self, dotted=False, scolor=None, swidth=None, points=[]): self.bezcurve = None self.points = points self.degree = None self.trans = coin.SoTransform() self.sep = coin.SoSeparator() self.recompute() - Tracker.__init__(self,dotted,scolor,swidth,[self.trans,self.sep],name="bezcurveTracker") - + Tracker.__init__(self, dotted, scolor, swidth, + [self.trans, self.sep], name="bezcurveTracker") + def update(self, points, degree=None): + """Update the points and recompute.""" self.points = points if degree: self.degree = degree self.recompute() - + def recompute(self): - + """Recompute the tracker.""" if self.bezcurve: for seg in self.bezcurve: self.sep.removeChild(seg) seg = None - + self.bezcurve = [] - + if (len(self.points) >= 2): - if self.degree: - - poles=self.points[1:] - + poles = self.points[1:] segpoleslst = [poles[x:x+self.degree] for x in range(0, len(poles), (self.degree or 1))] else: segpoleslst = [self.points] - - startpoint=self.points[0] + startpoint = self.points[0] - for segpoles in segpoleslst: - c = Part.BezierCurve() #last segment may have lower degree + c = Part.BezierCurve() # last segment may have lower degree c.increase(len(segpoles)) - c.setPoles([startpoint]+segpoles) + c.setPoles([startpoint] + segpoles) c = c.toShape() startpoint = segpoles[-1] - - buf=c.writeInventor(2,0.01) - #fp=open("spline.iv","w") - #fp.write(buf) - #fp.close() + buf = c.writeInventor(2, 0.01) + # fp=open("spline.iv", "w") + # fp.write(buf) + # fp.close() try: ivin = coin.SoInput() ivin.setBuffer(buf) ivob = coin.SoDB.readAll(ivin) - except: + except Exception: # workaround for pivy SoInput.setBuffer() bug - import re buf = buf.replace("\n","") - pts = re.findall("point \[(.*?)\]",buf)[0] + pts = re.findall("point \[(.*?)\]", buf)[0] pts = pts.split(",") pc = [] for p in pts: v = p.strip().split() - pc.append([float(v[0]),float(v[1]),float(v[2])]) + pc.append([float(v[0]), float(v[1]), float(v[2])]) coords = coin.SoCoordinate3() - coords.point.setValues(0,len(pc),pc) + coords.point.setValues(0, len(pc), pc) line = coin.SoLineSet() line.numVertices.setValue(-1) bezcurveseg = coin.SoSeparator() @@ -454,16 +519,17 @@ class bezcurveTracker(Tracker): FreeCAD.Console.PrintWarning("bezcurveTracker.recompute() failed to read-in Inventor string\n") self.bezcurve.append(bezcurveseg) - -####################################### + class arcTracker(Tracker): - """An arc tracker""" - def __init__(self,dotted=False,scolor=None,swidth=None,start=0,end=math.pi*2,normal=None): + """An arc tracker.""" + + def __init__(self, dotted=False, scolor=None, swidth=None, + start=0, end=math.pi*2, normal=None): self.circle = None self.startangle = math.degrees(start) self.endangle = math.degrees(end) self.trans = coin.SoTransform() - self.trans.translation.setValue([0,0,0]) + self.trans.translation.setValue([0, 0, 0]) self.sep = coin.SoSeparator() self.autoinvert = True if normal: @@ -472,72 +538,74 @@ class arcTracker(Tracker): self.normal = FreeCAD.DraftWorkingPlane.axis self.basevector = self.getDeviation() self.recompute() - Tracker.__init__(self,dotted,scolor,swidth,[self.trans, self.sep],name="arcTracker") - + Tracker.__init__(self, dotted, scolor, swidth, + [self.trans, self.sep], name="arcTracker") + def getDeviation(self): - """returns a deviation vector that represents the base of the circle""" + """Return a deviation vector that represents the base of the circle.""" import Part - c = Part.makeCircle(1,Vector(0,0,0),self.normal) + c = Part.makeCircle(1, Vector(0, 0, 0), self.normal) return c.Vertexes[0].Point - def setCenter(self,cen): - """sets the center point""" - self.trans.translation.setValue([cen.x,cen.y,cen.z]) + def setCenter(self, cen): + """Set the center point.""" + self.trans.translation.setValue([cen.x, cen.y, cen.z]) - def setRadius(self,rad): - """sets the radius""" - self.trans.scaleFactor.setValue([rad,rad,rad]) + def setRadius(self, rad): + """Set the radius.""" + self.trans.scaleFactor.setValue([rad, rad, rad]) def getRadius(self): - """returns the current radius""" + """Return the current radius.""" return self.trans.scaleFactor.getValue()[0] - def setStartAngle(self,ang): - """sets the start angle""" + def setStartAngle(self, ang): + """Set the start angle.""" self.startangle = math.degrees(ang) self.recompute() - def setEndAngle(self,ang): - """sets the end angle""" + def setEndAngle(self, ang): + """Set the end angle.""" self.endangle = math.degrees(ang) self.recompute() - def getAngle(self,pt): - """returns the angle of a given vector in radians""" + def getAngle(self, pt): + """Return the angle of a given vector in radians.""" c = self.trans.translation.getValue() - center = Vector(c[0],c[1],c[2]) + center = Vector(c[0], c[1], c[2]) rad = pt.sub(center) - a = DraftVecUtils.angle(rad,self.basevector,self.normal) - #print(a) - return(a) + a = DraftVecUtils.angle(rad, self.basevector, self.normal) + # print(a) + return a def getAngles(self): - """returns the start and end angles in degrees""" - return(self.startangle,self.endangle) - - def setStartPoint(self,pt): - """sets the start angle from a point""" + """Return the start and end angles in degrees.""" + return(self.startangle, self.endangle) + + def setStartPoint(self, pt): + """Set the start angle from a point.""" self.setStartAngle(-self.getAngle(pt)) - def setEndPoint(self,pt): - """sets the end angle from a point""" + def setEndPoint(self, pt): + """Set the end angle from a point.""" self.setEndAngle(-self.getAngle(pt)) - - def setApertureAngle(self,ang): - """sets the end angle by giving the aperture angle""" + + def setApertureAngle(self, ang): + """Set the end angle by giving the aperture angle.""" ap = math.degrees(ang) self.endangle = self.startangle + ap self.recompute() - def setBy3Points(self,p1,p2,p3): - """sets the arc by three points""" + def setBy3Points(self, p1, p2, p3): + """Set the arc by three points.""" import Part try: - arc=Part.ArcOfCircle(p1,p2,p3) - except: return - e=arc.toShape() + arc = Part.ArcOfCircle(p1, p2, p3) + except Exception: + return + e = arc.toShape() self.autoinvert = False - self.normal = e.Curve.Axis.negative() # axis is always in wrong direction + self.normal = e.Curve.Axis.negative() # axis is always in wrong direction self.basevector = self.getDeviation() self.setCenter(e.Curve.Center) self.setRadius(e.Curve.Radius) @@ -545,30 +613,33 @@ class arcTracker(Tracker): self.setEndPoint(p3) def recompute(self): - import Part,re - if self.circle: + """Recompute the tracker.""" + import Part + if self.circle: self.sep.removeChild(self.circle) self.circle = None if (self.endangle < self.startangle) or not self.autoinvert: - c = Part.makeCircle(1,Vector(0,0,0),self.normal,self.endangle,self.startangle) + c = Part.makeCircle(1, Vector(0, 0, 0), + self.normal, self.endangle, self.startangle) else: - c = Part.makeCircle(1,Vector(0,0,0),self.normal,self.startangle,self.endangle) - buf=c.writeInventor(2,0.01) + c = Part.makeCircle(1, Vector(0, 0, 0), + self.normal, self.startangle, self.endangle) + buf = c.writeInventor(2, 0.01) try: ivin = coin.SoInput() ivin.setBuffer(buf) ivob = coin.SoDB.readAll(ivin) - except: + except Exception: # workaround for pivy SoInput.setBuffer() bug - buf = buf.replace("\n","") - pts = re.findall("point \[(.*?)\]",buf)[0] + buf = buf.replace("\n", "") + pts = re.findall("point \[(.*?)\]", buf)[0] pts = pts.split(",") pc = [] for p in pts: v = p.strip().split() - pc.append([float(v[0]),float(v[1]),float(v[2])]) + pc.append([float(v[0]), float(v[1]), float(v[2])]) coords = coin.SoCoordinate3() - coords.point.setValues(0,len(pc),pc) + coords.point.setValues(0, len(pc), pc) line = coin.SoLineSet() line.numVertices.setValue(-1) self.circle = coin.SoSeparator() @@ -586,14 +657,17 @@ class arcTracker(Tracker): class ghostTracker(Tracker): - '''A Ghost tracker, that allows to copy whole object representations. - You can pass it an object or a list of objects, or a shape.''' - def __init__(self,sel,dotted=False,scolor=None,swidth=None): + """A Ghost tracker, that allows to copy whole object representations. + + You can pass it an object or a list of objects, or a shape. + """ + + def __init__(self, sel, dotted=False, scolor=None, swidth=None): self.trans = coin.SoTransform() - self.trans.translation.setValue([0,0,0]) + self.trans.translation.setValue([0, 0, 0]) self.children = [self.trans] rootsep = coin.SoSeparator() - if not isinstance(sel,list): + if not isinstance(sel, list): sel = [sel] for obj in sel: import Part @@ -601,10 +675,10 @@ class ghostTracker(Tracker): rootsep.addChild(self.getNode(obj)) else: self.coords = coin.SoCoordinate3() - self.coords.point.setValue((obj.X,obj.Y,obj.Z)) + self.coords.point.setValue((obj.X, obj.Y, obj.Z)) color = coin.SoBaseColor() color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") - self.marker = coin.SoMarkerSet() # this is the marker symbol + self.marker = coin.SoMarkerSet() # this is the marker symbol self.marker.markerIndex = FreeCADGui.getMarkerIndex("quad", 9) node = coin.SoAnnotation() selnode = coin.SoSeparator() @@ -613,63 +687,67 @@ class ghostTracker(Tracker): selnode.addChild(self.marker) node.addChild(selnode) rootsep.addChild(node) - self.children.append(rootsep) - Tracker.__init__(self,dotted,scolor,swidth,children=self.children,name="ghostTracker") + self.children.append(rootsep) + Tracker.__init__(self, dotted, scolor, swidth, + children=self.children, name="ghostTracker") - def update(self,obj): - """recreates the ghost from a new object""" + def update(self, obj): + """Recreate the ghost from a new object.""" obj.ViewObject.show() self.finalize() sep = self.getNode(obj) - Tracker.__init__(self,children=[self.sep]) + Tracker.__init__(self, children=[self.sep]) self.on() obj.ViewObject.hide() - def move(self,delta): - """moves the ghost to a given position, relative from its start position""" - self.trans.translation.setValue([delta.x,delta.y,delta.z]) + def move(self, delta): + """Move the ghost to a given position. - def rotate(self,axis,angle): - """rotates the ghost of a given angle""" - self.trans.rotation.setValue(coin.SbVec3f(DraftVecUtils.tup(axis)),angle) + Relative from its start position. + """ + self.trans.translation.setValue([delta.x, delta.y, delta.z]) - def center(self,point): - """sets the rotation/scale center of the ghost""" - self.trans.center.setValue(point.x,point.y,point.z) + def rotate(self, axis, angle): + """Rotate the ghost of a given angle.""" + self.trans.rotation.setValue(coin.SbVec3f(DraftVecUtils.tup(axis)), angle) - def scale(self,delta): - """scales the ghost by the given factor""" - self.trans.scaleFactor.setValue([delta.x,delta.y,delta.z]) + def center(self, point): + """Set the rotation/scale center of the ghost.""" + self.trans.center.setValue(point.x, point.y, point.z) - def getNode(self,obj): - """returns a coin node representing the given object""" + def scale(self, delta): + """Scale the ghost by the given factor.""" + self.trans.scaleFactor.setValue([delta.x, delta.y, delta.z]) + + def getNode(self, obj): + """Return a coin node representing the given object.""" import Part - if isinstance(obj,Part.Shape): + if isinstance(obj, Part.Shape): return self.getNodeLight(obj) elif obj.isDerivedFrom("Part::Feature"): return self.getNodeFull(obj) else: return self.getNodeFull(obj) - def getNodeFull(self,obj): - """gets a coin node which is a full copy of the current representation""" + def getNodeFull(self, obj): + """Get a coin node which is a copy of the current representation.""" sep = coin.SoSeparator() try: sep.addChild(obj.ViewObject.RootNode.copy()) # add Part container offset - if hasattr(obj,"getGlobalPlacement"): + if hasattr(obj, "getGlobalPlacement"): if obj.Placement != obj.getGlobalPlacement(): if sep.getChild(0).getNumChildren() > 0: if isinstance(sep.getChild(0).getChild(0),coin.SoTransform): gpl = obj.getGlobalPlacement() sep.getChild(0).getChild(0).translation.setValue(tuple(gpl.Base)) sep.getChild(0).getChild(0).rotation.setValue(gpl.Rotation.Q) - except: - print("ghostTracker: Error retrieving coin node (full)") + except Exception: + _msg("ghostTracker: Error retrieving coin node (full)") return sep - def getNodeLight(self,shape): - """extract a lighter version directly from a shape""" + def getNodeLight(self, shape): + """Extract a lighter version directly from a shape.""" # error-prone sep = coin.SoSeparator() try: @@ -679,43 +757,48 @@ class ghostTracker(Tracker): # only add wireframe or full node? sep.addChild(coinobj.getChildren()[1]) # sep.addChild(coinobj) - except: - print("ghostTracker: Error retrieving coin node (light)") + except Exception: + _msg("ghostTracker: Error retrieving coin node (light)") return sep - + def getMatrix(self): + """Get matrix of the active view.""" r = FreeCADGui.ActiveDocument.ActiveView.getViewer().getSoRenderManager().getViewportRegion() v = coin.SoGetMatrixAction(r) m = self.trans.getMatrix(v) if m: m = m.getValue() - return FreeCAD.Matrix(m[0][0],m[0][1],m[0][2],m[0][3], - m[1][0],m[1][1],m[1][2],m[1][3], - m[2][0],m[2][1],m[2][2],m[2][3], - m[3][0],m[3][1],m[3][2],m[3][3]) + return FreeCAD.Matrix(m[0][0], m[0][1], m[0][2], m[0][3], + m[1][0], m[1][1], m[1][2], m[1][3], + m[2][0], m[2][1], m[2][2], m[2][3], + m[3][0], m[3][1], m[3][2], m[3][3]) else: return FreeCAD.Matrix() - - def setMatrix(self,matrix): - m = coin.SbMatrix(matrix.A11,matrix.A12,matrix.A13,matrix.A14, - matrix.A21,matrix.A22,matrix.A23,matrix.A24, - matrix.A31,matrix.A32,matrix.A33,matrix.A34, - matrix.A41,matrix.A42,matrix.A43,matrix.A44) + + def setMatrix(self, matrix): + """Set the transformation matrix.""" + m = coin.SbMatrix(matrix.A11, matrix.A12, matrix.A13, matrix.A14, + matrix.A21, matrix.A22, matrix.A23, matrix.A24, + matrix.A31, matrix.A32, matrix.A33, matrix.A34, + matrix.A41, matrix.A42, matrix.A43, matrix.A44) self.trans.setMatrix(m) + class editTracker(Tracker): - """A node edit tracker""" - def __init__(self,pos=Vector(0,0,0),name=None,idx=0,objcol=None,\ - marker=FreeCADGui.getMarkerIndex("quad", 9),inactive=False): + """A node edit tracker.""" + + def __init__(self, pos=Vector(0, 0, 0), name=None, idx=0, objcol=None, + marker=FreeCADGui.getMarkerIndex("quad", 9), + inactive=False): self.color = coin.SoBaseColor() if objcol: self.color.rgb = objcol[:3] else: self.color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") - self.marker = coin.SoMarkerSet() # this is the marker symbol + self.marker = coin.SoMarkerSet() # this is the marker symbol self.marker.markerIndex = marker - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValue((pos.x,pos.y,pos.z)) + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValue((pos.x, pos.y, pos.z)) if inactive: self.selnode = coin.SoSeparator() else: @@ -724,72 +807,89 @@ class editTracker(Tracker): self.selnode.useNewSelection = False self.selnode.documentName.setValue(FreeCAD.ActiveDocument.Name) self.selnode.objectName.setValue(name) - self.selnode.subElementName.setValue("EditNode"+str(idx)) + self.selnode.subElementName.setValue("EditNode" + str(idx)) node = coin.SoAnnotation() self.selnode.addChild(self.coords) self.selnode.addChild(self.color) self.selnode.addChild(self.marker) node.addChild(self.selnode) ontop = not inactive - Tracker.__init__(self,children=[node],ontop=ontop,name="editTracker") + Tracker.__init__(self, children=[node], + ontop=ontop, name="editTracker") self.on() - def set(self,pos): - self.coords.point.setValue((pos.x,pos.y,pos.z)) + def set(self, pos): + """Set the point to the position.""" + self.coords.point.setValue((pos.x, pos.y, pos.z)) def get(self): + """Get a vector from the point.""" p = self.coords.point.getValues()[0] - return Vector(p[0],p[1],p[2]) + return Vector(p[0], p[1], p[2]) def get_doc_name(self): + """Get the document name.""" return str(self.selnode.documentName.getValue()) def get_obj_name(self): + """Get the object name.""" return str(self.selnode.objectName.getValue()) def get_subelement_name(self): + """Get the subelement name.""" return str(self.selnode.subElementName.getValue()) def get_subelement_index(self): + """Get the subelement index.""" subElement = self.get_subelement_name() idx = int(subElement[8:]) return idx - def move(self,delta): + def move(self, delta): + """Get the point and add a delta, and set the new point.""" self.set(self.get().add(delta)) - def setColor(self,color): + def setColor(self, color): + """Set the color.""" if color: self.color.rgb = color else: self.color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") + class PlaneTracker(Tracker): - """A working plane tracker""" + """A working plane tracker.""" + def __init__(self): # getting screen distance - p1 = Draft.get3DView().getPoint((100,100)) - p2 = Draft.get3DView().getPoint((110,100)) - bl = (p2.sub(p1)).Length * (Draft.getParam("snapRange", 8)/2) + p1 = Draft.get3DView().getPoint((100, 100)) + p2 = Draft.get3DView().getPoint((110, 100)) + bl = (p2.sub(p1)).Length * (Draft.getParam("snapRange", 8)/2.0) pick = coin.SoPickStyle() pick.style.setValue(coin.SoPickStyle.UNPICKABLE) self.trans = coin.SoTransform() - self.trans.translation.setValue([0,0,0]) + self.trans.translation.setValue([0, 0, 0]) m1 = coin.SoMaterial() m1.transparency.setValue(0.8) - m1.diffuseColor.setValue([0.4,0.4,0.6]) + m1.diffuseColor.setValue([0.4, 0.4, 0.6]) c1 = coin.SoCoordinate3() - c1.point.setValues([[-bl,-bl,0],[bl,-bl,0],[bl,bl,0],[-bl,bl,0]]) + c1.point.setValues([[-bl, -bl, 0], + [bl, -bl, 0], + [bl, bl, 0], + [-bl, bl, 0]]) f = coin.SoIndexedFaceSet() - f.coordIndex.setValues([0,1,2,3]) + f.coordIndex.setValues([0, 1, 2, 3]) m2 = coin.SoMaterial() m2.transparency.setValue(0.7) - m2.diffuseColor.setValue([0.2,0.2,0.3]) + m2.diffuseColor.setValue([0.2, 0.2, 0.3]) c2 = coin.SoCoordinate3() - c2.point.setValues([[0,bl,0],[0,0,0],[bl,0,0],[-.05*bl,.95*bl,0],[0,bl,0], - [.05*bl,.95*bl,0],[.95*bl,.05*bl,0],[bl,0,0],[.95*bl,-.05*bl,0]]) + c2.point.setValues([[0, bl, 0], [0, 0, 0], + [bl, 0, 0], [-0.05*bl, 0.95*bl, 0], + [0, bl, 0], [0.05*bl, 0.95*bl, 0], + [0.95*bl, 0.05*bl, 0], [bl, 0, 0], + [0.95*bl, -0.05*bl, 0]]) l = coin.SoLineSet() - l.numVertices.setValues([3,3,3]) + l.numVertices.setValues([3, 3, 3]) s = coin.SoSeparator() s.addChild(pick) s.addChild(self.trans) @@ -799,22 +899,25 @@ class PlaneTracker(Tracker): s.addChild(m2) s.addChild(c2) s.addChild(l) - Tracker.__init__(self,children=[s],name="planeTracker") + Tracker.__init__(self, children=[s], name="planeTracker") - def set(self,pos=None): - if pos: + def set(self, pos=None): + """Set the translation to the position.""" + if pos: Q = FreeCAD.DraftWorkingPlane.getRotation().Rotation.Q else: plm = FreeCAD.DraftWorkingPlane.getPlacement() Q = plm.Rotation.Q pos = plm.Base - self.trans.translation.setValue([pos.x,pos.y,pos.z]) - self.trans.rotation.setValue([Q[0],Q[1],Q[2],Q[3]]) + self.trans.translation.setValue([pos.x, pos.y, pos.z]) + self.trans.rotation.setValue([Q[0], Q[1], Q[2], Q[3]]) self.on() - -class wireTracker(Tracker): - """A wire tracker""" - def __init__(self,wire): + + +class wireTracker(Tracker): + """A wire tracker.""" + + def __init__(self, wire): self.line = coin.SoLineSet() self.closed = DraftGeomUtils.isReallyClosed(wire) if self.closed: @@ -823,36 +926,41 @@ class wireTracker(Tracker): self.line.numVertices.setValue(len(wire.Vertexes)) self.coords = coin.SoCoordinate3() self.update(wire) - Tracker.__init__(self,children=[self.coords,self.line],name="wireTracker") + Tracker.__init__(self, children=[self.coords, self.line], + name="wireTracker") - def update(self,wire,forceclosed=False): + def update(self, wire, forceclosed=False): + """Update the tracker.""" if wire: if self.closed or forceclosed: - self.line.numVertices.setValue(len(wire.Vertexes)+1) + self.line.numVertices.setValue(len(wire.Vertexes) + 1) else: self.line.numVertices.setValue(len(wire.Vertexes)) for i in range(len(wire.Vertexes)): - p=wire.Vertexes[i].Point - self.coords.point.set1Value(i,[p.x,p.y,p.z]) + p = wire.Vertexes[i].Point + self.coords.point.set1Value(i, [p.x, p.y, p.z]) if self.closed or forceclosed: t = len(wire.Vertexes) p = wire.Vertexes[0].Point - self.coords.point.set1Value(t,[p.x,p.y,p.z]) + self.coords.point.set1Value(t, [p.x, p.y, p.z]) - def updateFromPointlist(self,points,forceclosed=False): + def updateFromPointlist(self, points, forceclosed=False): + """Update the tracker from points.""" if points: for i in range(len(points)): - p=points[i] - self.coords.point.set1Value(i,[p.x,p.y,p.z]) + p = points[i] + self.coords.point.set1Value(i, [p.x, p.y, p.z]) + class gridTracker(Tracker): - """A grid tracker""" + """A grid tracker.""" + def __init__(self): col = self.getGridColor() pick = coin.SoPickStyle() pick.style.setValue(coin.SoPickStyle.UNPICKABLE) self.trans = coin.SoTransform() - self.trans.translation.setValue([0,0,0]) + self.trans.translation.setValue([0, 0, 0]) mat1 = coin.SoMaterial() mat1.transparency.setValue(0.7) mat1.diffuseColor.setValue(col) @@ -881,46 +989,48 @@ class gridTracker(Tracker): s.addChild(mat3) s.addChild(self.coords3) s.addChild(self.lines3) - Tracker.__init__(self,children=[s],name="gridTracker") + Tracker.__init__(self, children=[s], name="gridTracker") self.reset() def getGridColor(self): + """Get the grid color from the parameter editor.""" color = Draft.getParam("gridColor", 842157055) - r = ((color>>24)&0xFF)/255 - g = ((color>>16)&0xFF)/255 - b = ((color>>8)&0xFF)/255 + r = ((color >> 24) & 0xFF) / 255 + g = ((color >> 16) & 0xFF) / 255 + b = ((color >> 8) & 0xFF) / 255 return [r, g, b] def update(self): - """redraws the grid""" - # resize the grid to make sure it fits an exact pair number of main lines - numlines = self.numlines//self.mainlines//2*2*self.mainlines - bound = (numlines//2)*self.space + """Redraw the grid.""" + # Resize the grid to make sure it fits + # an exact pair number of main lines + numlines = self.numlines // self.mainlines // 2 * 2 * self.mainlines + bound = (numlines // 2) * self.space pts = [] mpts = [] apts = [] - for i in range(numlines+1): - curr = -bound + i*self.space + for i in range(numlines + 1): + curr = -bound + i * self.space z = 0 - if i/float(self.mainlines) == i//self.mainlines: - if round(curr,4) == 0: - apts.extend([[-bound,curr,z],[bound,curr,z]]) - apts.extend([[curr,-bound,z],[curr,bound,z]]) + if i / float(self.mainlines) == i // self.mainlines: + if round(curr, 4) == 0: + apts.extend([[-bound, curr, z], [bound, curr, z]]) + apts.extend([[curr, -bound, z], [curr, bound, z]]) else: - mpts.extend([[-bound,curr,z],[bound,curr,z]]) - mpts.extend([[curr,-bound,z],[curr,bound,z]]) + mpts.extend([[-bound, curr, z], [bound, curr, z]]) + mpts.extend([[curr, -bound, z], [curr, bound, z]]) else: - pts.extend([[-bound,curr,z],[bound,curr,z]]) - pts.extend([[curr,-bound,z],[curr,bound,z]]) + pts.extend([[-bound, curr, z], [bound, curr, z]]) + pts.extend([[curr, -bound, z], [curr, bound, z]]) if pts != self.pts: idx = [] midx = [] aidx = [] - for p in range(0,len(pts),2): + for p in range(0, len(pts), 2): idx.append(2) - for mp in range(0,len(mpts),2): + for mp in range(0, len(mpts), 2): midx.append(2) - for ap in range(0,len(apts),2): + for ap in range(0, len(apts), 2): aidx.append(2) self.lines1.numVertices.deleteValues(0) self.lines2.numVertices.deleteValues(0) @@ -932,52 +1042,57 @@ class gridTracker(Tracker): self.coords3.point.setValues(apts) self.lines3.numVertices.setValues(aidx) self.pts = pts - - def setSize(self,size): + + def setSize(self, size): + """Set size of the lines and update.""" self.numlines = size self.update() - def setSpacing(self,space): + def setSpacing(self, space): + """Set spacing and update.""" self.space = space self.update() - def setMainlines(self,ml): + def setMainlines(self, ml): + """Set mainlines and update.""" self.mainlines = ml self.update() - + def reset(self): - """resets the grid according to preferences settings""" - self.space = Draft.getParam("gridSpacing",1) - self.mainlines = Draft.getParam("gridEvery",10) - self.numlines = Draft.getParam("gridSize",100) + """Reset the grid according to preferences settings.""" + self.space = Draft.getParam("gridSpacing", 1) + self.mainlines = Draft.getParam("gridEvery", 10) + self.numlines = Draft.getParam("gridSize", 100) self.update() def set(self): - """moves and rotates the grid according to the current WP""" + """Move and rotate the grid according to the current working plane.""" self.reset() Q = FreeCAD.DraftWorkingPlane.getRotation().Rotation.Q P = FreeCAD.DraftWorkingPlane.position - self.trans.rotation.setValue([Q[0],Q[1],Q[2],Q[3]]) - self.trans.translation.setValue([P.x,P.y,P.z]) + self.trans.rotation.setValue([Q[0], Q[1], Q[2], Q[3]]) + self.trans.translation.setValue([P.x, P.y, P.z]) self.on() - def getClosestNode(self,point): - """returns the closest node from the given point""" + def getClosestNode(self, point): + """Return the closest node from the given point.""" # get the 2D coords. # point = FreeCAD.DraftWorkingPlane.projectPoint(point) pt = FreeCAD.DraftWorkingPlane.getLocalCoords(point) - pu = (round(pt.x/self.space,0))*self.space - pv = (round(pt.y/self.space,0))*self.space - pt = FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(pu,pv,0)) + pu = round(pt.x / self.space, 0) * self.space + pv = round(pt.y / self.space, 0) * self.space + pt = FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(pu, pv, 0)) return pt - -class boxTracker(Tracker): - """A box tracker, can be based on a line object""" - def __init__(self,line=None,width=0.1,height=1,shaded=False): + + +class boxTracker(Tracker): + """A box tracker, can be based on a line object.""" + + def __init__(self, line=None, width=0.1, height=1, shaded=False): self.trans = coin.SoTransform() m = coin.SoMaterial() m.transparency.setValue(0.8) - m.diffuseColor.setValue([0.4,0.4,0.6]) + m.diffuseColor.setValue([0.4, 0.4, 0.6]) w = coin.SoDrawStyle() w.style = coin.SoDrawStyle.LINES self.cube = coin.SoCube() @@ -988,16 +1103,19 @@ class boxTracker(Tracker): self.baseline = line self.update() if shaded: - Tracker.__init__(self,children=[self.trans,m,self.cube],name="boxTracker") + Tracker.__init__(self, children=[self.trans, m, self.cube], + name="boxTracker") else: - Tracker.__init__(self,children=[self.trans,w,self.cube],name="boxTracker") + Tracker.__init__(self, children=[self.trans, w, self.cube], + name="boxTracker") - def update(self,line=None,normal=None): + def update(self, line=None, normal=None): + """Update the tracker.""" import WorkingPlane, DraftGeomUtils if not normal: normal = FreeCAD.DraftWorkingPlane.axis if line: - if isinstance(line,list): + if isinstance(line, list): bp = line[0] lvec = line[1].sub(line[0]) else: @@ -1010,109 +1128,129 @@ class boxTracker(Tracker): return right = lvec.cross(normal) self.cube.width.setValue(lvec.Length) - p = WorkingPlane.getPlacementFromPoints([bp,bp.add(lvec),bp.add(right)]) + p = WorkingPlane.getPlacementFromPoints([bp, + bp.add(lvec), + bp.add(right)]) if p: self.trans.rotation.setValue(p.Rotation.Q) bp = bp.add(lvec.multiply(0.5)) - bp = bp.add(DraftVecUtils.scaleTo(normal,self.cube.depth.getValue()/2)) + bp = bp.add(DraftVecUtils.scaleTo(normal, self.cube.depth.getValue()/2.0)) self.pos(bp) - - def setRotation(self,rot): + + def setRotation(self, rot): + """Set the rotation.""" self.trans.rotation.setValue(rot.Q) - def pos(self,p): + def pos(self, p): + """Set the translation.""" self.trans.translation.setValue(DraftVecUtils.tup(p)) - def width(self,w=None): + def width(self, w=None): + """Set the width.""" if w: self.cube.height.setValue(w) else: return self.cube.height.getValue() - def length(self,l=None): + def length(self, l=None): + """Set the length.""" if l: self.cube.width.setValue(l) else: return self.cube.width.getValue() - - def height(self,h=None): + + def height(self, h=None): + """Set the height.""" if h: self.cube.depth.setValue(h) self.update() else: return self.cube.depth.getValue() + class radiusTracker(Tracker): - """A tracker that displays a transparent sphere to inicate a radius""" - def __init__(self,position=FreeCAD.Vector(0,0,0),radius=1): + """A tracker that displays a transparent sphere to inicate a radius.""" + + def __init__(self, position=FreeCAD.Vector(0, 0, 0), radius=1): self.trans = coin.SoTransform() - self.trans.translation.setValue([position.x,position.y,position.z]) + self.trans.translation.setValue([position.x, position.y, position.z]) m = coin.SoMaterial() m.transparency.setValue(0.9) - m.diffuseColor.setValue([0,1,0]) + m.diffuseColor.setValue([0, 1, 0]) self.sphere = coin.SoSphere() self.sphere.radius.setValue(radius) self.baseline = None - Tracker.__init__(self,children=[self.trans,m,self.sphere],name="radiusTracker") + Tracker.__init__(self, children=[self.trans, m, self.sphere], + name="radiusTracker") - def update(self,arg1,arg2=None): - if isinstance(arg1,FreeCAD.Vector): - self.trans.translation.setValue([arg1.x,arg1.y,arg1.z]) + def update(self, arg1, arg2=None): + """Update the tracker.""" + if isinstance(arg1, FreeCAD.Vector): + self.trans.translation.setValue([arg1.x, arg1.y, arg1.z]) else: self.sphere.radius.setValue(arg1) - if arg2 != None: - if isinstance(arg2,FreeCAD.Vector): - self.trans.translation.setValue([arg2.x,arg2.y,arg2.z]) + if arg2 is not None: + if isinstance(arg2, FreeCAD.Vector): + self.trans.translation.setValue([arg2.x, arg2.y, arg2.z]) else: self.sphere.radius.setValue(arg2) - + + class archDimTracker(Tracker): - """A wrapper around a Sketcher dim""" - def __init__(self,p1=FreeCAD.Vector(0,0,0),p2=FreeCAD.Vector(1,0,0),mode=1): + """A wrapper around a Sketcher dim.""" + + def __init__(self, + p1=FreeCAD.Vector(0, 0, 0), + p2=FreeCAD.Vector(1, 0, 0), mode=1): import SketcherGui self.dimnode = coin.SoType.fromName("SoDatumLabel").createInstance() - p1node = coin.SbVec3f([p1.x,p1.y,p1.z]) - p2node = coin.SbVec3f([p2.x,p2.y,p2.z]) - self.dimnode.pnts.setValues([p1node,p2node]) + p1node = coin.SbVec3f([p1.x, p1.y, p1.z]) + p2node = coin.SbVec3f([p2.x, p2.y, p2.z]) + self.dimnode.pnts.setValues([p1node, p2node]) self.dimnode.lineWidth = 1 color = FreeCADGui.draftToolBar.getDefaultColor("snap") self.dimnode.textColor.setValue(coin.SbVec3f(color)) self.setString() self.setMode(mode) - Tracker.__init__(self,children=[self.dimnode],name="archDimTracker") - - def setString(self,text=None): - """sets the dim string to the given value or auto value""" + Tracker.__init__(self, children=[self.dimnode], name="archDimTracker") + + def setString(self, text=None): + """Set the dim string to the given value or auto value.""" self.dimnode.param1.setValue(.5) p1 = Vector(self.dimnode.pnts.getValues()[0].getValue()) p2 = Vector(self.dimnode.pnts.getValues()[-1].getValue()) m = self.dimnode.datumtype.getValue() if m == 2: - self.Distance = (DraftVecUtils.project(p2.sub(p1),Vector(1,0,0))).Length + self.Distance = (DraftVecUtils.project(p2.sub(p1), Vector(1, 0, 0))).Length elif m == 3: - self.Distance = (DraftVecUtils.project(p2.sub(p1),Vector(0,1,0))).Length + self.Distance = (DraftVecUtils.project(p2.sub(p1), Vector(0, 1, 0))).Length else: self.Distance = (p2.sub(p1)).Length - text = FreeCAD.Units.Quantity(self.Distance,FreeCAD.Units.Length).UserString + text = FreeCAD.Units.Quantity(self.Distance, FreeCAD.Units.Length).UserString self.dimnode.string.setValue(text.encode('utf8')) - - def setMode(self,mode=1): - """sets the mode: 0 = without lines (a simple mark), 1 = - aligned (default), 2 = horizontal, 3 = vertical.""" + + def setMode(self, mode=1): + """Set the mode. + + 0 = without lines (a simple mark) + 1 = aligned (default) + 2 = horizontal + 3 = vertical. + """ self.dimnode.datumtype.setValue(mode) - def p1(self,point=None): - """sets or gets the first point of the dim""" + def p1(self, point=None): + """Set or get the first point of the dim.""" if point: - self.dimnode.pnts.set1Value(0,point.x,point.y,point.z) + self.dimnode.pnts.set1Value(0, point.x, point.y, point.z) self.setString() else: return Vector(self.dimnode.pnts.getValues()[0].getValue()) - def p2(self,point=None): - """sets or gets the second point of the dim""" + def p2(self, point=None): + """Set or get the second point of the dim.""" if point: - self.dimnode.pnts.set1Value(1,point.x,point.y,point.z) + self.dimnode.pnts.set1Value(1, point.x, point.y, point.z) self.setString() else: return Vector(self.dimnode.pnts.getValues()[-1].getValue()) diff --git a/src/Mod/Draft/draftobjects/__init__.py b/src/Mod/Draft/draftobjects/__init__.py index e69de29bb2..418915c82b 100644 --- a/src/Mod/Draft/draftobjects/__init__.py +++ b/src/Mod/Draft/draftobjects/__init__.py @@ -0,0 +1,8 @@ +"""Functions and classes that define custom scripted objects. + +These classes define a custom object which is based on one of the core +objects defined in C++. The custom object inherits some basic properties, +and new properties are added. + +Most Draft objects are based on Part::Part2DObject. +""" diff --git a/src/Mod/Draft/draftobjects/arc_3points.py b/src/Mod/Draft/draftobjects/arc_3points.py index 9dc7e45dad..b386591544 100644 --- a/src/Mod/Draft/draftobjects/arc_3points.py +++ b/src/Mod/Draft/draftobjects/arc_3points.py @@ -1,8 +1,3 @@ -"""Provides the object code for Draft Arc_3Points.""" -## @package arc_3points -# \ingroup DRAFT -# \brief Provides the object code for Draft Arc_3Points. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -25,14 +20,21 @@ # * USA * # * * # *************************************************************************** +"""Provides the object code for Draft Arc_3Points.""" +## @package arc_3points +# \ingroup DRAFT +# \brief Provides the object code for Draft Arc_3Points. + import math + import FreeCAD as App import Part import Draft -from draftutils.messages import _msg, _err, _log +import draftutils.utils as utils +from draftutils.messages import _msg, _err from draftutils.translate import _tr -if App.GuiUp: - import draftutils.gui_utils as gui_utils + +import draftutils.gui_utils as gui_utils def make_arc_3points(points, placement=None, face=False, @@ -109,40 +111,62 @@ def make_arc_3points(points, placement=None, face=False, Normally it returns a parametric Draft object (`Part::Part2DObject`). If `primitive` is `True`, it returns a basic `Part::Feature`. """ - _log("make_arc_3points") - _msg(16 * "-") - _msg(_tr("Arc by 3 points")) + _name = "make_arc_3points" + utils.print_header(_name, "Arc by 3 points") - if not isinstance(points, (list, tuple)): - _err(_tr("Wrong input: must be list or tuple of three points.")) + try: + utils.type_check([(points, (list, tuple))], name=_name) + except TypeError: + _err(_tr("Points: ") + "{}".format(points)) + _err(_tr("Wrong input: " + "must be list or tuple of three points exactly.")) return None if len(points) != 3: - _err(_tr("Wrong input: must be three points.")) + _err(_tr("Points: ") + "{}".format(points)) + _err(_tr("Wrong input: " + "must be list or tuple of three points exactly.")) return None if placement is not None: - if not isinstance(placement, App.Placement): - _err(_tr("Wrong input: incorrect placement")) + try: + utils.type_check([(placement, App.Placement)], name=_name) + except TypeError: + _err(_tr("Placement: ") + "{}".format(placement)) + _err(_tr("Wrong input: incorrect type of placement.")) return None p1, p2, p3 = points - _edge = Part.Arc(p1, p2, p3) + _msg("p1: {}".format(p1)) + _msg("p2: {}".format(p2)) + _msg("p3: {}".format(p3)) + + try: + utils.type_check([(p1, App.Vector), + (p2, App.Vector), + (p3, App.Vector)], name=_name) + except TypeError: + _err(_tr("Wrong input: incorrect type of points.")) + return None + + try: + _edge = Part.Arc(p1, p2, p3) + except Part.OCCError as error: + _err(_tr("Cannot generate shape: ") + "{}".format(error)) + return None + edge = _edge.toShape() radius = edge.Curve.Radius center = edge.Curve.Center - _msg("p1: {}".format(p1)) - _msg("p2: {}".format(p2)) - _msg("p3: {}".format(p3)) _msg(_tr("Radius: ") + "{}".format(radius)) _msg(_tr("Center: ") + "{}".format(center)) if primitive: + _msg(_tr("Create primitive object")) obj = App.ActiveDocument.addObject("Part::Feature", "Arc") obj.Shape = edge - _msg(_tr("Primitive object")) return obj rot = App.Rotation(edge.Curve.XAxis, @@ -168,8 +192,8 @@ def make_arc_3points(points, placement=None, face=False, _msg(_tr("Face: True")) if support: _msg(_tr("Support: ") + "{}".format(support)) - obj.MapMode = map_mode _msg(_tr("Map mode: " + "{}".format(map_mode))) + obj.MapMode = map_mode if placement: obj.AttachmentOffset.Base = placement.Base obj.AttachmentOffset.Rotation = original_placement.Rotation diff --git a/src/Mod/Draft/draftobjects/circulararray.py b/src/Mod/Draft/draftobjects/circulararray.py index 803b923b47..621ef8ffbd 100644 --- a/src/Mod/Draft/draftobjects/circulararray.py +++ b/src/Mod/Draft/draftobjects/circulararray.py @@ -1,9 +1,3 @@ -"""This module provides the object code for Draft CircularArray. -""" -## @package circulararray -# \ingroup DRAFT -# \brief This module provides the object code for Draft CircularArray. - # *************************************************************************** # * (c) 2019 Eliud Cabrera Castillo * # * * @@ -26,20 +20,130 @@ # * USA * # * * # *************************************************************************** +"""Provides the object code for Draft CircularArray.""" +## @package circulararray +# \ingroup DRAFT +# \brief This module provides the object code for Draft CircularArray. import FreeCAD as App import Draft +import draftutils.utils as utils +from draftutils.messages import _msg, _err +from draftutils.translate import _tr def make_circular_array(obj, r_distance=100, tan_distance=100, - axis=App.Vector(0, 0, 1), center=App.Vector(0, 0, 0), number=2, symmetry=1, - use_link=False): + axis=App.Vector(0, 0, 1), center=App.Vector(0, 0, 0), + use_link=True): """Create a circular array from the given object. + + Parameters + ---------- + obj: Part::Feature + Any type of object that has a `Part::TopoShape` + that can be duplicated. + + r_distance: float, optional + It defaults to `100`. + Radial distance to the next ring of circular arrays. + + tan_distance: float, optional + It defaults to `100`. + The tangential distance between two elements located + in the same circular ring. + The tangential distance together with the radial distance + determine how many copies are created. + + number: int, optional + It defaults to 2. + The number of layers or rings of repeated objects. + The original object stays at the center, and is counted + as a layer itself. So, if you want at least one layer of circular + copies, this number must be at least 2. + + symmetry: int, optional + It defaults to 1. + It indicates how many lines of symmetry the entire circular pattern + has. That is, with 1, the array is symmetric only after a full + 360 degrees rotation. + + When it is 2, the array is symmetric at 0 and 180 degrees. + When it is 3, the array is symmetric at 0, 120, and 240 degrees. + When it is 4, the array is symmetric at 0, 90, 180, and 270 degrees. + Et cetera. + + axis: Base::Vector3, optional + It defaults to `App.Vector(0, 0, 1)` or the `+Z` axis. + The unit vector indicating the axis of rotation. + + center: Base::Vector3, optional + It defaults to `App.Vector(0, 0, 0)` or the global origin. + The point through which the `axis` passes to define + the axis of rotation. + + use_link: bool, optional + It defaults to `True`. + If it is `True` the produced copies are not `Part::TopoShape` copies, + but rather `App::Link` objects. + The Links repeat the shape of the original `obj` exactly, + and therefore the resulting array is more memory efficient. + + Also, when `use_link` is `True`, the `Fuse` property + of the resulting array does not work; the array doesn't + contain separate shapes, it only has the original shape repeated + many times, so there is nothing to fuse together. + + If `use_link` is `False` the original shape is copied many times. + In this case the `Fuse` property is able to fuse + all copies into a single object, if they touch each other. + + Returns + ------- + Part::FeaturePython + A scripted object with `Proxy.Type='Array'`. + Its `Shape` is a compound of the copies of the original object. """ - obj = Draft.makeArray(obj, - arg1=r_distance, arg2=tan_distance, - arg3=axis, arg4=center, arg5=number, arg6=symmetry, - use_link=use_link) - return obj + _name = "make_circular_array" + utils.print_header(_name, _tr("Circular array")) + + _msg("r_distance: {}".format(r_distance)) + _msg("tan_distance: {}".format(tan_distance)) + + try: + utils.type_check([(r_distance, (int, float, App.Units.Quantity)), + (tan_distance, (int, float, App.Units.Quantity))], + name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number or quantity.")) + return None + + _msg("number: {}".format(number)) + _msg("symmetry: {}".format(symmetry)) + + try: + utils.type_check([(number, int), + (symmetry, int)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be an integer number.")) + return None + + _msg("axis: {}".format(axis)) + _msg("center: {}".format(center)) + + try: + utils.type_check([(axis, App.Vector), + (center, App.Vector)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be a vector.")) + return None + + _msg("use_link: {}".format(bool(use_link))) + + new_obj = Draft.makeArray(obj, + arg1=r_distance, arg2=tan_distance, + arg3=axis, arg4=center, + arg5=number, arg6=symmetry, + use_link=use_link) + return new_obj diff --git a/src/Mod/Draft/draftobjects/orthoarray.py b/src/Mod/Draft/draftobjects/orthoarray.py index e1193fda8d..a5fedf693b 100644 --- a/src/Mod/Draft/draftobjects/orthoarray.py +++ b/src/Mod/Draft/draftobjects/orthoarray.py @@ -1,8 +1,3 @@ -"""Provide the object code for Draft Array.""" -## @package orthoarray -# \ingroup DRAFT -# \brief Provide the object code for Draft Array. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -25,9 +20,16 @@ # * USA * # * * # *************************************************************************** +"""Provide the object code for Draft Array.""" +## @package orthoarray +# \ingroup DRAFT +# \brief Provide the object code for Draft Array. import FreeCAD as App import Draft +import draftutils.utils as utils +from draftutils.messages import _msg, _wrn, _err +from draftutils.translate import _tr def make_ortho_array(obj, @@ -37,24 +39,376 @@ def make_ortho_array(obj, n_x=2, n_y=2, n_z=1, - use_link=False): - """Create an orthogonal array from the given object.""" - obj = Draft.makeArray(obj, - arg1=v_x, arg2=v_y, arg3=v_z, - arg4=n_x, arg5=n_y, arg6=n_z, - use_link=use_link) - return obj + use_link=True): + """Create an orthogonal array from the given object. + + Parameters + ---------- + obj: Part::Feature + Any type of object that has a `Part::TopoShape` + that can be duplicated. + This means most 2D and 3D objects produced + with any workbench. + + v_x, v_y, v_z: Base::Vector3, optional + The vector indicating the vector displacement between two elements + in the specified orthogonal direction X, Y, Z. + + By default: + :: + v_x = App.Vector(10, 0, 0) + v_y = App.Vector(0, 10, 0) + v_z = App.Vector(0, 0, 10) + + Given that this is a vectorial displacement + the next object can appear displaced in one, two or three axes + at the same time. + + For example + :: + v_x = App.Vector(10, 5, 0) + + means that the next element in the X direction will be displaced + 10 mm in X, 5 mm in Y, and 0 mm in Z. + + A traditional "rectangular" array is obtained when + the displacement vector only has its corresponding component, + like in the default case. + + If these values are entered as single numbers instead + of vectors, the single value is expanded into a vector + of the corresponding direction, and the other components are assumed + to be zero. + + For example + :: + v_x = 15 + v_y = 10 + v_z = 1 + becomes + :: + v_x = App.Vector(15, 0, 0) + v_y = App.Vector(0, 10, 0) + v_z = App.Vector(0, 0, 1) + + n_x, n_y, n_z: int, optional + The number of copies in the specified orthogonal direction X, Y, Z. + This number includes the original object, therefore, it must be + at least 1. + + The values of `n_x` and `n_y` default to 2, + while `n_z` defaults to 1. + This means the array by default is a planar array. + + use_link: bool, optional + It defaults to `True`. + If it is `True` the produced copies are not `Part::TopoShape` copies, + but rather `App::Link` objects. + The Links repeat the shape of the original `obj` exactly, + and therefore the resulting array is more memory efficient. + + Also, when `use_link` is `True`, the `Fuse` property + of the resulting array does not work; the array doesn't + contain separate shapes, it only has the original shape repeated + many times, so there is nothing to fuse together. + + If `use_link` is `False` the original shape is copied many times. + In this case the `Fuse` property is able to fuse + all copies into a single object, if they touch each other. + + Returns + ------- + Part::FeaturePython + A scripted object with `Proxy.Type='Array'`. + Its `Shape` is a compound of the copies of the original object. + + See Also + -------- + make_ortho_array2d, make_rect_array, make_rect_array2d + """ + _name = "make_ortho_array" + utils.print_header(_name, _tr("Orthogonal array")) + + _msg("v_x: {}".format(v_x)) + _msg("v_y: {}".format(v_y)) + _msg("v_z: {}".format(v_z)) + + try: + utils.type_check([(v_x, (int, float, App.Vector)), + (v_y, (int, float, App.Vector)), + (v_z, (int, float, App.Vector))], + name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number or vector.")) + return None + + _text = "Input: single value expanded to vector." + if not isinstance(v_x, App.Vector): + v_x = App.Vector(v_x, 0, 0) + _wrn(_tr(_text)) + if not isinstance(v_y, App.Vector): + v_y = App.Vector(0, v_y, 0) + _wrn(_tr(_text)) + if not isinstance(v_z, App.Vector): + v_z = App.Vector(0, 0, v_z) + _wrn(_tr(_text)) + + _msg("n_x: {}".format(n_x)) + _msg("n_y: {}".format(n_y)) + _msg("n_z: {}".format(n_z)) + + try: + utils.type_check([(n_x, int), + (n_y, int), + (n_z, int)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be an integer number.")) + return None + + _text = ("Input: number of elements must be at least 1. " + "It is set to 1.") + if n_x < 1: + _wrn(_tr(_text)) + n_x = 1 + if n_y < 1: + _wrn(_tr(_text)) + n_y = 1 + if n_z < 1: + _wrn(_tr(_text)) + n_z = 1 + + _msg("use_link: {}".format(bool(use_link))) + + new_obj = Draft.makeArray(obj, + arg1=v_x, arg2=v_y, arg3=v_z, + arg4=n_x, arg5=n_y, arg6=n_z, + use_link=use_link) + return new_obj -def make_ortho_array2(obj, - v_x=App.Vector(10, 0, 0), - v_y=App.Vector(0, 10, 0), +def make_ortho_array2d(obj, + v_x=App.Vector(10, 0, 0), + v_y=App.Vector(0, 10, 0), + n_x=2, + n_y=2, + use_link=True): + """Create a 2D orthogonal array from the given object. + + This works the same as `make_ortho_array`. + The Z component is ignored so it only considers vector displacements + in X and Y directions. + + Parameters + ---------- + obj: Part::Feature + Any type of object that has a `Part::TopoShape` + that can be duplicated. + This means most 2D and 3D objects produced + with any workbench. + + v_x, v_y: Base::Vector3, optional + Vectorial displacement of elements + in the corresponding X and Y directions. + See `make_ortho_array`. + + n_x, n_y: int, optional + Number of elements + in the corresponding X and Y directions. + See `make_ortho_array`. + + use_link: bool, optional + If it is `True`, create `App::Link` array. + See `make_ortho_array`. + + Returns + ------- + Part::FeaturePython + A scripted object with `Proxy.Type='Array'`. + Its `Shape` is a compound of the copies of the original object. + + See Also + -------- + make_ortho_array, make_rect_array, make_rect_array2d + """ + _name = "make_ortho_array2d" + utils.print_header(_name, _tr("Orthogonal array 2D")) + + _msg("v_x: {}".format(v_x)) + _msg("v_y: {}".format(v_y)) + + try: + utils.type_check([(v_x, (int, float, App.Vector)), + (v_y, (int, float, App.Vector))], + name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number or vector.")) + return None + + _text = "Input: single value expanded to vector." + if not isinstance(v_x, App.Vector): + v_x = App.Vector(v_x, 0, 0) + _wrn(_tr(_text)) + if not isinstance(v_y, App.Vector): + v_y = App.Vector(0, v_y, 0) + _wrn(_tr(_text)) + + _msg("n_x: {}".format(n_x)) + _msg("n_y: {}".format(n_y)) + + try: + utils.type_check([(n_x, int), + (n_y, int)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be an integer number.")) + return None + + _text = ("Input: number of elements must be at least 1. " + "It is set to 1.") + if n_x < 1: + _wrn(_tr(_text)) + n_x = 1 + if n_y < 1: + _wrn(_tr(_text)) + n_y = 1 + + _msg("use_link: {}".format(bool(use_link))) + + new_obj = Draft.makeArray(obj, + arg1=v_x, arg2=v_y, + arg3=n_x, arg4=n_y, + use_link=use_link) + return new_obj + + +def make_rect_array(obj, + d_x=10, + d_y=10, + d_z=10, + n_x=2, + n_y=2, + n_z=1, + use_link=True): + """Create a rectangular array from the given object. + + This function wraps around `make_ortho_array` + to produce strictly rectangular arrays, in which + the displacement vectors `v_x`, `v_y`, and `v_z` + only have their respective components in X, Y, and Z. + + Parameters + ---------- + obj: Part::Feature + Any type of object that has a `Part::TopoShape` + that can be duplicated. + This means most 2D and 3D objects produced + with any workbench. + + d_x, d_y, d_z: Base::Vector3, optional + Displacement of elements in the corresponding X, Y, and Z directions. + + n_x, n_y, n_z: int, optional + Number of elements in the corresponding X, Y, and Z directions. + + use_link: bool, optional + If it is `True`, create `App::Link` array. + See `make_ortho_array`. + + Returns + ------- + Part::FeaturePython + A scripted object with `Proxy.Type='Array'`. + Its `Shape` is a compound of the copies of the original object. + + See Also + -------- + make_ortho_array, make_ortho_array2d, make_rect_array2d + """ + _name = "make_rect_array" + utils.print_header(_name, _tr("Rectangular array")) + + _msg("d_x: {}".format(d_x)) + _msg("d_y: {}".format(d_y)) + _msg("d_z: {}".format(d_z)) + + try: + utils.type_check([(d_x, (int, float)), + (d_y, (int, float)), + (d_z, (int, float))], + name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number.")) + return None + + new_obj = make_ortho_array(obj, + v_x=App.Vector(d_x, 0, 0), + v_y=App.Vector(0, d_y, 0), + v_z=App.Vector(0, 0, d_z), + n_x=n_x, + n_y=n_y, + n_z=n_z, + use_link=use_link) + return new_obj + + +def make_rect_array2d(obj, + d_x=10, + d_y=10, n_x=2, n_y=2, - use_link=False): - """Create a 2D orthogonal array from the given object.""" - obj = Draft.makeArray(obj, - arg1=v_x, arg2=v_y, - arg3=n_x, arg4=n_y, - use_link=use_link) - return obj + use_link=True): + """Create a 2D rectangular array from the given object. + + This function wraps around `make_ortho_array2d` + to produce strictly rectangular arrays, in which + the displacement vectors `v_x` and `v_y` + only have their respective components in X and Y. + + Parameters + ---------- + obj: Part::Feature + Any type of object that has a `Part::TopoShape` + that can be duplicated. + This means most 2D and 3D objects produced + with any workbench. + + d_x, d_y: Base::Vector3, optional + Displacement of elements in the corresponding X and Y directions. + + n_x, n_y: int, optional + Number of elements in the corresponding X and Y directions. + + use_link: bool, optional + If it is `True`, create `App::Link` array. + See `make_ortho_array`. + + Returns + ------- + Part::FeaturePython + A scripted object with `Proxy.Type='Array'`. + Its `Shape` is a compound of the copies of the original object. + + See Also + -------- + make_ortho_array, make_ortho_array2d, make_rect_array + """ + _name = "make_rect_array2d" + utils.print_header(_name, _tr("Rectangular array 2D")) + + _msg("d_x: {}".format(d_x)) + _msg("d_y: {}".format(d_y)) + + try: + utils.type_check([(d_x, (int, float)), + (d_y, (int, float))], + name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number.")) + return None + + new_obj = make_ortho_array2d(obj, + v_x=App.Vector(d_x, 0, 0), + v_y=App.Vector(0, d_y, 0), + n_x=n_x, + n_y=n_y, + use_link=use_link) + return new_obj diff --git a/src/Mod/Draft/draftobjects/polararray.py b/src/Mod/Draft/draftobjects/polararray.py index 52d1573f73..b7e128245b 100644 --- a/src/Mod/Draft/draftobjects/polararray.py +++ b/src/Mod/Draft/draftobjects/polararray.py @@ -1,9 +1,3 @@ -"""This module provides the object code for Draft PolarArray. -""" -## @package polararray -# \ingroup DRAFT -# \brief This module provides the object code for Draft PolarArray. - # *************************************************************************** # * (c) 2019 Eliud Cabrera Castillo * # * * @@ -26,17 +20,93 @@ # * USA * # * * # *************************************************************************** +"""Provide the object code for Draft PolarArray.""" +## @package polararray +# \ingroup DRAFT +# \brief This module provides the object code for Draft PolarArray. import FreeCAD as App import Draft +import draftutils.utils as utils +from draftutils.messages import _msg, _err +from draftutils.translate import _tr def make_polar_array(obj, - center=App.Vector(0, 0, 0), angle=180, number=4, - use_link=False): + number=4, angle=360, center=App.Vector(0, 0, 0), + use_link=True): """Create a polar array from the given object. + + Parameters + ---------- + obj: Part::Feature + Any type of object that has a `Part::TopoShape` + that can be duplicated. + This means most 2D and 3D objects produced + with any workbench. + + number: int, optional + It defaults to 4. + The number of copies produced in the circular pattern. + + angle: float, optional + It defaults to 360. + The magnitude in degrees swept by the polar pattern. + + center: Base::Vector3, optional + It defaults to the origin `App.Vector(0, 0, 0)`. + The vector indicating the center of rotation of the array. + + use_link: bool, optional + It defaults to `True`. + If it is `True` the produced copies are not `Part::TopoShape` copies, + but rather `App::Link` objects. + The Links repeat the shape of the original `obj` exactly, + and therefore the resulting array is more memory efficient. + + Also, when `use_link` is `True`, the `Fuse` property + of the resulting array does not work; the array doesn't + contain separate shapes, it only has the original shape repeated + many times, so there is nothing to fuse together. + + If `use_link` is `False` the original shape is copied many times. + In this case the `Fuse` property is able to fuse + all copies into a single object, if they touch each other. + + Returns + ------- + Part::FeaturePython + A scripted object with `Proxy.Type='Array'`. + Its `Shape` is a compound of the copies of the original object. """ - obj = Draft.makeArray(obj, - arg1=center, arg2=angle, arg3=number, - use_link=use_link) - return obj + _name = "make_polar_array" + utils.print_header(_name, _tr("Polar array")) + + _msg("Number: {}".format(number)) + _msg("Angle: {}".format(angle)) + _msg("Center: {}".format(center)) + + try: + utils.type_check([(number, int)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be an integer number.")) + return None + + try: + utils.type_check([(angle, (int, float))], name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number.")) + return None + + try: + utils.type_check([(center, App.Vector)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be a vector.")) + return None + + _msg("use_link: {}".format(bool(use_link))) + + new_obj = Draft.makeArray(obj, + arg1=center, arg2=angle, arg3=number, + use_link=use_link) + return new_obj diff --git a/src/Mod/Draft/drafttaskpanels/__init__.py b/src/Mod/Draft/drafttaskpanels/__init__.py index e69de29bb2..ca18b3c193 100644 --- a/src/Mod/Draft/drafttaskpanels/__init__.py +++ b/src/Mod/Draft/drafttaskpanels/__init__.py @@ -0,0 +1,7 @@ +"""Classes that define the task panels of GUI commands. + +These classes load `.ui` files that will be used in the task panel +of the graphical commands. +The classes define the behavior and callbacks of the different widgets +included in the `.ui` file. +""" diff --git a/src/Mod/Draft/drafttaskpanels/task_circulararray.py b/src/Mod/Draft/drafttaskpanels/task_circulararray.py index d1e7d4327d..bcab6bf807 100644 --- a/src/Mod/Draft/drafttaskpanels/task_circulararray.py +++ b/src/Mod/Draft/drafttaskpanels/task_circulararray.py @@ -1,9 +1,3 @@ -"""This module provides the task panel for the Draft CircularArray tool. -""" -## @package task_circulararray -# \ingroup DRAFT -# \brief This module provides the task panel code for the CircularArray tool. - # *************************************************************************** # * (c) 2019 Eliud Cabrera Castillo * # * * @@ -26,183 +20,257 @@ # * USA * # * * # *************************************************************************** +"""Provides the task panel code for the Draft CircularArray tool.""" +## @package task_circulararray +# \ingroup DRAFT +# \brief This module provides the task panel code for the CircularArray tool. + +import PySide.QtGui as QtGui +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui -# import Draft -import Draft_rc +import Draft_rc # include resources, icons, ui files import DraftVecUtils +import draftutils.utils as utils +from draftutils.messages import _msg, _wrn, _err, _log +from draftutils.translate import _tr +from FreeCAD import Units as U -import PySide.QtCore as QtCore -import PySide.QtGui as QtGui -from PySide.QtCore import QT_TRANSLATE_NOOP -# import DraftTools -from DraftGui import translate -# from DraftGui import displayExternal - -_Quantity = App.Units.Quantity - - -def _Msg(text, end="\n"): - """Print message with newline""" - App.Console.PrintMessage(text + end) - - -def _Wrn(text, end="\n"): - """Print warning with newline""" - App.Console.PrintWarning(text + end) - - -def _tr(text): - """Function to translate with the context set""" - return translate("Draft", text) - - -# So the resource file doesn't trigger errors from code checkers (flake8) -True if Draft_rc.__name__ else False +# The module is used to prevent complaints from code checkers (flake8) +bool(Draft_rc.__name__) class TaskPanelCircularArray: """TaskPanel code for the CircularArray command. The names of the widgets are defined in the `.ui` file. - In this class all those widgets are automatically created - under the name `self.form.` + This `.ui` file `must` be loaded into an attribute + called `self.form` so that it is loaded into the task panel correctly. + + In this class all widgets are automatically created + as `self.form.`. The `.ui` file may use special FreeCAD widgets such as `Gui::InputField` (based on `QLineEdit`) and `Gui::QuantitySpinBox` (based on `QAbstractSpinBox`). See the Doxygen documentation of the corresponding files in `src/Gui/`, for example, `InputField.h` and `QuantitySpinBox.h`. + + Attributes + ---------- + source_command: gui_base.GuiCommandBase + This attribute holds a reference to the calling class + of this task panel. + This parent class, which is derived from `gui_base.GuiCommandBase`, + is responsible for calling this task panel, for installing + certain callbacks, and for removing them. + + It also delays the execution of the internal creation commands + by using the `draftutils.todo.ToDo` class. + + See Also + -------- + * https://forum.freecadweb.org/viewtopic.php?f=10&t=40007 + * https://forum.freecadweb.org/viewtopic.php?t=5374#p43038 """ def __init__(self): + self.name = "Circular array" + _log(_tr("Task panel:") + "{}".format(_tr(self.name))) + + # The .ui file must be loaded into an attribute + # called `self.form` so that it is displayed in the task panel. ui_file = ":/ui/TaskPanel_CircularArray.ui" self.form = Gui.PySideUic.loadUi(ui_file) - self.name = self.form.windowTitle() icon_name = "Draft_CircularArray" svg = ":/icons/" + icon_name pix = QtGui.QPixmap(svg) icon = QtGui.QIcon.fromTheme(icon_name, QtGui.QIcon(svg)) self.form.setWindowIcon(icon) + self.form.setWindowTitle(_tr(self.name)) + self.form.label_icon.setPixmap(pix.scaled(32, 32)) - start_distance = _Quantity(1000.0, App.Units.Length) - distance_unit = start_distance.getUserPreferred()[2] - self.form.spinbox_r_distance.setProperty('rawValue', - 2 * start_distance.Value) - self.form.spinbox_r_distance.setProperty('unit', distance_unit) - self.form.spinbox_tan_distance.setProperty('rawValue', - start_distance.Value) - self.form.spinbox_tan_distance.setProperty('unit', distance_unit) + # ------------------------------------------------------------------- + # Default values for the internal function, + # and for the task panel interface + start_distance = U.Quantity(50.0, App.Units.Length) + length_unit = start_distance.getUserPreferred()[2] self.r_distance = 2 * start_distance.Value self.tan_distance = start_distance.Value - self.form.spinbox_number.setValue(3) - self.form.spinbox_symmetry.setValue(1) + self.form.spinbox_r_distance.setProperty('rawValue', + self.r_distance) + self.form.spinbox_r_distance.setProperty('unit', length_unit) + self.form.spinbox_tan_distance.setProperty('rawValue', + self.tan_distance) + self.form.spinbox_tan_distance.setProperty('unit', length_unit) - self.number = self.form.spinbox_number.value() - self.symmetry = self.form.spinbox_symmetry.value() + self.number = 3 + self.symmetry = 1 + self.form.spinbox_number.setValue(self.number) + self.form.spinbox_symmetry.setValue(self.symmetry) + + # TODO: the axis is currently fixed, it should be editable + # or selectable from the task panel self.axis = App.Vector(0, 0, 1) - start_point = _Quantity(0.0, App.Units.Length) + start_point = U.Quantity(0.0, App.Units.Length) length_unit = start_point.getUserPreferred()[2] - self.form.input_c_x.setProperty('rawValue', start_point.Value) + + self.center = App.Vector(start_point.Value, + start_point.Value, + start_point.Value) + + self.form.input_c_x.setProperty('rawValue', self.center.x) self.form.input_c_x.setProperty('unit', length_unit) - self.form.input_c_y.setProperty('rawValue', start_point.Value) + self.form.input_c_y.setProperty('rawValue', self.center.y) self.form.input_c_y.setProperty('unit', length_unit) - self.form.input_c_z.setProperty('rawValue', start_point.Value) + self.form.input_c_z.setProperty('rawValue', self.center.z) self.form.input_c_z.setProperty('unit', length_unit) - self.valid_input = True - self.c_x_str = "" - self.c_y_str = "" - self.c_z_str = "" - self.center = App.Vector(0, 0, 0) + self.fuse = utils.get_param("Draft_array_fuse", False) + self.use_link = utils.get_param("Draft_array_Link", True) - # Old style for Qt4 - # QtCore.QObject.connect(self.form.button_reset, - # QtCore.SIGNAL("clicked()"), - # self.reset_point) - # New style for Qt5 - self.form.button_reset.clicked.connect(self.reset_point) + self.form.checkbox_fuse.setChecked(self.fuse) + self.form.checkbox_link.setChecked(self.use_link) + # ------------------------------------------------------------------- + + # Some objects need to be selected before we can execute the function. + self.selection = None + + # This is used to test the input of the internal function. + # It should be changed to True before we can execute the function. + self.valid_input = False + + self.set_widget_callbacks() + + self.tr_true = QT_TRANSLATE_NOOP("Draft", "True") + self.tr_false = QT_TRANSLATE_NOOP("Draft", "False") # The mask is not used at the moment, but could be used in the future # by a callback to restrict the coordinates of the pointer. self.mask = "" - # When the checkbox changes, change the fuse value - self.fuse = False - QtCore.QObject.connect(self.form.checkbox_fuse, - QtCore.SIGNAL("stateChanged(int)"), - self.set_fuse) + def set_widget_callbacks(self): + """Set up the callbacks (slots) for the widget signals.""" + # New style for Qt5 + self.form.button_reset.clicked.connect(self.reset_point) - self.use_link = False - QtCore.QObject.connect(self.form.checkbox_link, - QtCore.SIGNAL("stateChanged(int)"), - self.set_link) + # When the checkbox changes, change the internal value + self.form.checkbox_fuse.stateChanged.connect(self.set_fuse) + self.form.checkbox_link.stateChanged.connect(self.set_link) + + # Old style for Qt4, avoid! + # QtCore.QObject.connect(self.form.button_reset, + # QtCore.SIGNAL("clicked()"), + # self.reset_point) + # QtCore.QObject.connect(self.form.checkbox_fuse, + # QtCore.SIGNAL("stateChanged(int)"), + # self.set_fuse) + # QtCore.QObject.connect(self.form.checkbox_link, + # QtCore.SIGNAL("stateChanged(int)"), + # self.set_link) def accept(self): - """Function that executes when clicking the OK button""" - selection = Gui.Selection.getSelection() - self.number = self.form.spinbox_number.value() + """Execute when clicking the OK button or Enter key.""" + self.selection = Gui.Selection.getSelection() - tan_d_str = self.form.spinbox_tan_distance.text() - self.tan_distance = _Quantity(tan_d_str).Value - self.valid_input = self.validate_input(selection, + (self.r_distance, + self.tan_distance) = self.get_distances() + + (self.number, + self.symmetry) = self.get_number_symmetry() + + self.axis = self.get_axis() + self.center = self.get_center() + + self.valid_input = self.validate_input(self.selection, + self.r_distance, + self.tan_distance, self.number, - self.tan_distance) + self.symmetry, + self.axis, + self.center) if self.valid_input: - self.create_object(selection) - self.print_messages(selection) + self.create_object() + self.print_messages() self.finish() - def validate_input(self, selection, number, tan_distance): - """Check that the input is valid""" + def validate_input(self, selection, + r_distance, tan_distance, + number, symmetry, + axis, center): + """Check that the input is valid. + + Some values may not need to be checked because + the interface may not allow to input wrong data. + """ if not selection: - _Wrn(_tr("At least one element must be selected")) + _err(_tr("At least one element must be selected.")) return False + if number < 2: - _Wrn(_tr("Number of elements must be at least 2")) + _err(_tr("Number of layers must be at least 2.")) return False - # Todo: each of the elements of the selection could be tested, - # not only the first one. - if selection[0].isDerivedFrom("App::FeaturePython"): - _Wrn(_tr("Selection is not suitable for array")) - _Wrn(_tr("Object:") + " {}".format(selection[0].Label)) + + # TODO: this should handle multiple objects. + # Each of the elements of the selection should be tested. + obj = selection[0] + if obj.isDerivedFrom("App::FeaturePython"): + _err(_tr("Selection is not suitable for array.")) + _err(_tr("Object:") + " {}".format(selection[0].Label)) return False + + if r_distance == 0: + _wrn(_tr("Radial distance is zero. " + "Resulting array may not look correct.")) + elif r_distance < 0: + _wrn(_tr("Radial distance is negative. " + "It is made positive to proceed.")) + self.r_distance = abs(r_distance) + if tan_distance == 0: - _Wrn(_tr("Tangential distance cannot be zero")) + _err(_tr("Tangential distance cannot be zero.")) return False - return True + elif tan_distance < 0: + _wrn(_tr("Tangential distance is negative. " + "It is made positive to proceed.")) + self.tan_distance = abs(tan_distance) - def create_object(self, selection): - """Create the actual object""" - r_d_str = self.form.spinbox_r_distance.text() - tan_d_str = self.form.spinbox_tan_distance.text() - self.r_distance = _Quantity(r_d_str).Value - self.tan_distance = _Quantity(tan_d_str).Value - - self.number = self.form.spinbox_number.value() - self.symmetry = self.form.spinbox_symmetry.value() - self.center = self.set_point() - - if len(selection) == 1: - sel_obj = selection[0] - else: - # This can be changed so a compound of multiple - # selected objects is produced - sel_obj = selection[0] + # The other arguments are not tested but they should be present. + if symmetry and axis and center: + pass self.fuse = self.form.checkbox_fuse.isChecked() self.use_link = self.form.checkbox_link.isChecked() + return True + + def create_object(self): + """Create the new object. + + At this stage we already tested that the input is correct + so the necessary attributes are already set. + Then we proceed with the internal function to create the new object. + """ + if len(self.selection) == 1: + sel_obj = self.selection[0] + else: + # TODO: this should handle multiple objects. + # For example, it could take the shapes of all objects, + # make a compound and then use it as input for the array function. + sel_obj = self.selection[0] # This creates the object immediately # obj = Draft.makeArray(sel_obj, - # self.center, self.angle, self.number) + # self.r_distance, self.tan_distance, + # self.axis, self.center, + # self.number, self.symmetry, + # self.use_link) # if obj: # obj.Fuse = self.fuse @@ -210,96 +278,122 @@ class TaskPanelCircularArray: # of this class, the GuiCommand. # This is needed to schedule geometry manipulation # that would crash Coin3D if done in the event callback. - _cmd = "obj = Draft.makeArray(" - _cmd += "FreeCAD.ActiveDocument." + sel_obj.Name + ", " - _cmd += "arg1=" + str(self.r_distance) + ", " - _cmd += "arg2=" + str(self.tan_distance) + ", " - _cmd += "arg3=" + DraftVecUtils.toString(self.axis) + ", " - _cmd += "arg4=" + DraftVecUtils.toString(self.center) + ", " - _cmd += "arg5=" + str(self.number) + ", " - _cmd += "arg6=" + str(self.symmetry) + ", " + _cmd = "draftobjects.circulararray.make_circular_array" + _cmd += "(" + _cmd += "App.ActiveDocument." + sel_obj.Name + ", " + _cmd += "r_distance=" + str(self.r_distance) + ", " + _cmd += "tan_distance=" + str(self.tan_distance) + ", " + _cmd += "number=" + str(self.number) + ", " + _cmd += "symmetry=" + str(self.symmetry) + ", " + _cmd += "axis=" + DraftVecUtils.toString(self.axis) + ", " + _cmd += "center=" + DraftVecUtils.toString(self.center) + ", " _cmd += "use_link=" + str(self.use_link) _cmd += ")" - _cmd_list = ["FreeCADGui.addModule('Draft')", - _cmd, + _cmd_list = ["Gui.addModule('Draft')", + "Gui.addModule('draftobjects.circulararray')", + "obj = " + _cmd, "obj.Fuse = " + str(self.fuse), "Draft.autogroup(obj)", - "FreeCAD.ActiveDocument.recompute()"] - self.source_command.commit("Circular array", _cmd_list) + "App.ActiveDocument.recompute()"] - def set_point(self): - """Assign the values to the center""" - self.c_x_str = self.form.input_c_x.text() - self.c_y_str = self.form.input_c_y.text() - self.c_z_str = self.form.input_c_z.text() - center = App.Vector(_Quantity(self.c_x_str).Value, - _Quantity(self.c_y_str).Value, - _Quantity(self.c_z_str).Value) + # We commit the command list through the parent command + self.source_command.commit(_tr(self.name), _cmd_list) + + def get_distances(self): + """Get the distance parameters from the widgets.""" + r_d_str = self.form.spinbox_r_distance.text() + tan_d_str = self.form.spinbox_tan_distance.text() + return (U.Quantity(r_d_str).Value, + U.Quantity(tan_d_str).Value) + + def get_number_symmetry(self): + """Get the number and symmetry parameters from the widgets.""" + number = self.form.spinbox_number.value() + symmetry = self.form.spinbox_symmetry.value() + return number, symmetry + + def get_center(self): + """Get the value of the center from the widgets.""" + c_x_str = self.form.input_c_x.text() + c_y_str = self.form.input_c_y.text() + c_z_str = self.form.input_c_z.text() + center = App.Vector(U.Quantity(c_x_str).Value, + U.Quantity(c_y_str).Value, + U.Quantity(c_z_str).Value) return center + def get_axis(self): + """Get the axis that will be used for the array. NOT IMPLEMENTED. + + It should consider a second selection of an edge or wire to use + as an axis. + """ + return self.axis + def reset_point(self): - """Reset the point to the original distance""" + """Reset the center point to the original distance.""" self.form.input_c_x.setProperty('rawValue', 0) self.form.input_c_y.setProperty('rawValue', 0) self.form.input_c_z.setProperty('rawValue', 0) - self.center = self.set_point() - _Msg(_tr("Center reset:") + self.center = self.get_center() + _msg(_tr("Center reset:") + " ({0}, {1}, {2})".format(self.center.x, self.center.y, self.center.z)) - def print_fuse_state(self): - """Print the state translated""" - if self.fuse: - translated_state = QT_TRANSLATE_NOOP("Draft", "True") + def print_fuse_state(self, fuse): + """Print the fuse state translated.""" + if fuse: + state = self.tr_true else: - translated_state = QT_TRANSLATE_NOOP("Draft", "False") - _Msg(_tr("Fuse:") + " {}".format(translated_state)) + state = self.tr_false + _msg(_tr("Fuse:") + " {}".format(state)) def set_fuse(self): - """This function is called when the fuse checkbox changes""" + """Execute as a callback when the fuse checkbox changes.""" self.fuse = self.form.checkbox_fuse.isChecked() - self.print_fuse_state() + self.print_fuse_state(self.fuse) + utils.set_param("Draft_array_fuse", self.fuse) - def print_link_state(self): - """Print the state translated""" - if self.use_link: - translated_state = QT_TRANSLATE_NOOP("Draft", "True") + def print_link_state(self, use_link): + """Print the link state translated.""" + if use_link: + state = self.tr_true else: - translated_state = QT_TRANSLATE_NOOP("Draft", "False") - _Msg(_tr("Use Link object:") + " {}".format(translated_state)) + state = self.tr_false + _msg(_tr("Create Link array:") + " {}".format(state)) def set_link(self): - """This function is called when the fuse checkbox changes""" + """Execute as a callback when the link checkbox changes.""" self.use_link = self.form.checkbox_link.isChecked() - self.print_link_state() + self.print_link_state(self.use_link) + utils.set_param("Draft_array_Link", self.use_link) - def print_messages(self, selection): - """Print messages about the operation""" - if len(selection) == 1: - sel_obj = selection[0] + def print_messages(self): + """Print messages about the operation.""" + if len(self.selection) == 1: + sel_obj = self.selection[0] else: - # This can be changed so a compound of multiple - # selected objects is produced - sel_obj = selection[0] - _Msg("{}".format(16*"-")) - _Msg("{}".format(self.name)) - _Msg(_tr("Object:") + " {}".format(sel_obj.Label)) - _Msg(_tr("Radial distance:") + " {}".format(self.r_distance)) - _Msg(_tr("Tangential distance:") + " {}".format(self.tan_distance)) - _Msg(_tr("Number of circular layers:") + " {}".format(self.number)) - _Msg(_tr("Symmetry parameter:") + " {}".format(self.symmetry)) - _Msg(_tr("Center of rotation:") + # TODO: this should handle multiple objects. + # For example, it could take the shapes of all objects, + # make a compound and then use it as input for the array function. + sel_obj = self.selection[0] + _msg(_tr("Object:") + " {}".format(sel_obj.Label)) + _msg(_tr("Radial distance:") + " {}".format(self.r_distance)) + _msg(_tr("Tangential distance:") + " {}".format(self.tan_distance)) + _msg(_tr("Number of circular layers:") + " {}".format(self.number)) + _msg(_tr("Symmetry parameter:") + " {}".format(self.symmetry)) + _msg(_tr("Center of rotation:") + " ({0}, {1}, {2})".format(self.center.x, self.center.y, self.center.z)) - self.print_fuse_state() - self.print_link_state() + self.print_fuse_state(self.fuse) + self.print_link_state(self.use_link) def display_point(self, point=None, plane=None, mask=None): - """Displays the coordinates in the x, y, and z widgets. + """Display the coordinates in the x, y, and z widgets. This function should be used in a Coin callback so that the coordinate values are automatically updated when the @@ -307,21 +401,21 @@ class TaskPanelCircularArray: This was copied from `DraftGui.py` but needs to be improved for this particular command. - point : + point: Base::Vector3 is a vector that arrives by the callback. - plane : + plane: WorkingPlane is a `WorkingPlane` instance, for example, `App.DraftWorkingPlane`. It is not used at the moment, but could be used to set up the grid. - mask : + mask: str is a string that specifies which coordinate is being edited. It is used to restrict edition of a single coordinate. It is not used at the moment but could be used with a callback. """ # Get the coordinates to display - dp = None + d_p = None if point: - dp = point + d_p = point # Set the widgets to the value of the mouse pointer. # @@ -336,25 +430,28 @@ class TaskPanelCircularArray: # sbx = self.form.spinbox_c_x # sby = self.form.spinbox_c_y # sbz = self.form.spinbox_c_z - if dp: + if d_p: if self.mask in ('y', 'z'): - # sbx.setText(displayExternal(dp.x, None, 'Length')) - self.form.input_c_x.setProperty('rawValue', dp.x) + # sbx.setText(displayExternal(d_p.x, None, 'Length')) + self.form.input_c_x.setProperty('rawValue', d_p.x) else: - # sbx.setText(displayExternal(dp.x, None, 'Length')) - self.form.input_c_x.setProperty('rawValue', dp.x) + # sbx.setText(displayExternal(d_p.x, None, 'Length')) + self.form.input_c_x.setProperty('rawValue', d_p.x) if self.mask in ('x', 'z'): - # sby.setText(displayExternal(dp.y, None, 'Length')) - self.form.input_c_y.setProperty('rawValue', dp.y) + # sby.setText(displayExternal(d_p.y, None, 'Length')) + self.form.input_c_y.setProperty('rawValue', d_p.y) else: - # sby.setText(displayExternal(dp.y, None, 'Length')) - self.form.input_c_y.setProperty('rawValue', dp.y) + # sby.setText(displayExternal(d_p.y, None, 'Length')) + self.form.input_c_y.setProperty('rawValue', d_p.y) if self.mask in ('x', 'y'): - # sbz.setText(displayExternal(dp.z, None, 'Length')) - self.form.input_c_z.setProperty('rawValue', dp.z) + # sbz.setText(displayExternal(d_p.z, None, 'Length')) + self.form.input_c_z.setProperty('rawValue', d_p.z) else: - # sbz.setText(displayExternal(dp.z, None, 'Length')) - self.form.input_c_z.setProperty('rawValue', dp.z) + # sbz.setText(displayExternal(d_p.z, None, 'Length')) + self.form.input_c_z.setProperty('rawValue', d_p.z) + + if plane: + pass # Set masks if (mask == "x") or (self.mask == "x"): @@ -379,7 +476,7 @@ class TaskPanelCircularArray: self.set_focus() def set_focus(self, key=None): - """Set the focus on the widget that receives the key signal""" + """Set the focus on the widget that receives the key signal.""" if key is None or key == "x": self.form.input_c_x.setFocus() self.form.input_c_x.selectAll() @@ -391,12 +488,16 @@ class TaskPanelCircularArray: self.form.input_c_z.selectAll() def reject(self): - """Function that executes when clicking the Cancel button""" - _Msg(_tr("Aborted:") + " {}".format(self.name)) + """Execute when clicking the Cancel button or pressing Escape.""" + _msg(_tr("Aborted:") + " {}".format(_tr(self.name))) self.finish() def finish(self): - """Function that runs at the end after OK or Cancel""" + """Finish the command, after accept or reject. + + It finally calls the parent class to execute + the delayed functions, and perform cleanup. + """ # App.ActiveDocument.commitTransaction() Gui.ActiveDocument.resetEdit() # Runs the parent command to complete the call diff --git a/src/Mod/Draft/drafttaskpanels/task_orthoarray.py b/src/Mod/Draft/drafttaskpanels/task_orthoarray.py index 1eea177091..554d124599 100644 --- a/src/Mod/Draft/drafttaskpanels/task_orthoarray.py +++ b/src/Mod/Draft/drafttaskpanels/task_orthoarray.py @@ -1,8 +1,3 @@ -"""Provide the task panel for the Draft OrthoArray tool.""" -## @package task_orthoarray -# \ingroup DRAFT -# \brief Provide the task panel for the Draft OrthoArray tool. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -25,175 +20,227 @@ # * USA * # * * # *************************************************************************** - -import FreeCAD as App -import FreeCADGui as Gui -# import Draft -import Draft_rc -import DraftVecUtils +"""Provides the task panel for the Draft OrthoArray tool.""" +## @package task_orthoarray +# \ingroup DRAFT +# \brief Provide the task panel for the Draft OrthoArray tool. import PySide.QtGui as QtGui from PySide.QtCore import QT_TRANSLATE_NOOP -# import DraftTools -from draftutils.translate import translate -# from DraftGui import displayExternal -_Quantity = App.Units.Quantity +import FreeCAD as App +import FreeCADGui as Gui +import Draft_rc # include resources, icons, ui files +import DraftVecUtils +import draftutils.utils as utils +from draftutils.messages import _msg, _err, _log +from draftutils.translate import _tr +from FreeCAD import Units as U - -def _Msg(text, end="\n"): - """Print message with newline.""" - App.Console.PrintMessage(text + end) - - -def _Wrn(text, end="\n"): - """Print warning with newline.""" - App.Console.PrintWarning(text + end) - - -def _tr(text): - """Translate with the context set.""" - return translate("Draft", text) - - -# So the resource file doesn't trigger errors from code checkers (flake8) -True if Draft_rc else False +# The module is used to prevent complaints from code checkers (flake8) +bool(Draft_rc.__name__) class TaskPanelOrthoArray: - """TaskPanel for the OrthoArray command. + """TaskPanel code for the OrthoArray command. The names of the widgets are defined in the `.ui` file. - In this class all those widgets are automatically created - under the name `self.form.` + This `.ui` file `must` be loaded into an attribute + called `self.form` so that it is loaded into the task panel correctly. + + In this class all widgets are automatically created + as `self.form.`. The `.ui` file may use special FreeCAD widgets such as `Gui::InputField` (based on `QLineEdit`) and `Gui::QuantitySpinBox` (based on `QAbstractSpinBox`). See the Doxygen documentation of the corresponding files in `src/Gui/`, for example, `InputField.h` and `QuantitySpinBox.h`. + + Attributes + ---------- + source_command: gui_base.GuiCommandBase + This attribute holds a reference to the calling class + of this task panel. + This parent class, which is derived from `gui_base.GuiCommandBase`, + is responsible for calling this task panel, for installing + certain callbacks, and for removing them. + + It also delays the execution of the internal creation commands + by using the `draftutils.todo.ToDo` class. + + See Also + -------- + * https://forum.freecadweb.org/viewtopic.php?f=10&t=40007 + * https://forum.freecadweb.org/viewtopic.php?t=5374#p43038 """ def __init__(self): + self.name = "Orthogonal array" + _log(_tr("Task panel:") + "{}".format(_tr(self.name))) + + # The .ui file must be loaded into an attribute + # called `self.form` so that it is displayed in the task panel. ui_file = ":/ui/TaskPanel_OrthoArray.ui" self.form = Gui.PySideUic.loadUi(ui_file) - self.name = self.form.windowTitle() icon_name = "Draft_Array" svg = ":/icons/" + icon_name pix = QtGui.QPixmap(svg) icon = QtGui.QIcon.fromTheme(icon_name, QtGui.QIcon(svg)) self.form.setWindowIcon(icon) + self.form.setWindowTitle(_tr(self.name)) + self.form.label_icon.setPixmap(pix.scaled(32, 32)) - start_x = _Quantity(100.0, App.Units.Length) + # ------------------------------------------------------------------- + # Default values for the internal function, + # and for the task panel interface + start_x = U.Quantity(100.0, App.Units.Length) start_y = start_x start_z = start_x - start_zero = _Quantity(0.0, App.Units.Length) length_unit = start_x.getUserPreferred()[2] - self.form.input_X_x.setProperty('rawValue', start_x.Value) + self.v_x = App.Vector(start_x.Value, 0, 0) + self.v_y = App.Vector(0, start_y.Value, 0) + self.v_z = App.Vector(0, 0, start_z.Value) + + self.form.input_X_x.setProperty('rawValue', self.v_x.x) self.form.input_X_x.setProperty('unit', length_unit) - self.form.input_X_y.setProperty('rawValue', start_zero.Value) + self.form.input_X_y.setProperty('rawValue', self.v_x.y) self.form.input_X_y.setProperty('unit', length_unit) - self.form.input_X_z.setProperty('rawValue', start_zero.Value) + self.form.input_X_z.setProperty('rawValue', self.v_x.z) self.form.input_X_z.setProperty('unit', length_unit) - self.form.input_Y_x.setProperty('rawValue', start_zero.Value) + self.form.input_Y_x.setProperty('rawValue', self.v_y.x) self.form.input_Y_x.setProperty('unit', length_unit) - self.form.input_Y_y.setProperty('rawValue', start_y.Value) + self.form.input_Y_y.setProperty('rawValue', self.v_y.y) self.form.input_Y_y.setProperty('unit', length_unit) - self.form.input_Y_z.setProperty('rawValue', start_zero.Value) + self.form.input_Y_z.setProperty('rawValue', self.v_y.z) self.form.input_Y_z.setProperty('unit', length_unit) - self.form.input_Z_x.setProperty('rawValue', start_zero.Value) + self.form.input_Z_x.setProperty('rawValue', self.v_z.x) self.form.input_Z_x.setProperty('unit', length_unit) - self.form.input_Z_y.setProperty('rawValue', start_zero.Value) + self.form.input_Z_y.setProperty('rawValue', self.v_z.y) self.form.input_Z_y.setProperty('unit', length_unit) - self.form.input_Z_z.setProperty('rawValue', start_z.Value) + self.form.input_Z_z.setProperty('rawValue', self.v_z.z) self.form.input_Z_z.setProperty('unit', length_unit) - self.v_X = App.Vector(100, 0, 0) - self.v_Y = App.Vector(0, 100, 0) - self.v_Z = App.Vector(0, 0, 100) + self.n_x = 2 + self.n_y = 2 + self.n_z = 1 - # Old style for Qt4, avoid! - # QtCore.QObject.connect(self.form.button_reset, - # QtCore.SIGNAL("clicked()"), - # self.reset_point) + self.form.spinbox_n_X.setValue(self.n_x) + self.form.spinbox_n_Y.setValue(self.n_y) + self.form.spinbox_n_Z.setValue(self.n_z) + + self.fuse = utils.get_param("Draft_array_fuse", False) + self.use_link = utils.get_param("Draft_array_Link", True) + + self.form.checkbox_fuse.setChecked(self.fuse) + self.form.checkbox_link.setChecked(self.use_link) + # ------------------------------------------------------------------- + + # Some objects need to be selected before we can execute the function. + self.selection = None + + # This is used to test the input of the internal function. + # It should be changed to True before we can execute the function. + self.valid_input = False + + self.set_widget_callbacks() + + self.tr_true = QT_TRANSLATE_NOOP("Draft", "True") + self.tr_false = QT_TRANSLATE_NOOP("Draft", "False") + + def set_widget_callbacks(self): + """Set up the callbacks (slots) for the widget signals.""" # New style for Qt5 self.form.button_reset_X.clicked.connect(lambda: self.reset_v("X")) self.form.button_reset_Y.clicked.connect(lambda: self.reset_v("Y")) self.form.button_reset_Z.clicked.connect(lambda: self.reset_v("Z")) - self.n_X = 2 - self.n_Y = 2 - self.n_Z = 1 - - self.form.spinbox_n_X.setValue(self.n_X) - self.form.spinbox_n_Y.setValue(self.n_Y) - self.form.spinbox_n_Z.setValue(self.n_Z) - - self.valid_input = False - - # When the checkbox changes, change the fuse value - self.fuse = False + # When the checkbox changes, change the internal value self.form.checkbox_fuse.stateChanged.connect(self.set_fuse) - - self.use_link = False self.form.checkbox_link.stateChanged.connect(self.set_link) + # Old style for Qt4, avoid! + # QtCore.QObject.connect(self.form.button_reset, + # QtCore.SIGNAL("clicked()"), + # self.reset_point) + def accept(self): - """Execute when clicking the OK button.""" - selection = Gui.Selection.getSelection() - n_X = self.form.spinbox_n_X.value() - n_Y = self.form.spinbox_n_Y.value() - n_Z = self.form.spinbox_n_Z.value() - self.valid_input = self.validate_input(selection, - n_X, - n_Y, - n_Z) + """Execute when clicking the OK button or Enter key.""" + self.selection = Gui.Selection.getSelection() + + (self.v_x, + self.v_y, + self.v_z) = self.get_intervals() + + (self.n_x, + self.n_y, + self.n_z) = self.get_numbers() + + self.valid_input = self.validate_input(self.selection, + self.v_x, self.v_y, self.v_z, + self.n_x, self.n_y, self.n_z) if self.valid_input: - self.create_object(selection) - self.print_messages(selection) + self.create_object() + self.print_messages() self.finish() - def validate_input(self, selection, n_X, n_Y, n_Z): - """Check that the input is valid.""" + def validate_input(self, selection, + v_x, v_y, v_z, + n_x, n_y, n_z): + """Check that the input is valid. + + Some values may not need to be checked because + the interface may not allow to input wrong data. + """ if not selection: - _Wrn(_tr("At least one element must be selected")) + _err(_tr("At least one element must be selected.")) return False - if n_X < 1 or n_Y < 1 or n_Z < 1: - _Wrn(_tr("Number of elements must be at least 1")) + + if n_x < 1 or n_y < 1 or n_z < 1: + _err(_tr("Number of elements must be at least 1.")) return False - # Todo: each of the elements of the selection could be tested, - # not only the first one. + + # TODO: this should handle multiple objects. + # Each of the elements of the selection should be tested. obj = selection[0] if obj.isDerivedFrom("App::FeaturePython"): - _Wrn(_tr("Selection is not suitable for array")) - _Wrn(_tr("Object:") + " {0} ({1})".format(obj.Label, obj.TypeId)) + _err(_tr("Selection is not suitable for array.")) + _err(_tr("Object:") + " {0} ({1})".format(obj.Label, obj.TypeId)) return False - return True - def create_object(self, selection): - """Create the actual object.""" - self.v_X, self.v_Y, self.v_Z = self.set_intervals() - self.n_X, self.n_Y, self.n_Z = self.set_numbers() - - if len(selection) == 1: - sel_obj = selection[0] - else: - # This can be changed so a compound of multiple - # selected objects is produced - sel_obj = selection[0] + # The other arguments are not tested but they should be present. + if v_x and v_y and v_z: + pass self.fuse = self.form.checkbox_fuse.isChecked() self.use_link = self.form.checkbox_link.isChecked() + return True + + def create_object(self): + """Create the new object. + + At this stage we already tested that the input is correct + so the necessary attributes are already set. + Then we proceed with the internal function to create the new object. + """ + if len(self.selection) == 1: + sel_obj = self.selection[0] + else: + # TODO: this should handle multiple objects. + # For example, it could take the shapes of all objects, + # make a compound and then use it as input for the array function. + sel_obj = self.selection[0] # This creates the object immediately # obj = Draft.makeArray(sel_obj, - # self.v_X, self.v_Y, self.v_Z, - # self.n_X, self.n_Y, self.n_Z) + # self.v_x, self.v_y, self.v_z, + # self.n_x, self.n_y, self.n_z, + # self.use_link) # if obj: # obj.Fuse = self.fuse @@ -201,146 +248,162 @@ class TaskPanelOrthoArray: # of this class, the GuiCommand. # This is needed to schedule geometry manipulation # that would crash Coin3D if done in the event callback. - _cmd = "obj = Draft.makeArray(" - _cmd += "FreeCAD.ActiveDocument." + sel_obj.Name + ", " - _cmd += "arg1=" + DraftVecUtils.toString(self.v_X) + ", " - _cmd += "arg2=" + DraftVecUtils.toString(self.v_Y) + ", " - _cmd += "arg3=" + DraftVecUtils.toString(self.v_Z) + ", " - _cmd += "arg4=" + str(self.n_X) + ", " - _cmd += "arg5=" + str(self.n_Y) + ", " - _cmd += "arg6=" + str(self.n_Z) + ", " + _cmd = "draftobjects.orthoarray.make_ortho_array" + _cmd += "(" + _cmd += "App.ActiveDocument." + sel_obj.Name + ", " + _cmd += "v_x=" + DraftVecUtils.toString(self.v_x) + ", " + _cmd += "v_y=" + DraftVecUtils.toString(self.v_y) + ", " + _cmd += "v_z=" + DraftVecUtils.toString(self.v_z) + ", " + _cmd += "n_x=" + str(self.n_x) + ", " + _cmd += "n_y=" + str(self.n_y) + ", " + _cmd += "n_z=" + str(self.n_z) + ", " _cmd += "use_link=" + str(self.use_link) _cmd += ")" - _cmd_list = ["FreeCADGui.addModule('Draft')", - _cmd, + _cmd_list = ["Gui.addModule('Draft')", + "Gui.addModule('draftobjects.orthoarray')", + "obj = " + _cmd, "obj.Fuse = " + str(self.fuse), "Draft.autogroup(obj)", - "FreeCAD.ActiveDocument.recompute()"] - self.source_command.commit("Ortho array", _cmd_list) + "App.ActiveDocument.recompute()"] - def set_numbers(self): - """Assign the number of elements.""" - self.n_X = self.form.spinbox_n_X.value() - self.n_Y = self.form.spinbox_n_Y.value() - self.n_Z = self.form.spinbox_n_Z.value() - return self.n_X, self.n_Y, self.n_Z + # We commit the command list through the parent command + self.source_command.commit(_tr(self.name), _cmd_list) - def set_intervals(self): - """Assign the interval vectors.""" - v_X_x_str = self.form.input_X_x.text() - v_X_y_str = self.form.input_X_y.text() - v_X_z_str = self.form.input_X_z.text() - self.v_X = App.Vector(_Quantity(v_X_x_str).Value, - _Quantity(v_X_y_str).Value, - _Quantity(v_X_z_str).Value) + def get_numbers(self): + """Get the number of elements from the widgets.""" + return (self.form.spinbox_n_X.value(), + self.form.spinbox_n_Y.value(), + self.form.spinbox_n_Z.value()) - v_Y_x_str = self.form.input_Y_x.text() - v_Y_y_str = self.form.input_Y_y.text() - v_Y_z_str = self.form.input_Y_z.text() - self.v_Y = App.Vector(_Quantity(v_Y_x_str).Value, - _Quantity(v_Y_y_str).Value, - _Quantity(v_Y_z_str).Value) + def get_intervals(self): + """Get the interval vectors from the widgets.""" + v_x_x_str = self.form.input_X_x.text() + v_x_y_str = self.form.input_X_y.text() + v_x_z_str = self.form.input_X_z.text() + v_x = App.Vector(U.Quantity(v_x_x_str).Value, + U.Quantity(v_x_y_str).Value, + U.Quantity(v_x_z_str).Value) - v_Z_x_str = self.form.input_Z_x.text() - v_Z_y_str = self.form.input_Z_y.text() - v_Z_z_str = self.form.input_Z_z.text() - self.v_Z = App.Vector(_Quantity(v_Z_x_str).Value, - _Quantity(v_Z_y_str).Value, - _Quantity(v_Z_z_str).Value) - return self.v_X, self.v_Y, self.v_Z + v_y_x_str = self.form.input_Y_x.text() + v_y_y_str = self.form.input_Y_y.text() + v_y_z_str = self.form.input_Y_z.text() + v_y = App.Vector(U.Quantity(v_y_x_str).Value, + U.Quantity(v_y_y_str).Value, + U.Quantity(v_y_z_str).Value) + + v_z_x_str = self.form.input_Z_x.text() + v_z_y_str = self.form.input_Z_y.text() + v_z_z_str = self.form.input_Z_z.text() + v_z = App.Vector(U.Quantity(v_z_x_str).Value, + U.Quantity(v_z_y_str).Value, + U.Quantity(v_z_z_str).Value) + return v_x, v_y, v_z def reset_v(self, interval): - """Reset the interval to zero distance.""" + """Reset the interval to zero distance. + + Parameters + ---------- + interval: str + Either "X", "Y", "Z", to reset the interval vector + for that direction. + """ if interval == "X": self.form.input_X_x.setProperty('rawValue', 100) self.form.input_X_y.setProperty('rawValue', 0) self.form.input_X_z.setProperty('rawValue', 0) - _Msg(_tr("Interval X reset:") - + " ({0}, {1}, {2})".format(self.v_X.x, - self.v_X.y, - self.v_X.z)) + self.v_x, self.v_y, self.v_z = self.get_intervals() + _msg(_tr("Interval X reset:") + + " ({0}, {1}, {2})".format(self.v_x.x, + self.v_x.y, + self.v_x.z)) elif interval == "Y": self.form.input_Y_x.setProperty('rawValue', 0) self.form.input_Y_y.setProperty('rawValue', 100) self.form.input_Y_z.setProperty('rawValue', 0) - _Msg(_tr("Interval Y reset:") - + " ({0}, {1}, {2})".format(self.v_Y.x, - self.v_Y.y, - self.v_Y.z)) + self.v_x, self.v_y, self.v_z = self.get_intervals() + _msg(_tr("Interval Y reset:") + + " ({0}, {1}, {2})".format(self.v_y.x, + self.v_y.y, + self.v_y.z)) elif interval == "Z": self.form.input_Z_x.setProperty('rawValue', 0) self.form.input_Z_y.setProperty('rawValue', 0) self.form.input_Z_z.setProperty('rawValue', 100) - _Msg(_tr("Interval Z reset:") - + " ({0}, {1}, {2})".format(self.v_Z.x, - self.v_Z.y, - self.v_Z.z)) + self.v_x, self.v_y, self.v_z = self.get_intervals() + _msg(_tr("Interval Z reset:") + + " ({0}, {1}, {2})".format(self.v_z.x, + self.v_z.y, + self.v_z.z)) - self.n_X, self.n_Y, self.n_Z = self.set_intervals() - - def print_fuse_state(self): - """Print the state translated.""" - if self.fuse: - translated_state = QT_TRANSLATE_NOOP("Draft", "True") + def print_fuse_state(self, fuse): + """Print the fuse state translated.""" + if fuse: + state = self.tr_true else: - translated_state = QT_TRANSLATE_NOOP("Draft", "False") - _Msg(_tr("Fuse:") + " {}".format(translated_state)) + state = self.tr_false + _msg(_tr("Fuse:") + " {}".format(state)) def set_fuse(self): - """Run callback when the fuse checkbox changes.""" + """Execute as a callback when the fuse checkbox changes.""" self.fuse = self.form.checkbox_fuse.isChecked() - self.print_fuse_state() + self.print_fuse_state(self.fuse) + utils.set_param("Draft_array_fuse", self.fuse) - def print_link_state(self): - """Print the state translated.""" - if self.use_link: - translated_state = QT_TRANSLATE_NOOP("Draft", "True") + def print_link_state(self, use_link): + """Print the link state translated.""" + if use_link: + state = self.tr_true else: - translated_state = QT_TRANSLATE_NOOP("Draft", "False") - _Msg(_tr("Use Link object:") + " {}".format(translated_state)) + state = self.tr_false + _msg(_tr("Create Link array:") + " {}".format(state)) def set_link(self): - """Run callback when the link checkbox changes.""" + """Execute as a callback when the link checkbox changes.""" self.use_link = self.form.checkbox_link.isChecked() - self.print_link_state() + self.print_link_state(self.use_link) + utils.set_param("Draft_array_Link", self.use_link) - def print_messages(self, selection): + def print_messages(self): """Print messages about the operation.""" - if len(selection) == 1: - sel_obj = selection[0] + if len(self.selection) == 1: + sel_obj = self.selection[0] else: - # This can be changed so a compound of multiple - # selected objects is produced - sel_obj = selection[0] - _Msg("{}".format(16*"-")) - _Msg("{}".format(self.name)) - _Msg(_tr("Object:") + " {}".format(sel_obj.Label)) - _Msg(_tr("Number of X elements:") + " {}".format(self.n_X)) - _Msg(_tr("Interval X:") - + " ({0}, {1}, {2})".format(self.v_X.x, - self.v_X.y, - self.v_X.z)) - _Msg(_tr("Number of Y elements:") + " {}".format(self.n_Y)) - _Msg(_tr("Interval Y:") - + " ({0}, {1}, {2})".format(self.v_Y.x, - self.v_Y.y, - self.v_Y.z)) - _Msg(_tr("Number of Z elements:") + " {}".format(self.n_Z)) - _Msg(_tr("Interval Z:") - + " ({0}, {1}, {2})".format(self.v_Z.x, - self.v_Z.y, - self.v_Z.z)) - self.print_fuse_state() - self.print_link_state() + # TODO: this should handle multiple objects. + # For example, it could take the shapes of all objects, + # make a compound and then use it as input for the array function. + sel_obj = self.selection[0] + _msg(_tr("Object:") + " {}".format(sel_obj.Label)) + _msg(_tr("Number of X elements:") + " {}".format(self.n_x)) + _msg(_tr("Interval X:") + + " ({0}, {1}, {2})".format(self.v_x.x, + self.v_x.y, + self.v_x.z)) + _msg(_tr("Number of Y elements:") + " {}".format(self.n_y)) + _msg(_tr("Interval Y:") + + " ({0}, {1}, {2})".format(self.v_y.x, + self.v_y.y, + self.v_y.z)) + _msg(_tr("Number of Z elements:") + " {}".format(self.n_z)) + _msg(_tr("Interval Z:") + + " ({0}, {1}, {2})".format(self.v_z.x, + self.v_z.y, + self.v_z.z)) + self.print_fuse_state(self.fuse) + self.print_link_state(self.use_link) def reject(self): - """Run when clicking the Cancel button.""" - _Msg(_tr("Aborted:") + " {}".format(self.name)) + """Execute when clicking the Cancel button or pressing Escape.""" + _msg(_tr("Aborted:") + " {}".format(_tr(self.name))) self.finish() def finish(self): - """Run at the end after OK or Cancel.""" + """Finish the command, after accept or reject. + + It finally calls the parent class to execute + the delayed functions, and perform cleanup. + """ # App.ActiveDocument.commitTransaction() Gui.ActiveDocument.resetEdit() # Runs the parent command to complete the call diff --git a/src/Mod/Draft/drafttaskpanels/task_polararray.py b/src/Mod/Draft/drafttaskpanels/task_polararray.py index 042a18a8a3..48d55c2a53 100644 --- a/src/Mod/Draft/drafttaskpanels/task_polararray.py +++ b/src/Mod/Draft/drafttaskpanels/task_polararray.py @@ -1,9 +1,3 @@ -"""This module provides the task panel for the Draft PolarArray tool. -""" -## @package task_polararray -# \ingroup DRAFT -# \brief This module provides the task panel code for the PolarArray tool. - # *************************************************************************** # * (c) 2019 Eliud Cabrera Castillo * # * * @@ -26,163 +20,221 @@ # * USA * # * * # *************************************************************************** +"""Provides the task panel for the Draft PolarArray tool.""" +## @package task_polararray +# \ingroup DRAFT +# \brief This module provides the task panel code for the PolarArray tool. + +import PySide.QtGui as QtGui +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui -# import Draft -import Draft_rc +import Draft_rc # include resources, icons, ui files import DraftVecUtils +import draftutils.utils as utils +from draftutils.messages import _msg, _wrn, _err, _log +from draftutils.translate import _tr +from FreeCAD import Units as U -import PySide.QtCore as QtCore -import PySide.QtGui as QtGui -from PySide.QtCore import QT_TRANSLATE_NOOP -# import DraftTools -from DraftGui import translate -# from DraftGui import displayExternal - -_Quantity = App.Units.Quantity - - -def _Msg(text, end="\n"): - """Print message with newline""" - App.Console.PrintMessage(text + end) - - -def _Wrn(text, end="\n"): - """Print warning with newline""" - App.Console.PrintWarning(text + end) - - -def _tr(text): - """Function to translate with the context set""" - return translate("Draft", text) - - -# So the resource file doesn't trigger errors from code checkers (flake8) -True if Draft_rc.__name__ else False +# The module is used to prevent complaints from code checkers (flake8) +bool(Draft_rc.__name__) class TaskPanelPolarArray: - """TaskPanel for the PolarArray command. + """TaskPanel code for the PolarArray command. The names of the widgets are defined in the `.ui` file. - In this class all those widgets are automatically created - under the name `self.form.` + This `.ui` file `must` be loaded into an attribute + called `self.form` so that it is loaded into the task panel correctly. + + In this class all widgets are automatically created + as `self.form.`. The `.ui` file may use special FreeCAD widgets such as `Gui::InputField` (based on `QLineEdit`) and `Gui::QuantitySpinBox` (based on `QAbstractSpinBox`). See the Doxygen documentation of the corresponding files in `src/Gui/`, for example, `InputField.h` and `QuantitySpinBox.h`. + + Attributes + ---------- + source_command: gui_base.GuiCommandBase + This attribute holds a reference to the calling class + of this task panel. + This parent class, which is derived from `gui_base.GuiCommandBase`, + is responsible for calling this task panel, for installing + certain callbacks, and for removing them. + + It also delays the execution of the internal creation commands + by using the `draftutils.todo.ToDo` class. + + See Also + -------- + * https://forum.freecadweb.org/viewtopic.php?f=10&t=40007 + * https://forum.freecadweb.org/viewtopic.php?t=5374#p43038 """ def __init__(self): + self.name = "Polar array" + _log(_tr("Task panel:") + "{}".format(_tr(self.name))) + + # The .ui file must be loaded into an attribute + # called `self.form` so that it is displayed in the task panel. ui_file = ":/ui/TaskPanel_PolarArray.ui" self.form = Gui.PySideUic.loadUi(ui_file) - self.name = self.form.windowTitle() icon_name = "Draft_PolarArray" svg = ":/icons/" + icon_name pix = QtGui.QPixmap(svg) icon = QtGui.QIcon.fromTheme(icon_name, QtGui.QIcon(svg)) self.form.setWindowIcon(icon) + self.form.setWindowTitle(_tr(self.name)) + self.form.label_icon.setPixmap(pix.scaled(32, 32)) - start_angle = _Quantity(180.0, App.Units.Angle) + # ------------------------------------------------------------------- + # Default values for the internal function, + # and for the task panel interface + start_angle = U.Quantity(360.0, App.Units.Angle) angle_unit = start_angle.getUserPreferred()[2] - self.form.spinbox_angle.setProperty('rawValue', start_angle.Value) - self.form.spinbox_angle.setProperty('unit', angle_unit) - self.form.spinbox_number.setValue(4) - self.angle_str = self.form.spinbox_angle.text() self.angle = start_angle.Value + self.number = 5 - self.number = self.form.spinbox_number.value() + self.form.spinbox_angle.setProperty('rawValue', self.angle) + self.form.spinbox_angle.setProperty('unit', angle_unit) - start_point = _Quantity(0.0, App.Units.Length) + self.form.spinbox_number.setValue(self.number) + + start_point = U.Quantity(0.0, App.Units.Length) length_unit = start_point.getUserPreferred()[2] - self.form.input_c_x.setProperty('rawValue', start_point.Value) + + self.center = App.Vector(start_point.Value, + start_point.Value, + start_point.Value) + + self.form.input_c_x.setProperty('rawValue', self.center.x) self.form.input_c_x.setProperty('unit', length_unit) - self.form.input_c_y.setProperty('rawValue', start_point.Value) + self.form.input_c_y.setProperty('rawValue', self.center.y) self.form.input_c_y.setProperty('unit', length_unit) - self.form.input_c_z.setProperty('rawValue', start_point.Value) + self.form.input_c_z.setProperty('rawValue', self.center.z) self.form.input_c_z.setProperty('unit', length_unit) - self.valid_input = True - self.c_x_str = "" - self.c_y_str = "" - self.c_z_str = "" - self.center = App.Vector(0, 0, 0) + self.fuse = utils.get_param("Draft_array_fuse", False) + self.use_link = utils.get_param("Draft_array_Link", True) - # Old style for Qt4 - # QtCore.QObject.connect(self.form.button_reset, - # QtCore.SIGNAL("clicked()"), - # self.reset_point) - # New style for Qt5 - self.form.button_reset.clicked.connect(self.reset_point) + self.form.checkbox_fuse.setChecked(self.fuse) + self.form.checkbox_link.setChecked(self.use_link) + # ------------------------------------------------------------------- + + # Some objects need to be selected before we can execute the function. + self.selection = None + + # This is used to test the input of the internal function. + # It should be changed to True before we can execute the function. + self.valid_input = False + + self.set_widget_callbacks() + + self.tr_true = QT_TRANSLATE_NOOP("Draft", "True") + self.tr_false = QT_TRANSLATE_NOOP("Draft", "False") # The mask is not used at the moment, but could be used in the future # by a callback to restrict the coordinates of the pointer. self.mask = "" - # When the checkbox changes, change the fuse value - self.fuse = False - QtCore.QObject.connect(self.form.checkbox_fuse, - QtCore.SIGNAL("stateChanged(int)"), - self.set_fuse) + def set_widget_callbacks(self): + """Set up the callbacks (slots) for the widget signals.""" + # New style for Qt5 + self.form.button_reset.clicked.connect(self.reset_point) - self.use_link = False - QtCore.QObject.connect(self.form.checkbox_link, - QtCore.SIGNAL("stateChanged(int)"), - self.set_link) + # When the checkbox changes, change the internal value + self.form.checkbox_fuse.stateChanged.connect(self.set_fuse) + self.form.checkbox_link.stateChanged.connect(self.set_link) + + # Old style for Qt4, avoid! + # QtCore.QObject.connect(self.form.button_reset, + # QtCore.SIGNAL("clicked()"), + # self.reset_point) def accept(self): - """Function that executes when clicking the OK button""" - selection = Gui.Selection.getSelection() - self.number = self.form.spinbox_number.value() - self.valid_input = self.validate_input(selection, - self.number) + """Execute when clicking the OK button or Enter key.""" + self.selection = Gui.Selection.getSelection() + + (self.number, + self.angle) = self.get_number_angle() + + self.center = self.get_center() + + self.valid_input = self.validate_input(self.selection, + self.number, + self.angle, + self.center) if self.valid_input: - self.create_object(selection) - self.print_messages(selection) + self.create_object() + self.print_messages() self.finish() - def validate_input(self, selection, number): - """Check that the input is valid""" + def validate_input(self, selection, + number, angle, center): + """Check that the input is valid. + + Some values may not need to be checked because + the interface may not allow to input wrong data. + """ if not selection: - _Wrn(_tr("At least one element must be selected")) + _err(_tr("At least one element must be selected.")) return False + + # TODO: this should handle multiple objects. + # Each of the elements of the selection should be tested. + obj = selection[0] + if obj.isDerivedFrom("App::FeaturePython"): + _err(_tr("Selection is not suitable for array.")) + _err(_tr("Object:") + " {}".format(selection[0].Label)) + return False + if number < 2: - _Wrn(_tr("Number of elements must be at least 2")) + _err(_tr("Number of elements must be at least 2.")) return False - # Todo: each of the elements of the selection could be tested, - # not only the first one. - if selection[0].isDerivedFrom("App::FeaturePython"): - _Wrn(_tr("Selection is not suitable for array")) - _Wrn(_tr("Object:") + " {}".format(selection[0].Label)) - return False - return True - def create_object(self, selection): - """Create the actual object""" - self.angle_str = self.form.spinbox_angle.text() - self.angle = _Quantity(self.angle_str).Value + if angle > 360: + _wrn(_tr("The angle is above 360 degrees. " + "It is set to this value to proceed.")) + self.angle = 360 + elif angle < -360: + _wrn(_tr("The angle is below -360 degrees. " + "It is set to this value to proceed.")) + self.angle = -360 - self.center = self.set_point() - - if len(selection) == 1: - sel_obj = selection[0] - else: - # This can be changed so a compound of multiple - # selected objects is produced - sel_obj = selection[0] + # The other arguments are not tested but they should be present. + if center: + pass self.fuse = self.form.checkbox_fuse.isChecked() self.use_link = self.form.checkbox_link.isChecked() + return True + + def create_object(self): + """Create the new object. + + At this stage we already tested that the input is correct + so the necessary attributes are already set. + Then we proceed with the internal function to create the new object. + """ + if len(self.selection) == 1: + sel_obj = self.selection[0] + else: + # TODO: this should handle multiple objects. + # For example, it could take the shapes of all objects, + # make a compound and then use it as input for the array function. + sel_obj = self.selection[0] # This creates the object immediately # obj = Draft.makeArray(sel_obj, - # self.center, self.angle, self.number) + # self.center, self.angle, self.number, + # self.use_link) # if obj: # obj.Fuse = self.fuse @@ -190,91 +242,104 @@ class TaskPanelPolarArray: # of this class, the GuiCommand. # This is needed to schedule geometry manipulation # that would crash Coin3D if done in the event callback. - _cmd = "obj = Draft.makeArray(" - _cmd += "FreeCAD.ActiveDocument." + sel_obj.Name + ", " - _cmd += "arg1=" + DraftVecUtils.toString(self.center) + ", " - _cmd += "arg2=" + str(self.angle) + ", " - _cmd += "arg3=" + str(self.number) + ", " + _cmd = "draftobjects.polararray.make_polar_array" + _cmd += "(" + _cmd += "App.ActiveDocument." + sel_obj.Name + ", " + _cmd += "number=" + str(self.number) + ", " + _cmd += "angle=" + str(self.angle) + ", " + _cmd += "center=" + DraftVecUtils.toString(self.center) + ", " _cmd += "use_link=" + str(self.use_link) _cmd += ")" - _cmd_list = ["FreeCADGui.addModule('Draft')", - _cmd, + _cmd_list = ["Gui.addModule('Draft')", + "Gui.addModule('draftobjects.polararray')", + "obj = " + _cmd, "obj.Fuse = " + str(self.fuse), "Draft.autogroup(obj)", - "FreeCAD.ActiveDocument.recompute()"] - self.source_command.commit("Polar array", _cmd_list) + "App.ActiveDocument.recompute()"] - def set_point(self): - """Assign the values to the center""" - self.c_x_str = self.form.input_c_x.text() - self.c_y_str = self.form.input_c_y.text() - self.c_z_str = self.form.input_c_z.text() - center = App.Vector(_Quantity(self.c_x_str).Value, - _Quantity(self.c_y_str).Value, - _Quantity(self.c_z_str).Value) + # We commit the command list through the parent command + self.source_command.commit(_tr(self.name), _cmd_list) + + def get_number_angle(self): + """Get the number and angle parameters from the widgets.""" + number = self.form.spinbox_number.value() + + angle_str = self.form.spinbox_angle.text() + angle = U.Quantity(angle_str).Value + return number, angle + + def get_center(self): + """Get the value of the center from the widgets.""" + c_x_str = self.form.input_c_x.text() + c_y_str = self.form.input_c_y.text() + c_z_str = self.form.input_c_z.text() + center = App.Vector(U.Quantity(c_x_str).Value, + U.Quantity(c_y_str).Value, + U.Quantity(c_z_str).Value) return center def reset_point(self): - """Reset the point to the original distance""" + """Reset the center point to the original distance.""" self.form.input_c_x.setProperty('rawValue', 0) self.form.input_c_y.setProperty('rawValue', 0) self.form.input_c_z.setProperty('rawValue', 0) - self.center = self.set_point() - _Msg(_tr("Center reset:") + self.center = self.get_center() + _msg(_tr("Center reset:") + " ({0}, {1}, {2})".format(self.center.x, self.center.y, self.center.z)) - def print_fuse_state(self): - """Print the state translated""" - if self.fuse: - translated_state = QT_TRANSLATE_NOOP("Draft", "True") + def print_fuse_state(self, fuse): + """Print the fuse state translated.""" + if fuse: + state = self.tr_true else: - translated_state = QT_TRANSLATE_NOOP("Draft", "False") - _Msg(_tr("Fuse:") + " {}".format(translated_state)) + state = self.tr_false + _msg(_tr("Fuse:") + " {}".format(state)) def set_fuse(self): - """This function is called when the fuse checkbox changes""" + """Execute as a callback when the fuse checkbox changes.""" self.fuse = self.form.checkbox_fuse.isChecked() - self.print_fuse_state() + self.print_fuse_state(self.fuse) + utils.set_param("Draft_array_fuse", self.fuse) - def print_link_state(self): - """Print the state translated""" - if self.use_link: - translated_state = QT_TRANSLATE_NOOP("Draft", "True") + def print_link_state(self, use_link): + """Print the link state translated.""" + if use_link: + state = self.tr_true else: - translated_state = QT_TRANSLATE_NOOP("Draft", "False") - _Msg(_tr("Use Link object:") + " {}".format(translated_state)) + state = self.tr_false + _msg(_tr("Create Link array:") + " {}".format(state)) def set_link(self): - """This function is called when the fuse checkbox changes""" + """Execute as a callback when the link checkbox changes.""" self.use_link = self.form.checkbox_link.isChecked() - self.print_link_state() + self.print_link_state(self.use_link) + utils.set_param("Draft_array_Link", self.use_link) - def print_messages(self, selection): - """Print messages about the operation""" - if len(selection) == 1: - sel_obj = selection[0] + def print_messages(self): + """Print messages about the operation.""" + if len(self.selection) == 1: + sel_obj = self.selection[0] else: - # This can be changed so a compound of multiple - # selected objects is produced - sel_obj = selection[0] - _Msg("{}".format(16*"-")) - _Msg("{}".format(self.name)) - _Msg(_tr("Object:") + " {}".format(sel_obj.Label)) - _Msg(_tr("Start angle:") + " {}".format(self.angle_str)) - _Msg(_tr("Number of elements:") + " {}".format(self.number)) - _Msg(_tr("Center of rotation:") + # TODO: this should handle multiple objects. + # For example, it could take the shapes of all objects, + # make a compound and then use it as input for the array function. + sel_obj = self.selection[0] + _msg(_tr("Object:") + " {}".format(sel_obj.Label)) + _msg(_tr("Number of elements:") + " {}".format(self.number)) + _msg(_tr("Polar angle:") + " {}".format(self.angle)) + _msg(_tr("Center of rotation:") + " ({0}, {1}, {2})".format(self.center.x, self.center.y, self.center.z)) - self.print_fuse_state() - self.print_link_state() + self.print_fuse_state(self.fuse) + self.print_link_state(self.use_link) def display_point(self, point=None, plane=None, mask=None): - """Displays the coordinates in the x, y, and z widgets. + """Display the coordinates in the x, y, and z widgets. This function should be used in a Coin callback so that the coordinate values are automatically updated when the @@ -331,6 +396,9 @@ class TaskPanelPolarArray: # sbz.setText(displayExternal(dp.z, None, 'Length')) self.form.input_c_z.setProperty('rawValue', dp.z) + if plane: + pass + # Set masks if (mask == "x") or (self.mask == "x"): self.form.input_c_x.setEnabled(True) @@ -354,7 +422,7 @@ class TaskPanelPolarArray: self.set_focus() def set_focus(self, key=None): - """Set the focus on the widget that receives the key signal""" + """Set the focus on the widget that receives the key signal.""" if key is None or key == "x": self.form.input_c_x.setFocus() self.form.input_c_x.selectAll() @@ -366,12 +434,16 @@ class TaskPanelPolarArray: self.form.input_c_z.selectAll() def reject(self): - """Function that executes when clicking the Cancel button""" - _Msg(_tr("Aborted:") + " {}".format(self.name)) + """Execute when clicking the Cancel button or pressing Escape.""" + _msg(_tr("Aborted:") + " {}".format(_tr(self.name))) self.finish() def finish(self): - """Function that runs at the end after OK or Cancel""" + """Finish the command, after accept or reject. + + It finally calls the parent class to execute + the delayed functions, and perform cleanup. + """ # App.ActiveDocument.commitTransaction() Gui.ActiveDocument.resetEdit() # Runs the parent command to complete the call diff --git a/src/Mod/Draft/drafttaskpanels/task_scale.py b/src/Mod/Draft/drafttaskpanels/task_scale.py new file mode 100644 index 0000000000..25bfb86d74 --- /dev/null +++ b/src/Mod/Draft/drafttaskpanels/task_scale.py @@ -0,0 +1,157 @@ +"""Provide the task panel for the Draft Scale tool.""" +## @package task_scale +# \ingroup DRAFT +# \brief Provide the task panel for the Draft Scale tool. + +# *************************************************************************** +# * (c) 2009 Yorik van Havre * +# * (c) 2020 Eliud Cabrera Castillo * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * FreeCAD 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 FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD as App +import FreeCADGui as Gui +import Draft +import Draft_rc +import PySide.QtCore as QtCore +import PySide.QtGui as QtGui +from draftutils.translate import translate +_Quantity = App.Units.Quantity + + +# So the resource file doesn't trigger errors from code checkers (flake8) +True if Draft_rc.__name__ else False + + +class ScaleTaskPanel: + """The task panel for the Draft Scale tool.""" + + def __init__(self): + self.sourceCmd = None + self.form = QtGui.QWidget() + layout = QtGui.QGridLayout(self.form) + self.xLabel = QtGui.QLabel() + layout.addWidget(self.xLabel, 0, 0, 1, 1) + self.xValue = QtGui.QDoubleSpinBox() + self.xValue.setRange(0.0000001, 1000000.0) + self.xValue.setDecimals(Draft.getParam("precision")) + self.xValue.setValue(1) + layout.addWidget(self.xValue,0,1,1,1) + self.yLabel = QtGui.QLabel() + layout.addWidget(self.yLabel,1,0,1,1) + self.yValue = QtGui.QDoubleSpinBox() + self.yValue.setRange(.0000001,1000000.0) + self.yValue.setDecimals(Draft.getParam("precision")) + self.yValue.setValue(1) + layout.addWidget(self.yValue,1,1,1,1) + self.zLabel = QtGui.QLabel() + layout.addWidget(self.zLabel,2,0,1,1) + self.zValue = QtGui.QDoubleSpinBox() + self.zValue.setRange(.0000001,1000000.0) + self.zValue.setDecimals(Draft.getParam("precision")) + self.zValue.setValue(1) + layout.addWidget(self.zValue,2,1,1,1) + self.lock = QtGui.QCheckBox() + self.lock.setChecked(App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleUniform", False)) + layout.addWidget(self.lock,3,0,1,2) + self.relative = QtGui.QCheckBox() + self.relative.setChecked(App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleRelative", False)) + layout.addWidget(self.relative,4,0,1,2) + self.isCopy = QtGui.QCheckBox() + self.isCopy.setChecked(App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleCopy", False)) + layout.addWidget(self.isCopy,5,0,1,2) + self.isSubelementMode = QtGui.QCheckBox() + layout.addWidget(self.isSubelementMode,6,0,1,2) + self.isClone = QtGui.QCheckBox() + layout.addWidget(self.isClone,7,0,1,2) + self.isClone.setChecked(App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleClone", False)) + self.pickrefButton = QtGui.QPushButton() + layout.addWidget(self.pickrefButton,8,0,1,2) + QtCore.QObject.connect(self.xValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) + QtCore.QObject.connect(self.yValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) + QtCore.QObject.connect(self.zValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) + QtCore.QObject.connect(self.pickrefButton,QtCore.SIGNAL("clicked()"),self.pickRef) + QtCore.QObject.connect(self.lock,QtCore.SIGNAL("toggled(bool)"),self.setLock) + QtCore.QObject.connect(self.relative,QtCore.SIGNAL("toggled(bool)"),self.setRelative) + QtCore.QObject.connect(self.isCopy,QtCore.SIGNAL("toggled(bool)"),self.setCopy) + QtCore.QObject.connect(self.isClone,QtCore.SIGNAL("toggled(bool)"),self.setClone) + self.retranslateUi() + + def setLock(self, state): + """Set the uniform scaling.""" + App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleUniform", state) + + def setRelative(self, state): + """Set the relative scaling.""" + App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleRelative", state) + + def setCopy(self, state): + """Set the scale and copy option.""" + App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleCopy", state) + if state and self.isClone.isChecked(): + self.isClone.setChecked(False) + + def setClone(self, state): + """Set the clone and scale option.""" + App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleClone", state) + if state and self.isCopy.isChecked(): + self.isCopy.setChecked(False) + + def setValue(self, val=None): + """Set the value of the points.""" + if self.lock.isChecked(): + self.xValue.setValue(val) + self.yValue.setValue(val) + self.zValue.setValue(val) + if self.sourceCmd: + self.sourceCmd.scaleGhost(self.xValue.value(),self.yValue.value(),self.zValue.value(),self.relative.isChecked()) + + def retranslateUi(self, widget=None): + """Translate the various widgets""" + self.form.setWindowTitle(translate("Draft", "Scale")) + self.xLabel.setText(translate("Draft", "X factor")) + self.yLabel.setText(translate("Draft", "Y factor")) + self.zLabel.setText(translate("Draft", "Z factor")) + self.lock.setText(translate("Draft", "Uniform scaling")) + self.relative.setText(translate("Draft", "Working plane orientation")) + self.isCopy.setText(translate("Draft", "Copy")) + self.isSubelementMode.setText(translate("Draft", "Modify subelements")) + self.pickrefButton.setText(translate("Draft", "Pick from/to points")) + self.isClone.setText(translate("Draft", "Create a clone")) + + def pickRef(self): + """Pick a reference point from the calling class.""" + if self.sourceCmd: + self.sourceCmd.pickRef() + + def accept(self): + """Execute when clicking the OK button.""" + if self.sourceCmd: + self.sourceCmd.scale() + Gui.ActiveDocument.resetEdit() + return True + + def reject(self): + """Execute when clicking the Cancel button.""" + if self.sourceCmd: + self.sourceCmd.finish() + Gui.ActiveDocument.resetEdit() + return True diff --git a/src/Mod/Draft/drafttaskpanels/task_selectplane.py b/src/Mod/Draft/drafttaskpanels/task_selectplane.py new file mode 100644 index 0000000000..8889cd6e85 --- /dev/null +++ b/src/Mod/Draft/drafttaskpanels/task_selectplane.py @@ -0,0 +1,50 @@ +# *************************************************************************** +# * Copyright (c) 2019 Yorik van Havre * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * FreeCAD 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 FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides the task panel for the Draft SelectPlane tool.""" +## @package task_selectplane +# \ingroup DRAFT +# \brief This module provides the task panel code for the SelectPlane tool. + +import FreeCADGui as Gui + +# As it is right now this code only loads the task panel .ui file. +# All logic on how to use the widgets is located in the GuiCommand class +# itself. +# On the other hand, the newer tools introduced in v0.19 like OrthoArray, +# PolarArray, and CircularArray include the logic and manipulation +# of the widgets in this task panel class. +# In addition, the task panel code launches the actual function +# using the delayed mechanism defined by the `todo.ToDo` class. +# Therefore, at some point this class should be refactored +# to be more similar to OrthoArray and the new tools. + + +class SelectPlaneTaskPanel: + """The task panel definition of the Draft_SelectPlane command.""" + + def __init__(self): + self.form = Gui.PySideUic.loadUi(":/ui/TaskSelectPlane.ui") + + def getStandardButtons(self): + """Execute to set the standard buttons.""" + return 2097152 # int(QtGui.QDialogButtonBox.Close) diff --git a/src/Mod/Draft/drafttaskpanels/task_shapestring.py b/src/Mod/Draft/drafttaskpanels/task_shapestring.py new file mode 100644 index 0000000000..03403eac29 --- /dev/null +++ b/src/Mod/Draft/drafttaskpanels/task_shapestring.py @@ -0,0 +1,190 @@ +"""Provide the task panel for the Draft ShapeString tool.""" +## @package task_shapestring +# \ingroup DRAFT +# \brief Provide the task panel for the Draft ShapeString tool. + +# *************************************************************************** +# * (c) 2009 Yorik van Havre * +# * (c) 2020 Eliud Cabrera Castillo * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * FreeCAD 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 FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +import sys +import FreeCAD as App +import FreeCADGui as Gui +import Draft +import Draft_rc +import DraftVecUtils +import DraftTools +import PySide.QtCore as QtCore +import PySide.QtGui as QtGui +from draftutils.translate import translate +from draftutils.messages import _msg, _err + +_Quantity = App.Units.Quantity + + +# So the resource file doesn't trigger errors from code checkers (flake8) +True if Draft_rc.__name__ else False + + +class ShapeStringTaskPanel: + """TaskPanel for Draft_ShapeString.""" + + oldValueBuffer = False + + def __init__(self): + self.form = QtGui.QWidget() + self.form.setObjectName("ShapeStringTaskPanel") + self.form.setWindowTitle(translate("draft", "ShapeString")) + layout = QtGui.QVBoxLayout(self.form) + uiFile = QtCore.QFile(":/ui/TaskShapeString.ui") + loader = Gui.UiLoader() + self.task = loader.load(uiFile) + layout.addWidget(self.task) + + qStart = _Quantity(0.0, App.Units.Length) + self.task.sbX.setProperty('rawValue', qStart.Value) + self.task.sbX.setProperty('unit', qStart.getUserPreferred()[2]) + self.task.sbY.setProperty('rawValue', qStart.Value) + self.task.sbY.setProperty('unit', qStart.getUserPreferred()[2]) + self.task.sbZ.setProperty('rawValue', qStart.Value) + self.task.sbZ.setProperty('unit', qStart.getUserPreferred()[2]) + self.task.sbHeight.setProperty('rawValue', 10.0) + self.task.sbHeight.setProperty('unit', qStart.getUserPreferred()[2]) + + self.stringText = translate("draft", "Default") + self.task.leString.setText(self.stringText) + self.platWinDialog("Overwrite") + self.task.fcFontFile.setFileName(Draft.getParam("FontFile", "")) + self.fileSpec = Draft.getParam("FontFile", "") + self.point = App.Vector(0.0, 0.0, 0.0) + self.pointPicked = False + + QtCore.QObject.connect(self.task.fcFontFile, QtCore.SIGNAL("fileNameSelected(const QString&)"), self.fileSelect) + QtCore.QObject.connect(self.task.pbReset, QtCore.SIGNAL("clicked()"), self.resetPoint) + self.point = None + self.view = Draft.get3DView() + self.call = self.view.addEventCallback("SoEvent", self.action) + _msg(translate("draft", "Pick ShapeString location point:")) + + def fileSelect(self, fn): + """Assign the selected file.""" + self.fileSpec = fn + + def resetPoint(self): + """Reset the selected point.""" + self.pointPicked = False + origin = App.Vector(0.0, 0.0, 0.0) + self.setPoint(origin) + + def action(self, arg): + """scene event handler""" + if arg["Type"] == "SoKeyboardEvent": + if arg["Key"] == "ESCAPE": + self.reject() + elif arg["Type"] == "SoLocation2Event": # mouse movement detection + self.point,ctrlPoint,info = DraftTools.getPoint(self.sourceCmd, arg, noTracker=True) + if not self.pointPicked: + self.setPoint(self.point) + elif arg["Type"] == "SoMouseButtonEvent": + if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): + self.setPoint(self.point) + self.pointPicked = True + + def setPoint(self, point): + """Assign the selected point.""" + self.task.sbX.setProperty('rawValue', point.x) + self.task.sbY.setProperty('rawValue', point.y) + self.task.sbZ.setProperty('rawValue', point.z) + + def createObject(self): + """Create object in the current document.""" + dquote = '"' + if sys.version_info.major < 3: # Python3: no more unicode + String = 'u' + dquote + str(self.task.leString.text().encode('unicode_escape')) + dquote + else: + String = dquote + self.task.leString.text() + dquote + FFile = dquote + str(self.fileSpec) + dquote + + Size = str(_Quantity(self.task.sbHeight.text()).Value) + Tracking = str(0.0) + x = _Quantity(self.task.sbX.text()).Value + y = _Quantity(self.task.sbY.text()).Value + z = _Quantity(self.task.sbZ.text()).Value + ssBase = App.Vector(x, y, z) + # this try block is almost identical to the one in DraftTools + try: + qr, sup, points, fil = self.sourceCmd.getStrings() + Gui.addModule("Draft") + self.sourceCmd.commit(translate("draft", "Create ShapeString"), + ['ss=Draft.makeShapeString(String='+String+',FontFile='+FFile+',Size='+Size+',Tracking='+Tracking+')', + 'plm=FreeCAD.Placement()', + 'plm.Base='+DraftVecUtils.toString(ssBase), + 'plm.Rotation.Q='+qr, + 'ss.Placement=plm', + 'ss.Support='+sup, + 'Draft.autogroup(ss)']) + except Exception: + _err("Draft_ShapeString: error delaying commit\n") + + def platWinDialog(self, flag): + """Handle the type of dialog depending on the platform.""" + ParamGroup = App.ParamGet("User parameter:BaseApp/Preferences/Dialog") + if flag == "Overwrite": + GroupContent = ParamGroup.GetContents() + found = False + if GroupContent: + for ParamSet in GroupContent: + if ParamSet[1] == "DontUseNativeFontDialog": + found = True + break + + if found is False: + # initialize nonexisting one + ParamGroup.SetBool("DontUseNativeFontDialog", True) + + param = ParamGroup.GetBool("DontUseNativeFontDialog") + self.oldValueBuffer = ParamGroup.GetBool("DontUseNativeDialog") + ParamGroup.SetBool("DontUseNativeDialog", param) + + elif flag == "Restore": + ParamGroup.SetBool("DontUseNativeDialog", self.oldValueBuffer) + + def accept(self): + """Execute when clicking the OK button.""" + self.createObject() + if self.call: + self.view.removeEventCallback("SoEvent", self.call) + Gui.ActiveDocument.resetEdit() + Gui.Snapper.off() + self.sourceCmd.creator.finish(self.sourceCmd) + self.platWinDialog("Restore") + return True + + def reject(self): + """Run when clicking the Cancel button.""" + if self.call: + self.view.removeEventCallback("SoEvent", self.call) + Gui.ActiveDocument.resetEdit() + Gui.Snapper.off() + self.sourceCmd.creator.finish(self.sourceCmd) + self.platWinDialog("Restore") + return True diff --git a/src/Mod/Draft/drafttests/__init__.py b/src/Mod/Draft/drafttests/__init__.py index 4287ca8617..058cb96aef 100644 --- a/src/Mod/Draft/drafttests/__init__.py +++ b/src/Mod/Draft/drafttests/__init__.py @@ -1 +1,7 @@ -# \ No newline at end of file +"""Classes and functions used to test the workbench. + +These classes are called by the unit test launcher +that is defined in `Init.py` and `InitGui.py`. + +The unit tests are based on the standard `unittest` module. +""" diff --git a/src/Mod/Draft/drafttests/test_import_gui.py b/src/Mod/Draft/drafttests/test_import_gui.py index decec80ac7..5576f60826 100644 --- a/src/Mod/Draft/drafttests/test_import_gui.py +++ b/src/Mod/Draft/drafttests/test_import_gui.py @@ -24,7 +24,6 @@ """Unit test for the Draft Workbench, GUI import tests.""" import unittest -import FreeCAD as App import drafttests.auxiliary as aux @@ -39,39 +38,23 @@ class DraftGuiImport(unittest.TestCase): def test_import_gui_draftgui(self): """Import Draft TaskView GUI tools.""" module = "DraftGui" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_draft_snap(self): """Import Draft snapping.""" module = "draftguitools.gui_snapper" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_draft_tools(self): """Import Draft graphical commands.""" module = "DraftTools" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_draft_trackers(self): """Import Draft tracker utilities.""" module = "draftguitools.gui_trackers" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) diff --git a/src/Mod/Draft/drafttests/test_import_tools.py b/src/Mod/Draft/drafttests/test_import_tools.py index ada1eeabea..5827f7a75b 100644 --- a/src/Mod/Draft/drafttests/test_import_tools.py +++ b/src/Mod/Draft/drafttests/test_import_tools.py @@ -24,7 +24,6 @@ """Unit test for the Draft Workbench, tools import tests.""" import unittest -import FreeCAD as App import drafttests.auxiliary as aux @@ -38,50 +37,30 @@ class DraftImportTools(unittest.TestCase): def test_import_gui_draftedit(self): """Import Draft Edit.""" - module = "DraftEdit" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return + module = "draftguitools.gui_edit" imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_draftfillet(self): """Import Draft Fillet.""" module = "DraftFillet" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_draftlayer(self): """Import Draft Layer.""" module = "DraftLayer" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_draftplane(self): """Import Draft SelectPlane.""" - module = "DraftSelectPlane" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return + module = "draftguitools.gui_selectplane" imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_workingplane(self): """Import Draft WorkingPlane.""" module = "WorkingPlane" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) diff --git a/src/Mod/Draft/drafttests/test_modification.py b/src/Mod/Draft/drafttests/test_modification.py index 3de9fbbb56..d9537883ea 100644 --- a/src/Mod/Draft/drafttests/test_modification.py +++ b/src/Mod/Draft/drafttests/test_modification.py @@ -433,9 +433,9 @@ class DraftModification(unittest.TestCase): _msg(" Array") _msg(" radial_distance={0}, " "tangential_distance={1}".format(rad_distance, tan_distance)) + _msg(" number={0}, symmetry={1}".format(number, symmetry)) _msg(" axis={}".format(axis)) _msg(" center={}".format(center)) - _msg(" number={0}, symmetry={1}".format(number, symmetry)) obj = Draft.makeArray(rect, rad_distance, tan_distance, axis, center, diff --git a/src/Mod/Draft/drafttests/test_pivy.py b/src/Mod/Draft/drafttests/test_pivy.py index 4a9e0fb1ed..c099b91d9a 100644 --- a/src/Mod/Draft/drafttests/test_pivy.py +++ b/src/Mod/Draft/drafttests/test_pivy.py @@ -58,14 +58,8 @@ class DraftPivy(unittest.TestCase): def test_pivy_draw(self): """Use Coin (pivy.coin) to draw a cube on the active view.""" - module = "pivy.coin" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return - - import pivy.coin - cube = pivy.coin.SoCube() + import pivy.coin as coin + cube = coin.SoCube() _msg(" Draw cube") Gui.ActiveDocument.ActiveView.getSceneGraph().addChild(cube) _msg(" Adding cube to the active view scene") diff --git a/src/Mod/Draft/draftutils/__init__.py b/src/Mod/Draft/draftutils/__init__.py index e69de29bb2..b086fa4c12 100644 --- a/src/Mod/Draft/draftutils/__init__.py +++ b/src/Mod/Draft/draftutils/__init__.py @@ -0,0 +1,6 @@ +"""Utility functions that do not require the graphical user interface. + +These functions are used throughout the Draft Workbench. +They can be called from any module, whether it uses the graphical +user interface or not. +""" diff --git a/src/Mod/Draft/draftutils/gui_utils.py b/src/Mod/Draft/draftutils/gui_utils.py index d9056b8480..37b4c899d9 100644 --- a/src/Mod/Draft/draftutils/gui_utils.py +++ b/src/Mod/Draft/draftutils/gui_utils.py @@ -1,16 +1,8 @@ -"""This module provides GUI utility functions for the Draft Workbench. - -This module should contain auxiliary functions which require -the graphical user interface (GUI). -""" -## @package gui_utils -# \ingroup DRAFT -# \brief This module provides utility functions for the Draft Workbench - # *************************************************************************** # * (c) 2009, 2010 * # * Yorik van Havre , Ken Cline * # * (c) 2019 Eliud Cabrera Castillo * +# * (c) 2020 Carlo Pavan * # * * # * This file is part of the FreeCAD CAx development system. * # * * @@ -31,24 +23,32 @@ the graphical user interface (GUI). # * USA * # * * # *************************************************************************** +"""Provides GUI utility functions for the Draft Workbench. +This module contains auxiliary functions which can be used +in other modules of the workbench, and which require +the graphical user interface (GUI), as they access the view providers +of the objects or the 3D view. +""" +## @package gui_utils +# \ingroup DRAFT +# \brief This module provides GUI utility functions for the Draft Workbench -import FreeCAD -from .utils import _msg -from .utils import _wrn -# from .utils import _log -from .utils import _tr -from .utils import getParam -from .utils import get_type -import os import math +import os import six -if FreeCAD.GuiUp: - import FreeCADGui +import FreeCAD as App +from draftutils.messages import _msg, _wrn +from draftutils.utils import getParam +from draftutils.utils import get_type +from draftutils.translate import _tr, translate + +if App.GuiUp: + import FreeCADGui as Gui from pivy import coin from PySide import QtGui -# from PySide import QtSvg # for load_texture + # from PySide import QtSvg # for load_texture def get_3d_view(): @@ -62,13 +62,13 @@ def get_3d_view(): Return `None` if the graphical interface is not available. """ - if FreeCAD.GuiUp: - v = FreeCADGui.ActiveDocument.ActiveView + if App.GuiUp: + v = Gui.ActiveDocument.ActiveView if "View3DInventor" in str(type(v)): return v # print("Debug: Draft: Warning, not working in active view") - v = FreeCADGui.ActiveDocument.mdiViewsOfType("Gui::View3DInventor") + v = Gui.ActiveDocument.mdiViewsOfType("Gui::View3DInventor") if v: return v[0] @@ -80,10 +80,10 @@ get3DView = get_3d_view def autogroup(obj): - """Adds a given object to the defined Draft autogroup, if applicable. + """Add a given object to the defined Draft autogroup, if applicable. This function only works if the graphical interface is available. - It checks that the `FreeCAD.draftToolBar` class is available, + It checks that the `App.draftToolBar` class is available, which contains the group to use to automatically store new created objects. @@ -95,53 +95,58 @@ def autogroup(obj): Parameters ---------- - obj : App::DocumentObject + obj: App::DocumentObject Any type of object that will be stored in the group. """ - if FreeCAD.GuiUp: - # look for active Arch container - active_arch_obj = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch") - if hasattr(FreeCADGui,"draftToolBar"): - if (hasattr(FreeCADGui.draftToolBar,"autogroup") - and not FreeCADGui.draftToolBar.isConstructionMode() - ): - if FreeCADGui.draftToolBar.autogroup is not None: - active_group = FreeCAD.ActiveDocument.getObject(FreeCADGui.draftToolBar.autogroup) - if active_group: - found = False - for o in active_group.Group: - if o.Name == obj.Name: - found = True - if not found: - gr = active_group.Group - gr.append(obj) - active_group.Group = gr - elif active_arch_obj: - active_arch_obj.addObject(obj) - elif FreeCADGui.ActiveDocument.ActiveView.getActiveObject("part", False) is not None: - # add object to active part and change it's placement accordingly - # so object does not jump to different position, works with App::Link - # if not scaled. Modified accordingly to realthunder suggestions - p, parent, sub = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("part", False) - matrix = parent.getSubObject(sub, retType=4) - if matrix.hasScale() == 1: - FreeCAD.Console.PrintMessage(translate("Draft", - "Unable to insert new object into " - "a scaled part") - ) - return - inverse_placement = FreeCAD.Placement(matrix.inverse()) - if get_type(obj) == 'Point': - # point vector have a kind of placement, so should be - # processed before generic object with placement - point_vector = FreeCAD.Vector(obj.X, obj.Y, obj.Z) - real_point = inverse_placement.multVec(point_vector) - obj.X = real_point.x - obj.Y = real_point.y - obj.Z = real_point.z - elif hasattr(obj,"Placement"): - obj.Placement = FreeCAD.Placement(inverse_placement.multiply(obj.Placement)) - p.addObject(obj) + if not App.GuiUp: + return + + doc = App.ActiveDocument + view = Gui.ActiveDocument.ActiveView + + # Look for active Arch container + active_arch_obj = Gui.ActiveDocument.ActiveView.getActiveObject("Arch") + if hasattr(Gui, "draftToolBar"): + if (hasattr(Gui.draftToolBar, "autogroup") + and not Gui.draftToolBar.isConstructionMode()): + if Gui.draftToolBar.autogroup is not None: + active_group = doc.getObject(Gui.draftToolBar.autogroup) + if active_group: + found = False + for o in active_group.Group: + if o.Name == obj.Name: + found = True + if not found: + gr = active_group.Group + gr.append(obj) + active_group.Group = gr + elif active_arch_obj: + active_arch_obj.addObject(obj) + elif view.getActiveObject("part", False) is not None: + # Add object to active part and change its placement + # accordingly so the object does not jump + # to a different position, works with App::Link if not scaled. + # Modified accordingly to realthunder suggestions + p, parent, sub = view.getActiveObject("part", False) + matrix = parent.getSubObject(sub, retType=4) + if matrix.hasScale() == 1: + _msg(translate("Draft", + "Unable to insert new object into " + "a scaled part")) + return + inverse_placement = App.Placement(matrix.inverse()) + if get_type(obj) == 'Point': + # point vector have a kind of placement, so should be + # processed before generic object with placement + point_vector = App.Vector(obj.X, obj.Y, obj.Z) + real_point = inverse_placement.multVec(point_vector) + obj.X = real_point.x + obj.Y = real_point.y + obj.Z = real_point.z + elif hasattr(obj, "Placement"): + place = inverse_placement.multiply(obj.Placement) + obj.Placement = App.Placement(place) + p.addObject(obj) def dim_symbol(symbol=None, invert=False): @@ -149,7 +154,7 @@ def dim_symbol(symbol=None, invert=False): Parameters ---------- - symbol : int, optional + symbol: int, optional It defaults to `None`, in which it gets the value from the parameter database, `get_param("dimsymbol", 0)`. @@ -161,7 +166,7 @@ def dim_symbol(symbol=None, invert=False): * 4, `SoSeparator` with a `SoLineSet`, calling `dim_dash` * Otherwise, `SoSphere` - invert : bool, optional + invert: bool, optional It defaults to `False`. If it is `True` and `symbol=2`, the cone will be rotated -90 degrees around the Z axis, otherwise the rotation is positive, @@ -181,7 +186,7 @@ def dim_symbol(symbol=None, invert=False): return coin.SoSphere() elif symbol == 1: marker = coin.SoMarkerSet() - marker.markerIndex = FreeCADGui.getMarkerIndex("circle", 9) + marker.markerIndex = Gui.getMarkerIndex("circle", 9) return marker elif symbol == 2: marker = coin.SoSeparator() @@ -224,10 +229,10 @@ def dim_dash(p1, p2): Parameters ---------- - p1 : tuple of three floats or Base::Vector3 + p1: tuple of three floats or Base::Vector3 A point to define a line vertex. - p2 : tuple of three floats or Base::Vector3 + p2: tuple of three floats or Base::Vector3 A point to define a line vertex. Returns @@ -258,7 +263,7 @@ def remove_hidden(objectslist): Parameters ---------- - objectslist : list of App::DocumentObject + objectslist: list of App::DocumentObject List of any type of object. Returns @@ -291,19 +296,19 @@ def format_object(target, origin=None): Parameters ---------- - target : App::DocumentObject + target: App::DocumentObject Any type of scripted object. This object will adopt the applicable visual properties, `FontSize`, `TextColor`, `LineWidth`, `PointColor`, `LineColor`, and `ShapeColor`, defined in the Draft toolbar - (`FreeCADGui.draftToolBar`) or will adopt + (`Gui.draftToolBar`) or will adopt the properties from the `origin` object. The `target` is also placed in the construction group if the construction mode in the Draft toolbar is active. - origin : App::DocumentObject, optional + origin: App::DocumentObject, optional It defaults to `None`. If it exists, it will provide the visual properties to assign to `target`, with the exception of `BoundingBox`, `Proxy`, @@ -315,11 +320,11 @@ def format_object(target, origin=None): if not obrep: return ui = None - if FreeCAD.GuiUp: - if hasattr(FreeCADGui, "draftToolBar"): - ui = FreeCADGui.draftToolBar + if App.GuiUp: + if hasattr(Gui, "draftToolBar"): + ui = Gui.draftToolBar if ui: - doc = FreeCAD.ActiveDocument + doc = App.ActiveDocument if ui.isConstructionMode(): col = fcol = ui.getDefaultColor("constr") gname = getParam("constructiongroupname", "Construction") @@ -374,18 +379,18 @@ def format_object(target, origin=None): formatObject = format_object -def get_selection(gui=FreeCAD.GuiUp): +def get_selection(gui=App.GuiUp): """Return the current selected objects. This function only works if the graphical interface is available as the selection module only works on the 3D view. - It wraps around `FreeCADGui.Selection.getSelection` + It wraps around `Gui.Selection.getSelection` Parameters ---------- - gui : bool, optional - It defaults to the value of `FreeCAD.GuiUp`, which is `True` + gui: bool, optional + It defaults to the value of `App.GuiUp`, which is `True` when the interface exists, and `False` otherwise. This value can be set to `False` to simulate @@ -400,25 +405,25 @@ def get_selection(gui=FreeCAD.GuiUp): If the interface is not available, it returns `None`. """ if gui: - return FreeCADGui.Selection.getSelection() + return Gui.Selection.getSelection() return None getSelection = get_selection -def get_selection_ex(gui=FreeCAD.GuiUp): +def get_selection_ex(gui=App.GuiUp): """Return the current selected objects together with their subelements. This function only works if the graphical interface is available as the selection module only works on the 3D view. - It wraps around `FreeCADGui.Selection.getSelectionEx` + It wraps around `Gui.Selection.getSelectionEx` Parameters ---------- - gui : bool, optional - It defaults to the value of `FreeCAD.GuiUp`, which is `True` + gui: bool, optional + It defaults to the value of `App.GuiUp`, which is `True` when the interface exists, and `False` otherwise. This value can be set to `False` to simulate @@ -448,14 +453,14 @@ def get_selection_ex(gui=FreeCAD.GuiUp): if `HasSubObjects` is `False`. """ if gui: - return FreeCADGui.Selection.getSelectionEx() + return Gui.Selection.getSelectionEx() return None getSelectionEx = get_selection_ex -def select(objs=None, gui=FreeCAD.GuiUp): +def select(objs=None, gui=App.GuiUp): """Unselects everything and selects only the given list of objects. This function only works if the graphical interface is available @@ -463,29 +468,29 @@ def select(objs=None, gui=FreeCAD.GuiUp): Parameters ---------- - objs : list of App::DocumentObject, optional + objs: list of App::DocumentObject, optional It defaults to `None`. Any type of scripted object. It may be a list of objects or a single object. - gui : bool, optional - It defaults to the value of `FreeCAD.GuiUp`, which is `True` + gui: bool, optional + It defaults to the value of `App.GuiUp`, which is `True` when the interface exists, and `False` otherwise. This value can be set to `False` to simulate when the interface is not available. """ if gui: - FreeCADGui.Selection.clearSelection() + Gui.Selection.clearSelection() if objs: if not isinstance(objs, list): objs = [objs] for obj in objs: if obj: - FreeCADGui.Selection.addSelection(obj) + Gui.Selection.addSelection(obj) -def load_texture(filename, size=None, gui=FreeCAD.GuiUp): +def load_texture(filename, size=None, gui=App.GuiUp): """Return a Coin.SoSFImage to use as a texture for a 2D plane. This function only works if the graphical interface is available @@ -494,11 +499,11 @@ def load_texture(filename, size=None, gui=FreeCAD.GuiUp): Parameters ---------- - filename : str + filename: str A path to a pixel image file (PNG) that can be used as a texture on the face of an object. - size : tuple of two int, or a single int, optional + size: tuple of two int, or a single int, optional It defaults to `None`. If a tuple is given, the two values define the width and height in pixels to which the loaded image will be scaled. @@ -510,8 +515,8 @@ def load_texture(filename, size=None, gui=FreeCAD.GuiUp): CURRENTLY the input `size` parameter IS NOT USED. It always uses the `QImage` to determine this information. - gui : bool, optional - It defaults to the value of `FreeCAD.GuiUp`, which is `True` + gui: bool, optional + It defaults to the value of `App.GuiUp`, which is `True` when the interface exists, and `False` otherwise. This value can be set to `False` to simulate diff --git a/src/Mod/Draft/draftutils/init_tools.py b/src/Mod/Draft/draftutils/init_tools.py index 327756dba5..d9e71c7578 100644 --- a/src/Mod/Draft/draftutils/init_tools.py +++ b/src/Mod/Draft/draftutils/init_tools.py @@ -1,14 +1,3 @@ -"""Provides lists of commands for the Draft Workbench. - -This module returns lists of commands, so that the toolbars -can be initialized by Draft, and by other workbenches. -These commands should be defined in `DraftTools`, and in the individual -modules in `draftguitools`. -""" -## @package init_tools -# \ingroup DRAFT -# \brief This module provides lists of commands for the Draft Workbench. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -31,6 +20,16 @@ modules in `draftguitools`. # * USA * # * * # *************************************************************************** +"""Provides lists of commands for the Draft Workbench. + +This module returns lists of commands, so that the toolbars +can be initialized by Draft, and by other workbenches. +These commands should be defined in `DraftTools`, and in the individual +modules in `draftguitools`. +""" +## @package init_tools +# \ingroup DRAFT +# \brief This module provides lists of commands for the Draft Workbench. from PySide.QtCore import QT_TRANSLATE_NOOP @@ -43,12 +42,13 @@ def get_draft_drawing_commands(): "Draft_ArcTools", "Draft_Circle", "Draft_Ellipse", "Draft_Rectangle", "Draft_Polygon", "Draft_BSpline", "Draft_BezierTools", - "Draft_Point", "Draft_Facebinder"] + "Draft_Point", "Draft_Facebinder", + "Draft_ShapeString"] def get_draft_annotation_commands(): """Return the annotation commands list.""" - return ["Draft_Text", "Draft_ShapeString", "Draft_Dimension", + return ["Draft_Text", "Draft_Dimension", "Draft_Label"] @@ -61,7 +61,7 @@ def get_draft_modification_commands(): """Return the modification commands list.""" lst = ["Draft_Move", "Draft_Rotate", "Draft_Scale", "Draft_Mirror", - "Draft_Offset", "Draft_Trimex", + "Draft_Offset", "Draft_Trimex", "Draft_Stretch", "Separator", "Draft_Clone"] @@ -74,7 +74,8 @@ def get_draft_modification_commands(): "Separator", "Draft_WireToBSpline", "Draft_Draft2Sketch", "Separator", - "Draft_Shape2DView", "Draft_Drawing"] + "Draft_Shape2DView", "Draft_Drawing", + "Draft_WorkingPlaneProxy"] return lst @@ -97,7 +98,7 @@ def get_draft_utility_commands(): return ["Draft_Layer", "Draft_Heal", "Draft_FlipDimension", "Draft_ToggleConstructionMode", "Draft_ToggleContinueMode", "Draft_Edit", - "Draft_Slope", "Draft_SetWorkingPlaneProxy", + "Draft_Slope", "Draft_WorkingPlaneProxy", "Draft_AddConstruction"] diff --git a/src/Mod/Draft/draftutils/messages.py b/src/Mod/Draft/draftutils/messages.py index 7686fa5039..e1f8c99cdc 100644 --- a/src/Mod/Draft/draftutils/messages.py +++ b/src/Mod/Draft/draftutils/messages.py @@ -1,8 +1,3 @@ -"""Provide message utility functions for the Draft Workbench.""" -## @package messages -# \ingroup DRAFT -# \brief Provide message utility functions for the Draft Workbench. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -25,6 +20,16 @@ # * USA * # * * # *************************************************************************** +"""Provide message utility functions for the Draft Workbench. + +The Console module has long function names, so we define some shorthands +that are suitable for use in every workbench. These shorthands also include +a newline character at the end of the string, so it doesn't have to be +added manually. +""" +## @package messages +# \ingroup DRAFT +# \brief Provide message utility functions for the Draft Workbench. import FreeCAD as App diff --git a/src/Mod/Draft/draftutils/todo.py b/src/Mod/Draft/draftutils/todo.py index b5afd97369..d2da1bf4dc 100644 --- a/src/Mod/Draft/draftutils/todo.py +++ b/src/Mod/Draft/draftutils/todo.py @@ -1,12 +1,3 @@ -"""This module provides the ToDo class for the Draft Workbench. - -This module provides the ToDo class to delay the commit of commands, -which depends on QtCore.QTimer. -""" -## @package todo -# \ingroup DRAFT -# \brief This module provides the ToDo class for the Draft Workbench. - # *************************************************************************** # * (c) 2009, Yorik van Havre * # * (c) 2019 Eliud Cabrera Castillo * @@ -30,20 +21,33 @@ which depends on QtCore.QTimer. # * USA * # * * # *************************************************************************** +"""Provides the ToDo class for the Draft Workbench. + +The ToDo class is used to delay the commit of commands for later execution. +This is necessary when a GUI command needs to manipulate the 3D view +in such a way that a callback would crash Coin. +The ToDo class essentially calls `QtCore.QTimer.singleShot` +to execute the instructions stored in internal lists. +""" +## @package todo +# \ingroup DRAFT +# \brief This module provides the ToDo class for the Draft Workbench. -import sys import six +import sys import traceback -import FreeCAD -import FreeCADGui from PySide import QtCore +import FreeCAD +import FreeCADGui +from draftutils.messages import _msg, _wrn, _log __title__ = "FreeCAD Draft Workbench, Todo class" __author__ = "Yorik van Havre " __url__ = ["http://www.freecadweb.org"] _DEBUG = 0 +_DEBUG_inner = 0 class ToDo: @@ -114,29 +118,31 @@ class ToDo: The lists are `itinerary`, `commitlist` and `afteritinerary`. """ if _DEBUG: - print("Debug: doing delayed tasks.\n" - "itinerary: {0}\n" - "commitlist: {1}\n" - "afteritinerary: {2}\n".format(todo.itinerary, - todo.commitlist, - todo.afteritinerary)) + _msg("Debug: doing delayed tasks.\n" + "itinerary: {0}\n" + "commitlist: {1}\n" + "afteritinerary: {2}\n".format(todo.itinerary, + todo.commitlist, + todo.afteritinerary)) try: for f, arg in todo.itinerary: try: - # print("debug: executing", f) + if _DEBUG_inner: + _msg("Debug: executing.\n" + "function: {}\n".format(f)) if arg or (arg is False): f(arg) else: f() except Exception: - FreeCAD.Console.PrintLog(traceback.format_exc()) + _log(traceback.format_exc()) wrn = ("ToDo.doTasks, Unexpected error:\n" "{0}\n" "in {1}({2})".format(sys.exc_info()[0], f, arg)) - FreeCAD.Console.PrintWarning(wrn) + _wrn(wrn) except ReferenceError: - print("Debug: ToDo.doTasks: " - "queue contains a deleted object, skipping") + _wrn("Debug: ToDo.doTasks: " + "queue contains a deleted object, skipping") todo.itinerary = [] if todo.commitlist: @@ -144,7 +150,9 @@ class ToDo: if six.PY2: if isinstance(name, six.text_type): name = name.encode("utf8") - # print("debug: committing " + str(name)) + if _DEBUG_inner: + _msg("Debug: committing.\n" + "name: {}\n".format(name)) try: name = str(name) FreeCAD.ActiveDocument.openTransaction(name) @@ -155,11 +163,11 @@ class ToDo: func() FreeCAD.ActiveDocument.commitTransaction() except Exception: - FreeCAD.Console.PrintLog(traceback.format_exc()) + _log(traceback.format_exc()) wrn = ("ToDo.doTasks, Unexpected error:\n" "{0}\n" - "in {1}".format(sys.exec_info()[0], func)) - FreeCAD.Console.PrintWarning(wrn) + "in {1}".format(sys.exc_info()[0], func)) + _wrn(wrn) # Restack Draft screen widgets after creation if hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper.restack() @@ -167,17 +175,19 @@ class ToDo: for f, arg in todo.afteritinerary: try: - # print("debug: executing", f) + if _DEBUG_inner: + _msg("Debug: executing after.\n" + "function: {}\n".format(f)) if arg: f(arg) else: f() except Exception: - FreeCAD.Console.PrintLog(traceback.format_exc()) + _log(traceback.format_exc()) wrn = ("ToDo.doTasks, Unexpected error:\n" "{0}\n" "in {1}({2})".format(sys.exc_info()[0], f, arg)) - FreeCAD.Console.PrintWarning(wrn) + _wrn(wrn) todo.afteritinerary = [] @staticmethod @@ -207,7 +217,9 @@ class ToDo: :: f(arg) """ - # print("debug: delaying", f) + if _DEBUG: + _msg("Debug: delaying.\n" + "function: {}\n".format(f)) if todo.itinerary == []: QtCore.QTimer.singleShot(0, todo.doTasks) todo.itinerary.append((f, arg)) @@ -235,7 +247,9 @@ class ToDo: See the attributes of the `ToDo` class for more information. """ - # print("debug: delaying commit", cl) + if _DEBUG: + _msg("Debug: delaying commit.\n" + "commitlist: {}\n".format(cl)) QtCore.QTimer.singleShot(0, todo.doTasks) todo.commitlist = cl @@ -255,7 +269,9 @@ class ToDo: Finally, it will build the tuple `(f, arg)` and append it to the `afteritinerary` list. """ - # print("debug: delaying", f) + if _DEBUG: + _msg("Debug: delaying after.\n" + "function: {}\n".format(f)) if todo.afteritinerary == []: QtCore.QTimer.singleShot(0, todo.doTasks) todo.afteritinerary.append((f, arg)) diff --git a/src/Mod/Draft/draftutils/utils.py b/src/Mod/Draft/draftutils/utils.py index 8b75b4f6c7..ee93388f47 100644 --- a/src/Mod/Draft/draftutils/utils.py +++ b/src/Mod/Draft/draftutils/utils.py @@ -1,13 +1,4 @@ # -*- coding: utf-8 -*- -"""This module provides utility functions for the Draft Workbench. - -This module should contain auxiliary functions which don't require -the graphical user interface (GUI). -""" -## @package utils -# \ingroup DRAFT -# \brief This module provides utility functions for the Draft Workbench - # *************************************************************************** # * (c) 2009, 2010 * # * Yorik van Havre , Ken Cline * @@ -32,52 +23,35 @@ the graphical user interface (GUI). # * USA * # * * # *************************************************************************** +"""Provides utility functions for the Draft Workbench. + +This module contains auxiliary functions which can be used +in other modules of the workbench, and which don't require +the graphical user interface (GUI). +""" +## @package utils +# \ingroup DRAFT +# \brief This module provides utility functions for the Draft Workbench import os -import FreeCAD from PySide import QtCore + +import FreeCAD import Draft_rc +from draftutils.messages import _msg, _log +from draftutils.translate import _tr + App = FreeCAD # The module is used to prevent complaints from code checkers (flake8) True if Draft_rc else False - -if App.GuiUp: - # The right translate function needs to be imported here - # from DraftGui import translate - - # At the moment it is the same function as without GUI - def translate(context, text): - return text -else: - def translate(context, text): - return text - - -def _tr(text): - """Function to translate with the context set.""" - return translate("Draft", text) - - -def _msg(text, end="\n"): - App.Console.PrintMessage(text + end) - - -def _wrn(text, end="\n"): - App.Console.PrintWarning(text + end) - - -def _log(text, end="\n"): - App.Console.PrintLog(text + end) - - ARROW_TYPES = ["Dot", "Circle", "Arrow", "Tick", "Tick-2"] arrowtypes = ARROW_TYPES def string_encode_coin(ustr): - """Encode a unicode object to be used as a string in coin + """Encode a unicode object to be used as a string in coin. Parameters ---------- @@ -132,7 +106,7 @@ def type_check(args_and_types, name="?"): Defaults to `'?'`. The name of the check. Raises - ------- + ------ TypeError If the first element in the tuple is not an instance of the second element, it raises `Draft.name`. @@ -183,7 +157,8 @@ def get_param_type(param): "SvgLinesBlack", "dxfStdSize", "showSnapBar", "hideSnapBar", "alwaysShowGrid", "renderPolylineWidth", "showPlaneTracker", "UsePartPrimitives", - "DiscretizeEllipses", "showUnit"): + "DiscretizeEllipses", "showUnit", + "Draft_array_fuse", "Draft_array_Link"): return "bool" elif param in ("color", "constructioncolor", "snapcolor", "gridColor"): @@ -265,7 +240,7 @@ getParam = get_param def set_param(param, value): - """Set a Draft parameter with the given value + """Set a Draft parameter with the given value. The parameter database is located in the tree :: @@ -981,7 +956,7 @@ getMovableChildren = get_movable_children def utf8_decode(text): - """Decode the input string and return a unicode string. + r"""Decode the input string and return a unicode string. Python 2: :: @@ -1017,14 +992,14 @@ def utf8_decode(text): >>> "Aá".decode("utf-8") >>> b"Aá".decode("utf-8") - u'A\\xe1' + u'A\xe1' In Python 2 the unicode string is prefixed with `u`, and unicode characters are replaced by their two-digit hexadecimal representation, or four digit unicode escape. >>> "AáBẃCñ".decode("utf-8") - u'A\\xe1B\\u1e83C\\xf1' + u'A\xe1B\u1e83C\xf1' In Python 2 it will always return a `unicode` object. @@ -1045,3 +1020,29 @@ def utf8_decode(text): return text.decode("utf-8") except AttributeError: return text + + +def print_header(name, description, debug=True): + """Print a line to the console when something is called, and log it. + + Parameters + ---------- + name: str + The name of the function or class that is being called. + This `name` will be logged in the log file, so if there are problems + the log file can be investigated for clues. + + description: str + Arbitrary text that will be printed to the console + when the function or class is called. + + debug: bool, optional + It defaults to `True`. + If it is `False` the `description` will not be printed + to the console. + On the other hand the `name` will always be logged. + """ + _log(name) + if debug: + _msg(16 * "-") + _msg(description) diff --git a/src/Mod/Draft/draftviewproviders/__init__.py b/src/Mod/Draft/draftviewproviders/__init__.py index e69de29bb2..f634964578 100644 --- a/src/Mod/Draft/draftviewproviders/__init__.py +++ b/src/Mod/Draft/draftviewproviders/__init__.py @@ -0,0 +1,7 @@ +"""Classes that define the viewproviders of custom scripted objects. + +These classes define viewproviders for the custom objects +defined in `draftobjects`. +The viewproviders can be used only when the graphical interface +is available; in console mode the viewproviders are not available. +""" diff --git a/src/Mod/Draft/importSVG.py b/src/Mod/Draft/importSVG.py index 6faf63e823..d760552062 100644 --- a/src/Mod/Draft/importSVG.py +++ b/src/Mod/Draft/importSVG.py @@ -1,23 +1,3 @@ -## @package importSVG -# \ingroup DRAFT -# \brief SVG file importer & exporter -'''@package importSVG -\ingroup DRAFT -\brief SVG file importer & exporter - -This module provides support for importing and exporting SVG files. It -enables importing/exporting objects directly to/from the 3D document, but -doesn't handle the SVG output from the Drawing and TechDraw modules. - -Currently it only reads the following entities: -* paths, lines, circular arcs, rects, circles, ellipses, polygons, polylines. - -Currently unsupported: -* use, image. -''' -# Check code with -# flake8 --ignore=E226,E266,E401,W503 - # *************************************************************************** # * Copyright (c) 2009 Yorik van Havre * # * * @@ -38,12 +18,29 @@ Currently unsupported: # * USA * # * * # *************************************************************************** +"""Provides support for importing and exporting SVG files. + +It enables importing/exporting objects directly to/from the 3D document +but doesn't handle the SVG output from the Drawing and TechDraw modules. + +Currently it only reads the following entities: +* paths, lines, circular arcs, rects, circles, ellipses, polygons, polylines. + +Currently unsupported: +* use, image. +""" +## @package importSVG +# \ingroup DRAFT +# \brief SVG file importer and exporter + +# Check code with +# flake8 --ignore=E226,E266,E401,W503 __title__ = "FreeCAD Draft Workbench - SVG importer/exporter" __author__ = "Yorik van Havre, Sebastian Hoogen" __url__ = "https://www.freecadweb.org" -# ToDo: +# TODO: # ignoring CDATA # handle image element (external references and inline base64) # debug Problem with 'Sans' font from Inkscape @@ -51,27 +48,28 @@ __url__ = "https://www.freecadweb.org" # implement inheriting fill style from group # handle relative units -import xml.sax, FreeCAD, os, math, re, Draft, DraftVecUtils +import math +import os +import re +import xml.sax + +import FreeCAD +import Draft +import DraftVecUtils from FreeCAD import Vector from FreeCAD import Console as FCC +from draftutils.translate import translate if FreeCAD.GuiUp: - from DraftTools import translate from PySide import QtGui -else: - def translate(context, txt): - return txt - -try: import FreeCADGui -except ImportError: - gui = False -else: gui = True - -try: - draftui = FreeCADGui.draftToolBar -except AttributeError: + try: + draftui = FreeCADGui.draftToolBar + except AttributeError: + draftui = None +else: + gui = False draftui = None # Save the native open function to avoid collisions diff --git a/src/Mod/Mesh/Gui/DlgSettingsImportExport.ui b/src/Mod/Mesh/Gui/DlgSettingsImportExport.ui index 9a0b08c7d0..018fa469f1 100644 --- a/src/Mod/Mesh/Gui/DlgSettingsImportExport.ui +++ b/src/Mod/Mesh/Gui/DlgSettingsImportExport.ui @@ -31,7 +31,7 @@ mm - 0.000000000000000 + 0.000010000000000 100000000.000000000000000 diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp index 748735779c..da506351e5 100644 --- a/src/Mod/Part/App/AppPartPy.cpp +++ b/src/Mod/Part/App/AppPartPy.cpp @@ -290,6 +290,9 @@ public: add_varargs_method("show",&Module::show, "show(shape,[string]) -- Add the shape to the active document or create one if no document exists." ); + add_varargs_method("getFacets",&Module::getFacets, + "getFacets(shape): simplified mesh generation" + ); add_varargs_method("makeCompound",&Module::makeCompound, "makeCompound(list) -- Create a compound out of a list of shapes." ); @@ -720,6 +723,51 @@ private: return Py::None(); } + Py::Object getFacets(const Py::Tuple& args) + { + PyObject *shape; + PyObject *list = PyList_New(0); + if (!PyArg_ParseTuple(args.ptr(), "O", &shape)) + throw Py::Exception(); + auto theShape = static_cast(shape)->getTopoShapePtr()->getShape(); + for(TopExp_Explorer ex(theShape, TopAbs_FACE); ex.More(); ex.Next()) + { + TopoDS_Face currentFace = TopoDS::Face(ex.Current()); + TopLoc_Location loc; + Handle(Poly_Triangulation) facets = BRep_Tool::Triangulation(currentFace, loc); + const TopAbs_Orientation anOrientation = currentFace.Orientation(); + bool flip = (anOrientation == TopAbs_REVERSED); + if(!facets.IsNull()){ + auto nodes = facets->Nodes(); + auto triangles = facets->Triangles(); + for(int i = 1; i <= triangles.Length(); i++){ + Standard_Integer n1,n2,n3; + triangles(i).Get(n1, n2, n3); + gp_Pnt p1 = nodes(n1); + gp_Pnt p2 = nodes(n2); + gp_Pnt p3 = nodes(n3); + p1.Transform(loc.Transformation()); + p2.Transform(loc.Transformation()); + p3.Transform(loc.Transformation()); + // TODO: verify if tolerence should be hard coded + if (!p1.IsEqual(p2, 0.01) && !p2.IsEqual(p3, 0.01) && !p3.IsEqual(p1, 0.01)) { + PyObject *t1 = PyTuple_Pack(3, PyFloat_FromDouble(p1.X()), PyFloat_FromDouble(p1.Y()), PyFloat_FromDouble(p1.Z())); + PyObject *t2 = PyTuple_Pack(3, PyFloat_FromDouble(p2.X()), PyFloat_FromDouble(p2.Y()), PyFloat_FromDouble(p2.Z())); + PyObject *t3 = PyTuple_Pack(3, PyFloat_FromDouble(p3.X()), PyFloat_FromDouble(p3.Y()), PyFloat_FromDouble(p3.Z())); + PyObject *points; + if(flip) + { + points = PyTuple_Pack(3, t2, t1, t3); + } else { + points = PyTuple_Pack(3, t1, t2, t3); + } + PyList_Append(list, points); + } + } + } + } + return Py::asObject(list); + } Py::Object makeCompound(const Py::Tuple& args) { PyObject *pcObj; diff --git a/src/Mod/Part/Gui/DlgSettingsObjectColor.ui b/src/Mod/Part/Gui/DlgSettingsObjectColor.ui index 97da9983d8..9de8a266bd 100644 --- a/src/Mod/Part/Gui/DlgSettingsObjectColor.ui +++ b/src/Mod/Part/Gui/DlgSettingsObjectColor.ui @@ -251,6 +251,9 @@ Two-side rendering + + true + TwoSideRendering diff --git a/src/Mod/Part/Gui/ViewProviderExt.cpp b/src/Mod/Part/Gui/ViewProviderExt.cpp index 70230e0af6..709873bc3d 100644 --- a/src/Mod/Part/Gui/ViewProviderExt.cpp +++ b/src/Mod/Part/Gui/ViewProviderExt.cpp @@ -246,7 +246,7 @@ ViewProviderPartExt::ViewProviderPartExt() ("User parameter:BaseApp/Preferences/Mod/Part"); NormalsFromUV = hPart->GetBool("NormalsFromUVNodes", NormalsFromUV); - long twoside = hPart->GetBool("TwoSideRendering", false) ? 1 : 0; + long twoside = hPart->GetBool("TwoSideRendering", true) ? 1 : 0; // Let the user define a custom lower limit but a value less than // OCCT's epsilon is not allowed diff --git a/src/Mod/PartDesign/Gui/ViewProviderDatum.h b/src/Mod/PartDesign/Gui/ViewProviderDatum.h index 43a5057360..e801d9418e 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderDatum.h +++ b/src/Mod/PartDesign/Gui/ViewProviderDatum.h @@ -94,7 +94,7 @@ public: * Computes appropriate bounding box for the given list of objects to be passed to setExtents () * @param bboxAction a coin action for traverse the given objects views. * @param objs the list of objects to traverse, due to we traverse the scene graph, the geo children - * will likely be traveresed too. + * will likely be traversed too. */ static SbBox3f getRelevantBoundBox ( SoGetBoundingBoxAction &bboxAction, diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp index ac7407d2cc..350c14dbe3 100644 --- a/src/Mod/PartDesign/Gui/Workbench.cpp +++ b/src/Mod/PartDesign/Gui/Workbench.cpp @@ -175,7 +175,6 @@ void Workbench::setupContextMenu(const char* recipient, Gui::MenuItem* item) con body = PartDesignGui::getBodyFor (feature, false, false, assertModern); // lote of assertion so feature should be marked as a tip if ( selection.size () == 1 && feature && ( - feature->isDerivedFrom ( PartDesign::Body::getClassTypeId () ) || ( feature->isDerivedFrom ( PartDesign::Feature::getClassTypeId () ) && body ) || ( feature->isDerivedFrom ( Part::Feature::getClassTypeId () ) && body && body->BaseFeature.getValue() == feature ) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index fe552ecbd9..1badf749da 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -1138,37 +1138,63 @@ static void showShapes(const T &shapes, const char *name, const char *fmt=0, ... } template -static int foreachSubshape(const TopoDS_Shape &shape, Func func, int type=TopAbs_FACE) { - bool haveShape = false; +static int foreachSubshape(const TopoDS_Shape &shape, + Func func, int type=TopAbs_FACE, bool groupOpenEdges=false) +{ + int res = -1; + std::vector openShapes; switch(type) { case TopAbs_SOLID: for(TopExp_Explorer it(shape,TopAbs_SOLID); it.More(); it.Next()) { - haveShape = true; + res = TopAbs_SOLID; func(it.Current(),TopAbs_SOLID); } - if(haveShape) return TopAbs_SOLID; + if(res>=0) break; //fall through case TopAbs_FACE: for(TopExp_Explorer it(shape,TopAbs_FACE); it.More(); it.Next()) { - haveShape = true; + res = TopAbs_FACE; func(it.Current(),TopAbs_FACE); } - if(haveShape) return TopAbs_FACE; + if(res>=0) break; //fall through case TopAbs_WIRE: - for(TopExp_Explorer it(shape,TopAbs_WIRE); it.More(); it.Next()) { - haveShape = true; - func(it.Current(),TopAbs_WIRE); + for(TopExp_Explorer it(shape, TopAbs_WIRE); it.More(); it.Next()) { + res = TopAbs_WIRE; + if(groupOpenEdges && !BRep_Tool::IsClosed(TopoDS::Wire(it.Current()))) + openShapes.push_back(it.Current()); + else + func(it.Current(),TopAbs_WIRE); } - if(haveShape) return TopAbs_WIRE; + if(res>=0) break; //fall through default: for(TopExp_Explorer it(shape,TopAbs_EDGE); it.More(); it.Next()) { - haveShape = true; + res = TopAbs_EDGE; + if(groupOpenEdges) { + TopoDS_Edge e = TopoDS::Edge(it.Current()); + gp_Pnt p1,p2; + getEndPoints(e,p1,p2); + if(p1.SquareDistance(p2) > Precision::SquareConfusion()) { + openShapes.push_back(it.Current()); + continue; + } + } func(it.Current(),TopAbs_EDGE); } } - return haveShape?TopAbs_EDGE:-1; + if(openShapes.empty()) + return res; + + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + for(auto &s : openShapes) { + for(TopExp_Explorer it(s,TopAbs_EDGE); it.More(); it.Next()) + builder.Add(comp,s); + } + func(comp, TopAbs_COMPOUND); + return TopAbs_COMPOUND; } struct FindPlane { @@ -2950,7 +2976,7 @@ std::list Area::sortWires(const std::list &shapes, //explode the shape if(!shape.IsNull()){ foreachSubshape(shape,ShapeInfoBuilder( - arcPlaneFound,arc_plane,trsf,shape_list,rparams)); + arcPlaneFound,arc_plane,trsf,shape_list,rparams),TopAbs_FACE,true); } } FC_TIME_LOG(t1,"plane finding"); diff --git a/src/Mod/Path/App/ParamsHelper.h b/src/Mod/Path/App/ParamsHelper.h index 234cdfcecb..025cf677ef 100644 --- a/src/Mod/Path/App/ParamsHelper.h +++ b/src/Mod/Path/App/ParamsHelper.h @@ -119,7 +119,7 @@ * * - \c default is the default value of this parameter. Right now, you must * supply a default value. Boost.PP has trouble dealing with empty values. - * Remember that a sequence cannot be empty. Neight can tuple. Only array, + * Remember that a sequence cannot be empty. Neither can tuple. Only array, * something like (0,()) for an empty array. It is awkward to write, * and didn't add much functionality I want, hence the restriction of * non-empty defaults here. diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 1a51ebc872..ded7c91a93 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -25,6 +25,8 @@ INSTALL( SET(PathScripts_SRCS PathCommands.py + PathScripts/PathAdaptive.py + PathScripts/PathAdaptiveGui.py PathScripts/PathAreaOp.py PathScripts/PathArray.py PathScripts/PathCircularHoleBase.py @@ -100,6 +102,7 @@ SET(PathScripts_SRCS PathScripts/PathSetupSheetOpPrototype.py PathScripts/PathSetupSheetOpPrototypeGui.py PathScripts/PathSimpleCopy.py + PathScripts/PathSimulatorGui.py PathScripts/PathStock.py PathScripts/PathStop.py PathScripts/PathSurface.py @@ -113,15 +116,14 @@ SET(PathScripts_SRCS PathScripts/PathToolController.py PathScripts/PathToolControllerGui.py PathScripts/PathToolEdit.py - PathScripts/PathToolLibraryManager.py PathScripts/PathToolLibraryEditor.py + PathScripts/PathToolLibraryManager.py PathScripts/PathUtil.py PathScripts/PathUtils.py PathScripts/PathUtilsGui.py - PathScripts/PathSimulatorGui.py + PathScripts/PathWaterline.py + PathScripts/PathWaterlineGui.py PathScripts/PostUtils.py - PathScripts/PathAdaptiveGui.py - PathScripts/PathAdaptive.py PathScripts/__init__.py ) @@ -192,8 +194,8 @@ SET(PathTests_SRCS PathTests/boxtest.fcstd PathTests/test_centroid_00.ngc PathTests/test_geomop.fcstd - PathTests/test_linuxcnc_00.ngc PathTests/test_holes00.fcstd + PathTests/test_linuxcnc_00.ngc ) SET(PathImages_Ops diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index ca09e140b8..4471e2eb6e 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -1,9 +1,19 @@ + icons/Path-Adaptive.svg + icons/Path-ToolDuplicate.svg icons/Path-3DPocket.svg icons/Path-3DSurface.svg + icons/Path-Area-View.svg + icons/Path-Area-Workplane.svg + icons/Path-Area.svg icons/Path-Array.svg icons/Path-Axis.svg + icons/Path-BFastForward.svg + icons/Path-BPause.svg + icons/Path-BPlay.svg + icons/Path-BStep.svg + icons/Path-BStop.svg icons/Path-BaseGeometry.svg icons/Path-Comment.svg icons/Path-Compound.svg @@ -17,9 +27,9 @@ icons/Path-Drilling.svg icons/Path-Engrave.svg icons/Path-ExportTemplate.svg + icons/Path-Face.svg icons/Path-FacePocket.svg icons/Path-FaceProfile.svg - icons/Path-Face.svg icons/Path-Heights.svg icons/Path-Helix.svg icons/Path-Hop.svg @@ -27,13 +37,13 @@ icons/Path-Job.svg icons/Path-Kurve.svg icons/Path-LengthOffset.svg + icons/Path-Machine.svg icons/Path-MachineLathe.svg icons/Path-MachineMill.svg - icons/Path-Machine.svg icons/Path-OpActive.svg + icons/Path-OpCopy.svg icons/Path-OperationA.svg icons/Path-OperationB.svg - icons/Path-OpCopy.svg icons/Path-Plane.svg icons/Path-Pocket.svg icons/Path-Post.svg @@ -46,49 +56,40 @@ icons/Path-SetupSheet.svg icons/Path-Shape.svg icons/Path-SimpleCopy.svg + icons/Path-Simulator.svg icons/Path-Speed.svg icons/Path-Stock.svg icons/Path-Stop.svg icons/Path-ToolBit.svg icons/Path-ToolChange.svg icons/Path-ToolController.svg - icons/Path-ToolDuplicate.svg icons/Path-Toolpath.svg icons/Path-ToolTable.svg - icons/Path-Area.svg - icons/Path-Area-View.svg - icons/Path-Area-Workplane.svg - icons/Path-Simulator.svg - icons/Path-BFastForward.svg - icons/Path-BPause.svg - icons/Path-BPlay.svg - icons/Path-BStep.svg - icons/Path-BStop.svg + icons/Path-Waterline.svg icons/arrow-ccw.svg icons/arrow-cw.svg icons/arrow-down.svg - icons/arrow-left.svg icons/arrow-left-down.svg icons/arrow-left-up.svg - icons/arrow-right.svg + icons/arrow-left.svg icons/arrow-right-down.svg icons/arrow-right-up.svg + icons/arrow-right.svg icons/arrow-up.svg - icons/edge-join-miter.svg icons/edge-join-miter-not.svg - icons/edge-join-round.svg + icons/edge-join-miter.svg icons/edge-join-round-not.svg + icons/edge-join-round.svg icons/preferences-path.svg - icons/Path-Adaptive.svg panels/DlgJobChooser.ui panels/DlgJobCreate.ui panels/DlgJobModelSelect.ui panels/DlgJobTemplateExport.ui panels/DlgSelectPostProcessor.ui + panels/DlgTCChooser.ui panels/DlgToolControllerEdit.ui panels/DlgToolCopy.ui panels/DlgToolEdit.ui - panels/DlgTCChooser.ui panels/DogboneEdit.ui panels/DressupPathBoundary.ui panels/HoldingTagsEdit.ui @@ -106,6 +107,7 @@ panels/PageOpProbeEdit.ui panels/PageOpProfileFullEdit.ui panels/PageOpSurfaceEdit.ui + panels/PageOpWaterlineEdit.ui panels/PathEdit.ui panels/PointEdit.ui panels/SetupGlobal.ui @@ -120,16 +122,25 @@ preferences/PathDressupHoldingTags.ui preferences/PathJob.ui translations/Path_af.qm + translations/Path_ar.qm + translations/Path_ca.qm translations/Path_cs.qm translations/Path_de.qm translations/Path_el.qm translations/Path_es-ES.qm + translations/Path_eu.qm translations/Path_fi.qm + translations/Path_fil.qm translations/Path_fr.qm + translations/Path_gl.qm translations/Path_hr.qm translations/Path_hu.qm + translations/Path_id.qm translations/Path_it.qm translations/Path_ja.qm + translations/Path_kab.qm + translations/Path_ko.qm + translations/Path_lt.qm translations/Path_nl.qm translations/Path_no.qm translations/Path_pl.qm @@ -143,18 +154,9 @@ translations/Path_sv-SE.qm translations/Path_tr.qm translations/Path_uk.qm + translations/Path_val-ES.qm + translations/Path_vi.qm translations/Path_zh-CN.qm translations/Path_zh-TW.qm - translations/Path_eu.qm - translations/Path_ca.qm - translations/Path_gl.qm - translations/Path_kab.qm - translations/Path_ko.qm - translations/Path_fil.qm - translations/Path_id.qm - translations/Path_lt.qm - translations/Path_val-ES.qm - translations/Path_ar.qm - translations/Path_vi.qm diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg b/src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg new file mode 100644 index 0000000000..86c07f4c62 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg @@ -0,0 +1,281 @@ + + + Path_Waterline + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Path_Waterline + Path-Waterline + 2019-05-19 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [russ4262] Russell Johnson + + + + + [russ4262] Russell Johnson + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui index d019e3fe67..e4aaf19e5e 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui @@ -6,8 +6,8 @@ 0 0 - 357 - 427 + 350 + 400 @@ -24,7 +24,7 @@ - + ToolController @@ -38,7 +38,7 @@ - + Coolant Mode @@ -57,126 +57,35 @@ - - - - Algorithm - - - - - - - - OCL Dropcutter - - - - - OCL Waterline - - - - - - - - BoundBox - - - - + - Stock + Planar - BaseBoundBox + Rotational - - - - BoundBox extra offset X, Y - - - - - - - mm - - - - - - - mm - - - - - - - Drop Cutter Direction - - - - - + + - X + Single-pass - Y + Multi-pass - - - - Depth offset - - - - - - - mm - - - - - - - Sample interval - - - - - - - mm - - - - - - - Step over - - - - + <html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html> @@ -195,20 +104,180 @@ - - + + - Optimize output + Step over + + + + + + + Sample interval + + + + + + + Layer Mode + + + + + + + Optimize Linear Paths + + + + + + + Drop Cutter Direction + + + + + + + BoundBox extra offset X, Y + + + + + + + Use Start Point + + + + + + + Scan Type + + + + + + + BoundBox - - - Enabled + + + mm + + + + + + + 0 + 0 + + + + mm + + + + + + + mm + + + + + + + + + mm + + + + + + + Depth offset + + + + + + + + X + + + + + Y + + + + + + + + + Stock + + + + + BaseBoundBox + + + + + + + + Optimize StepOver Transitions + + + + + + + Cut Pattern + + + + + + + + Line + + + + + ZigZag + + + + + Circular + + + + + CircularZigZag + + + + diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui new file mode 100644 index 0000000000..5e0edef1c9 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui @@ -0,0 +1,269 @@ + + + Form + + + + 0 + 0 + 350 + 400 + + + + Form + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + ToolController + + + + + + + <html><head/><body><p>The tool and its settings to be used for this operation.</p></body></html> + + + + + + + + + + + + + + 8 + + + + + Stock + + + + + BaseBoundBox + + + + + + + + + 0 + 0 + + + + + + + + + 8 + + + + + Single-pass + + + + + Multi-pass + + + + + + + + + OCL Dropcutter + + + + + Experimental + + + + + + + + Optimize Linear Paths + + + + + + + + 0 + 0 + + + + Boundary Adjustment + + + + + + + + 8 + + + + + None + + + + + Line + + + + + ZigZag + + + + + Circular + + + + + CircularZigZag + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html> + + + 1 + + + 100 + + + 10 + + + 100 + + + + + + + Layer Mode + + + + + + + + 0 + 0 + + + + BoundBox + + + + + + + Step over + + + + + + + mm + + + + + + + Cut Pattern + + + + + + + Sample interval + + + + + + + Algorithm + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::InputField + QWidget +
gui::inputfield.h
+
+
+ + +
diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index dc638a9299..6fabab05f0 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -21,8 +21,9 @@ # * * # ***************************************************************************/ + class PathCommandGroup: - def __init__(self, cmdlist, menu, tooltip = None): + def __init__(self, cmdlist, menu, tooltip=None): self.cmdlist = cmdlist self.menu = menu if tooltip is None: @@ -34,7 +35,7 @@ class PathCommandGroup: return tuple(self.cmdlist) def GetResources(self): - return { 'MenuText': self.menu, 'ToolTip': self.tooltip } + return {'MenuText': self.menu, 'ToolTip': self.tooltip} def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -43,6 +44,7 @@ class PathCommandGroup: return True return False + class PathWorkbench (Workbench): "Path workbench" @@ -88,14 +90,14 @@ class PathWorkbench (Workbench): projcmdlist = ["Path_Job", "Path_Post"] toolcmdlist = ["Path_Inspect", "Path_Simulator", "Path_ToolLibraryEdit", "Path_SelectLoop", "Path_OpActiveToggle"] prepcmdlist = ["Path_Fixture", "Path_Comment", "Path_Stop", "Path_Custom", "Path_Probe"] - twodopcmdlist = ["Path_Contour", "Path_Profile_Faces", "Path_Profile_Edges", "Path_Pocket_Shape", "Path_Drilling", "Path_MillFace", "Path_Helix", "Path_Adaptive" ] + twodopcmdlist = ["Path_Contour", "Path_Profile_Faces", "Path_Profile_Edges", "Path_Pocket_Shape", "Path_Drilling", "Path_MillFace", "Path_Helix", "Path_Adaptive"] threedopcmdlist = ["Path_Pocket_3D"] engravecmdlist = ["Path_Engrave", "Path_Deburr"] - modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy" ] + modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy"] dressupcmdlist = ["Path_DressupAxisMap", "Path_DressupPathBoundary", "Path_DressupDogbone", "Path_DressupDragKnife", "Path_DressupLeadInOut", "Path_DressupRampEntry", "Path_DressupTag", "Path_DressupZCorrect"] extracmdlist = [] - #modcmdmore = ["Path_Hop",] - #remotecmdlist = ["Path_Remote"] + # modcmdmore = ["Path_Hop",] + # remotecmdlist = ["Path_Remote"] engravecmdgroup = ['Path_EngraveTools'] FreeCADGui.addCommand('Path_EngraveTools', PathCommandGroup(engravecmdlist, QtCore.QT_TRANSLATE_NOOP("Path", 'Engraving Operations'))) @@ -107,11 +109,12 @@ class PathWorkbench (Workbench): extracmdlist.extend(["Path_Area", "Path_Area_Workplane"]) try: - import ocl # pylint: disable=unused-variable + import ocl # pylint: disable=unused-variable from PathScripts import PathSurfaceGui - threedopcmdlist.append("Path_Surface") + from PathScripts import PathWaterlineGui + threedopcmdlist.extend(["Path_Surface", "Path_Waterline"]) threedcmdgroup = ['Path_3dTools'] - FreeCADGui.addCommand('Path_3dTools', PathCommandGroup(threedopcmdlist, QtCore.QT_TRANSLATE_NOOP("Path",'3D Operations'))) + FreeCADGui.addCommand('Path_3dTools', PathCommandGroup(threedopcmdlist, QtCore.QT_TRANSLATE_NOOP("Path", '3D Operations'))) except ImportError: FreeCAD.Console.PrintError("OpenCamLib is not working!\n") @@ -122,7 +125,9 @@ class PathWorkbench (Workbench): if extracmdlist: self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Helpful Tools"), extracmdlist) - self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], projcmdlist +["Path_ExportTemplate", "Separator"] + toolbitcmdlist + toolcmdlist +["Separator"] + twodopcmdlist + engravecmdlist +["Separator"] +threedopcmdlist +["Separator"]) + self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], projcmdlist + ["Path_ExportTemplate", "Separator"] + + toolbitcmdlist + toolcmdlist + ["Separator"] + twodopcmdlist + engravecmdlist + ["Separator"] + + threedopcmdlist + ["Separator"]) self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP( "Path", "Path Dressup")], dressupcmdlist) self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP( @@ -136,7 +141,7 @@ class PathWorkbench (Workbench): curveAccuracy = PathPreferences.defaultLibAreaCurveAccuracy() if curveAccuracy: - Path.Area.setDefaultParams(Accuracy = curveAccuracy) + Path.Area.setDefaultParams(Accuracy=curveAccuracy) Log('Loading Path workbench... done\n') @@ -171,8 +176,8 @@ class PathWorkbench (Workbench): if obj.isDerivedFrom("Path::Feature"): if "Profile" in selectedName or "Contour" in selectedName or "Dressup" in selectedName: self.appendContextMenu("", "Separator") - #self.appendContextMenu("", ["Set_StartPoint"]) - #self.appendContextMenu("", ["Set_EndPoint"]) + # self.appendContextMenu("", ["Set_StartPoint"]) + # self.appendContextMenu("", ["Set_EndPoint"]) for cmd in self.dressupcmds: self.appendContextMenu("", [cmd]) menuAppended = True @@ -182,10 +187,10 @@ class PathWorkbench (Workbench): if menuAppended: self.appendContextMenu("", "Separator") + Gui.addWorkbench(PathWorkbench()) FreeCAD.addImportType( "GCode (*.nc *.gc *.ncc *.ngc *.cnc *.tap *.gcode)", "PathGui") # FreeCAD.addExportType( # "GCode (*.nc *.gc *.ncc *.ngc *.cnc *.tap *.gcode)", "PathGui") - diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index d057742bfa..3ccb4a39d6 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -3,7 +3,6 @@ # *************************************************************************** # * * # * Copyright (c) 2017 sliptonic * -# * Copyright (c) 2020 russ4262 (Russell Johnson) * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -49,6 +48,7 @@ PathLog.setLevel(LOGLEVEL, PathLog.thisModule()) if LOGLEVEL is PathLog.Level.DEBUG: PathLog.trackModule() + # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -66,7 +66,6 @@ class ObjectOp(PathOp.ObjectOp): '''opFeatures(obj) ... returns the base features supported by all Path.Area based operations. The standard feature list is OR'ed with the return value of areaOpFeatures(). Do not overwrite, implement areaOpFeatures(obj) instead.''' - # return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) | PathOp.FeatureRotation return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) | PathOp.FeatureCoolant def areaOpFeatures(self, obj): @@ -304,8 +303,6 @@ class ObjectOp(PathOp.ObjectOp): pathParams['return_end'] = True # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers pathParams['preamble'] = False - #if not self.areaOpRetractTool(obj): - # pathParams['threshold'] = 2.001 * self.radius if self.endVector is None: V = hWire.Wires[0].Vertexes @@ -374,12 +371,6 @@ class ObjectOp(PathOp.ObjectOp): obj.ClearanceHeight.Value = strDep + self.clrOfset obj.SafeHeight.Value = strDep + self.safOfst - #if self.initWithRotation is False: - # if obj.FinalDepth.Value == obj.OpFinalDepth.Value: - # obj.FinalDepth.Value = finDep - # if obj.StartDepth.Value == obj.OpStartDepth.Value: - # obj.StartDepth.Value = strDep - # Create visual axes when debugging. if PathLog.getLevel(PathLog.thisModule()) == 4: self.visualAxis() @@ -467,10 +458,14 @@ class ObjectOp(PathOp.ObjectOp): # Rotate Model to correct angle ppCmds.insert(0, Path.Command('G0', {axisOfRot: angle, 'F': self.axialRapid})) - # Raise cutter to safe depth and return index to starting position - ppCmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - if axis != nextAxis: - ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid})) + # Raise cutter to safe height + ppCmds.insert(0, Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + + # Return index to starting position if axis of rotation changes. + if numShapes > 1: + if ns != numShapes - 1: + if axis != nextAxis: + ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid})) # Eif # Save gcode commands to object command list @@ -483,9 +478,43 @@ class ObjectOp(PathOp.ObjectOp): # Raise cutter to safe height and rotate back to original orientation if self.rotateFlag is True: + resetAxis = False + lastJobOp = None + nextJobOp = None + opIdx = 0 + JOB = PathUtils.findParentJob(obj) + jobOps = JOB.Operations.Group + numJobOps = len(jobOps) + + for joi in range(0, numJobOps): + jo = jobOps[joi] + if jo.Name == obj.Name: + opIdx = joi + lastOpIdx = opIdx - 1 + nextOpIdx = opIdx + 1 + if lastOpIdx > -1: + lastJobOp = jobOps[lastOpIdx] + if nextOpIdx < numJobOps: + nextJobOp = jobOps[nextOpIdx] + + if lastJobOp is not None: + if hasattr(lastJobOp, 'EnableRotation'): + PathLog.debug('Last Op, {}, has `EnableRotation` set to {}'.format(lastJobOp.Label, lastJobOp.EnableRotation)) + if lastJobOp.EnableRotation != obj.EnableRotation: + resetAxis = True + if ns == numShapes - 1: # If last shape, check next op EnableRotation setting + if nextJobOp is not None: + if hasattr(nextJobOp, 'EnableRotation'): + PathLog.debug('Next Op, {}, has `EnableRotation` set to {}'.format(nextJobOp.Label, nextJobOp.EnableRotation)) + if nextJobOp.EnableRotation != obj.EnableRotation: + resetAxis = True + + # Raise to safe height if rotation activated self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid})) - self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid})) + # reset rotational axes if necessary + if resetAxis is True: + self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid})) + self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid})) self.useTempJobClones('Delete') # Delete temp job clone group and contents self.guiMessage('title', None, show=True) # Process GUI messages to user @@ -531,10 +560,8 @@ class ObjectOp(PathOp.ObjectOp): Determine rotational radii for 4th-axis rotations, for clearance/safe heights ''' parentJob = PathUtils.findParentJob(obj) - # bb = parentJob.Stock.Shape.BoundBox xlim = 0.0 ylim = 0.0 - # zlim = 0.0 # Determine boundbox radius based upon xzy limits data if math.fabs(self.stockBB.ZMin) > math.fabs(self.stockBB.ZMax): diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index eea0deda3b..570d7c09e8 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -333,7 +333,6 @@ class ObjectOp(PathOp.ObjectOp): msg = translate("Path", "Final Depth setting is below the hole bottom for {}.".format(sub)) + ' ' msg += translate("Path", "{} depth is calculated at {} mm".format(sub, round(holeBtm, 4))) PathLog.warning(msg) - finDep = holeBtm holes.append({'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub), 'angle': angle, 'axis': axis, 'trgtDep': finDep, @@ -441,7 +440,7 @@ class ObjectOp(PathOp.ObjectOp): zlim = 0.0 xRotRad = 0.01 yRotRad = 0.01 - #xRotRad = 0.01 + zRotRad = 0.01 # Determine boundbox radius based upon xzy limits data if math.fabs(self.stockBB.ZMin) > math.fabs(self.stockBB.ZMax): diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index 46ba0f58ee..aa1b1e6d21 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -3,6 +3,7 @@ # *************************************************************************** # * * # * Copyright (c) 2018 sliptonic * +# * Copyright (c) 2020 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -58,6 +59,7 @@ def toolDepthAndOffset(width, extraDepth, tool): toolOffset = float(tool.FlatRadius) extraOffset = float(tool.Diameter) / 2 - width if 180 == angle else extraDepth / tan offset = toolOffset + extraOffset + return (depth, offset) @@ -76,6 +78,10 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): obj.setEditorMode('Join', 2) # hide for now obj.addProperty('App::PropertyEnumeration', 'Direction', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Direction of Operation')) obj.Direction = ['CW', 'CCW'] + obj.addProperty('App::PropertyEnumeration', 'Side', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Side of Operation')) + obj.Side = ['Outside', 'Inside'] + obj.setEditorMode('Side', 2) # Hide property, it's calculated by op + obj.addProperty('App::PropertyInteger', 'EntryPoint', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Select the segment, there the operations starts')) def opOnDocumentRestored(self, obj): obj.setEditorMode('Join', 2) # hide for now @@ -104,13 +110,20 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): basewires.append(Part.Wire(edgelist)) self.basewires.extend(basewires) - + + # Set default value + side = ["Outside"] + for w in basewires: self.adjusted_basewires.append(w) - wire = PathOpTools.offsetWire(w, base.Shape, offset, True) + wire = PathOpTools.offsetWire(w, base.Shape, offset, True, side) if wire: wires.append(wire) - + + # Save Outside or Inside + obj.Side = side[0] + + # Set direction of op forward = True if obj.Direction == 'CCW': forward = False @@ -123,9 +136,12 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): zValues.append(z) zValues.append(depth) PathLog.track(obj.Label, depth, zValues) - + + if obj.EntryPoint < 0: + obj.EntryPoint = 0; + self.wires = wires # pylint: disable=attribute-defined-outside-init - self.buildpathocc(obj, wires, zValues, True, forward) + self.buildpathocc(obj, wires, zValues, True, forward, obj.EntryPoint) # the last command is a move to clearance, which is automatically added by PathOp if self.commandlist: @@ -138,11 +154,13 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): def opSetDefaultValues(self, obj, job): PathLog.track(obj.Label, job.Label) obj.Width = '1 mm' - obj.ExtraDepth = '0.1 mm' + obj.ExtraDepth = '0.5 mm' obj.Join = 'Round' obj.setExpression('StepDown', '0 mm') obj.StepDown = '0 mm' obj.Direction = 'CW' + obj.Side = "Outside" + obj.EntryPoint = 0; def SetupProperties(): diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py index 4ab56d5b37..db5fdc2d21 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -3,6 +3,7 @@ # *************************************************************************** # * * # * Copyright (c) 2017 LTS under LGPL * +# * Copyright (c) 2020 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -31,6 +32,7 @@ import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathUtils as PathUtils import math +import copy from PySide import QtCore @@ -68,7 +70,11 @@ class ObjectDressup: obj.addProperty("App::PropertyEnumeration", "RadiusCenter", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupLeadInOut", "The Mode of Point Radiusoffset or Center")) obj.RadiusCenter = ["Radius", "Center"] obj.Proxy = self - + obj.addProperty("App::PropertyDistance", "ExtendLeadIn", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extends LeadIn distance")) + obj.addProperty("App::PropertyDistance", "ExtendLeadOut", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extends LeadOut distance")) + obj.addProperty("App::PropertyBool", "RapidPlunge", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Perform plunges with G0")) + obj.addProperty("App::PropertyBool", "IncludeLayers", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Apply LeadInOut to layers within an operation")) + self.wire = None self.rapids = None @@ -80,7 +86,7 @@ class ObjectDressup: return None def setup(self, obj): - obj.Length = 5.0 + obj.Length = obj.Base.ToolController.Tool.Diameter * 0.75 obj.LeadIn = True obj.LeadOut = True obj.KeepToolDown = False @@ -88,6 +94,10 @@ class ObjectDressup: obj.StyleOn = 'Arc' obj.StyleOff = 'Arc' obj.RadiusCenter = 'Radius' + obj.ExtendLeadIn = 0 + obj.ExtendLeadOut = 0 + obj.RapidPlunge = False + obj.IncludeLayers = True def execute(self, obj): if not obj.Base: @@ -113,78 +123,173 @@ class ObjectDressup: if hasattr(op, 'Direction') and op.Direction == 'CW': return 'right' return 'left' + + def getSideOfPath(self, obj): + op = PathDressup.baseOp(obj.Base) + if hasattr(op, 'Side'): + return op.Side + + return '' def normalize(self, Vector): x = Vector.x y = Vector.y length = math.sqrt(x*x + y*y) if((math.fabs(length)) > 0.0000000000001): - vx = round(x / length, 0) - vy = round(y / length, 0) + vx = round(x / length, 3) + vy = round(y / length, 3) return FreeCAD.Vector(vx, vy, 0) + + def invert(self, Vector): + x = Vector.x * -1 + y = Vector.y * -1 + z = Vector.z * -1 + return FreeCAD.Vector(x, y, z) + + def multiply(self, Vector, len): + x = Vector.x * len + y = Vector.y * len + z = Vector.z * len + return FreeCAD.Vector(x, y, z) + + def rotate(self, Vector, angle): + s = math.sin(math.radians(angle)) + c = math.cos(math.radians(angle)) + xnew = Vector.x * c - Vector.y * s; + ynew = Vector.x * s + Vector.y * c; + return FreeCAD.Vector(xnew, ynew, Vector.z) def getLeadStart(self, obj, queue, action): '''returns Lead In G-code.''' results = [] - # zdepth = currLocation["Z"] op = PathDressup.baseOp(obj.Base) tc = PathDressup.toolController(obj.Base) horizFeed = tc.HorizFeed.Value vertFeed = tc.VertFeed.Value toolnummer = tc.ToolNumber - # set the correct twist command + arcs_identical = False + + # Set the correct twist command if self.getDirectionOfPath(obj) == 'left': arcdir = "G3" else: arcdir = "G2" + R = obj.Length.Value # Radius of roll or length if queue[1].Name == "G1": # line p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base v = self.normalize(p1.sub(p0)) - # PathLog.notice(" CURRENT_IN : P0 Z:{} p1 Z:{}".format(p0.z,p1.z)) + # PathLog.debug(" CURRENT_IN : P0 Z:{} p1 Z:{}".format(p0.z,p1.z)) else: p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base - # PathLog.notice(" CURRENT_IN ARC : P0 X:{} Y:{} P1 X:{} Y:{} ".format(p0.x,p0.y,p1.x,p1.y)) v = self.normalize(p1.sub(p0)) + # PathLog.debug(" CURRENT_IN ARC : P0 X:{} Y:{} P1 X:{} Y:{} ".format(p0.x,p0.y,p1.x,p1.y)) + + # Calculate offset vector (will be overwritten for arcs) if self.getDirectionOfPath(obj) == 'right': off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) else: off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) - offsetvector = FreeCAD.Vector(v.x*R, v.y*R, 0) # IJ + + # Check if we enter at line or arc command + if queue[1].Name in movecommands and queue[1].Name not in arccommands: + # We have a line move + vec = p1.sub(p0) + vec_n = self.normalize(vec) + vec_inv = self.invert(vec_n) + vec_off = self.multiply(vec_inv, obj.ExtendLeadIn) + #PathLog.debug("LineCMD: {}, Vxinv: {}, Vyinv: {}, Vxoff: {}, Vyoff: {}".format(queue[0].Name, vec_inv.x, vec_inv.y, vec_off.x, vec_off.y)) + else: + # We have an arc move + # Calculate coordinates for middle of circle + pij = copy.deepcopy(p0) + pij.x += queue[1].Parameters['I'] + pij.y += queue[1].Parameters['J'] + + # Check if lead in and operation go in same direction (usually for inner circles) + if arcdir == queue[1].Name: + arcs_identical = True + + # Calculate vector circle start -> circle middle + vec_circ = pij.sub(p0) + + # Rotate vector to get direction for lead in + if arcdir == "G2": + vec_rot = self.rotate(vec_circ, 90) + else: + vec_rot = self.rotate(vec_circ, -90) + + # Normalize and invert vector + vec_n = self.normalize(vec_rot) + + v = self.invert(vec_n) + + # Calculate offset of lead in + if arcdir == "G3": + off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) + else: + off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) + + # Multiply offset by LeadIn length + vec_off = self.multiply(vec_n, obj.ExtendLeadIn) + + offsetvector = FreeCAD.Vector(v.x*R-vec_off.x, v.y*R-vec_off.y, 0) # IJ + if obj.RadiusCenter == 'Radius': leadstart = (p0.add(off_v)).sub(offsetvector) # Rmode + if arcs_identical: + t = p0.sub(leadstart) + t = p0.add(t) + leadstart = t + offsetvector = self.multiply(offsetvector, -1) else: leadstart = p0.add(off_v) # Dmode + if action == 'start': - extendcommand = Path.Command('G0', {"X": 0.0, "Y": 0.0, "Z": op.ClearanceHeight.Value}) - results.append(extendcommand) + #extendcommand = Path.Command('G0', {"X": 0.0, "Y": 0.0, "Z": op.ClearanceHeight.Value}) + #results.append(extendcommand) extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": op.ClearanceHeight.Value}) results.append(extendcommand) - extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": op.SafeHeight.Value}) + extendcommand = Path.Command('G0', {"Z": op.SafeHeight.Value}) results.append(extendcommand) + if action == 'layer': if not obj.KeepToolDown: extendcommand = Path.Command('G0', {"Z": op.SafeHeight.Value}) results.append(extendcommand) + extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y}) results.append(extendcommand) - extendcommand = Path.Command('G1', {"X": leadstart.x, "Y": leadstart.y, "Z": p1.z, "F": vertFeed}) + + if not obj.RapidPlunge: + extendcommand = Path.Command('G1', {"X": leadstart.x, "Y": leadstart.y, "Z": p1.z, "F": vertFeed}) + else: + extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": p1.z,}) results.append(extendcommand) + if obj.UseMachineCRC: if self.getDirectionOfPath(obj) == 'right': results.append(Path.Command('G42', {'D': toolnummer})) else: results.append(Path.Command('G41', {'D': toolnummer})) + if obj.StyleOn == 'Arc': - arcmove = Path.Command(arcdir, {"X": p0.x, "Y": p0.y, "I": offsetvector.x, "J": offsetvector.y, "F": horizFeed}) # add G2/G3 move + arcmove = Path.Command(arcdir, {"X": p0.x+vec_off.x, "Y": p0.y+vec_off.y, "I": offsetvector.x+vec_off.x, "J": offsetvector.y+vec_off.y, "F": horizFeed}) # add G2/G3 move results.append(arcmove) + if obj.ExtendLeadIn != 0: + extendcommand = Path.Command('G1', {"X": p0.x, "Y": p0.y, "F": horizFeed}) + results.append(extendcommand) elif obj.StyleOn == 'Tangent': extendcommand = Path.Command('G1', {"X": p0.x, "Y": p0.y, "F": horizFeed}) results.append(extendcommand) else: - PathLog.notice(" CURRENT_IN Perp") + PathLog.debug(" CURRENT_IN Perp") + + currLocation.update(results[-1].Parameters) + currLocation['Z'] = p1.z + return results def getLeadEnd(self, obj, queue, action): @@ -193,11 +298,14 @@ class ObjectDressup: results = [] horizFeed = PathDressup.toolController(obj.Base).HorizFeed.Value R = obj.Length.Value # Radius of roll or length - # set the correct twist command + arcs_identical = False + + # Set the correct twist command if self.getDirectionOfPath(obj) == 'right': arcdir = "G2" else: arcdir = "G3" + if queue[1].Name == "G1": # line p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base @@ -206,97 +314,157 @@ class ObjectDressup: p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base v = self.normalize(p1.sub(p0)) + if self.getDirectionOfPath(obj) == 'right': off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) else: off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) - offsetvector = FreeCAD.Vector(v.x*R, v.y*R, 0.0) + + # Check if we leave at line or arc command + if queue[1].Name in movecommands and queue[1].Name not in arccommands: + # We have a line move + vec = p1.sub(p0) + vec_n = self.normalize(vec) + vec_inv = self.invert(vec_n) + vec_off = self.multiply(vec_inv, obj.ExtendLeadOut) + #PathLog.debug("LineCMD: {}, Vxinv: {}, Vyinv: {}, Vxoff: {}, Vyoff: {}".format(queue[0].Name, vec_inv.x, vec_inv.y, vec_off.x, vec_off.y)) + else: + # We have an arc move + pij = copy.deepcopy(p0) + pij.x += queue[1].Parameters['I'] + pij.y += queue[1].Parameters['J'] + ve = pij.sub(p1) + + if arcdir == queue[1].Name: + arcs_identical = True + + if arcdir == "G2": + vec_rot = self.rotate(ve, -90) + else: + vec_rot = self.rotate(ve, 90) + + vec_n = self.normalize(vec_rot) + v = vec_n + + if arcdir == "G3": + off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) + else: + off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) + + vec_inv = self.invert(vec_rot) + + vec_off = self.multiply(vec_inv, obj.ExtendLeadOut) + + offsetvector = FreeCAD.Vector(v.x*R-vec_off.x, v.y*R-vec_off.y, 0.0) if obj.RadiusCenter == 'Radius': leadend = (p1.add(off_v)).add(offsetvector) # Rmode + if arcs_identical: + t = p1.sub(leadend) + t = p1.add(t) + leadend = t + off_v = self.multiply(off_v, -1) else: leadend = p1.add(off_v) # Dmode + IJ = off_v # .negative() #results.append(queue[1]) if obj.StyleOff == 'Arc': + if obj.ExtendLeadOut != 0: + extendcommand = Path.Command('G1', {"X": p1.x-vec_off.x, "Y": p1.y-vec_off.y, "F": horizFeed}) + results.append(extendcommand) arcmove = Path.Command(arcdir, {"X": leadend.x, "Y": leadend.y, "I": IJ.x, "J": IJ.y, "F": horizFeed}) # add G2/G3 move results.append(arcmove) elif obj.StyleOff == 'Tangent': extendcommand = Path.Command('G1', {"X": leadend.x, "Y": leadend.y, "F": horizFeed}) results.append(extendcommand) else: - PathLog.notice(" CURRENT_IN Perp") + PathLog.debug(" CURRENT_IN Perp") + if obj.UseMachineCRC: # crc off results.append(Path.Command('G40', {})) + return results def generateLeadInOutCurve(self, obj): global currLocation # pylint: disable=global-statement firstmove = Path.Command("G0", {"X": 0, "Y": 0, "Z": 0}) + op = PathDressup.baseOp(obj.Base) currLocation.update(firstmove.Parameters) newpath = [] queue = [] action = 'start' + prevCmd = '' + layers = [] + + # Read in all commands for curCommand in obj.Base.Path.Commands: - # replace = None - # don't worry about non-move commands, just add to output + #PathLog.debug("CurCMD: {}".format(curCommand)) if curCommand.Name not in movecommands + rapidcommands: + # Don't worry about non-move commands, just add to output newpath.append(curCommand) continue - - # rapid retract triggers exit move, else just add to output + if curCommand.Name in rapidcommands: - # detect start position - if (curCommand.x is not None) or (curCommand.y is not None): - firstmove = curCommand + # We don't care about rapid moves + prevCmd = curCommand currLocation.update(curCommand.Parameters) - if action != 'start': # done move out - if obj.LeadOut: - temp = self.getLeadEnd(obj, queue, 'end') - newpath.extend(temp) - newpath.append(curCommand) # Z clear DONE - + continue + if curCommand.Name in movecommands: + if prevCmd.Name in rapidcommands and curCommand.Name in movecommands and len(queue) > 0: + # Layer changed: Save current layer cmds and prepare next layer + layers.append(queue) + queue = [] + if obj.IncludeLayers and curCommand.z < currLocation['Z'] and prevCmd.Name in movecommands: + # Layer change within move cmds + #PathLog.debug("Layer change in move: {}->{}".format(currLocation['Z'], curCommand.z)) + layers.append(queue) + queue = [] + + # Save all move commands queue.append(curCommand) - if action == 'start' and len(queue) < 2: - continue - if action == 'layer': - if len(queue) > 2: - queue.pop(0) - if obj.LeadIn: - temp = self.getLeadStart(obj, queue, action) - newpath.extend(temp) - #newpath.append(curCommand) - action = 'none' - currLocation.update(curCommand.Parameters) - else: - newpath.append(curCommand) - if curCommand.z != currLocation["Z"] and action != 'start': # vertical feeding to depth - if obj.LeadOut: # fish cycle - if len(queue) > 2: - queue.pop(len(queue)-1) - temp = self.getLeadEnd(obj, queue, action) - newpath.extend(temp) - action = 'layer' - if len(queue) > 2: - queue.pop(0) - continue - else: - newpath.append(curCommand) - if len(queue) > 2: - queue.pop(0) - if obj.LeadIn and len(queue) >= 2 and action == 'start': - temp = self.getLeadStart(obj, queue, action) - newpath.extend(temp) - newpath.append(curCommand) - action = 'none' - currLocation.update(curCommand.Parameters) - else: - newpath.append(curCommand) - currLocation.update(curCommand.Parameters) + + currLocation.update(curCommand.Parameters) + prevCmd = curCommand + + # Add last layer + if len(queue) > 0: + layers.append(queue) + queue = [] + + # Go through each layer and add leadIn/Out + idx = 0 + for layer in layers: + #PathLog.debug("Layer {}".format(idx)) + + if obj.LeadIn: + temp = self.getLeadStart(obj, layer, action) + newpath.extend(temp) + + for cmd in layer: + #PathLog.debug("CurLoc: {}, NewCmd: {}".format(currLocation, cmd)) + #if currLocation['X'] == cmd.x and currLocation['Y'] == cmd.y and currLocation['Z'] == cmd.z and cmd.Name in ['G1', 'G01']: + #continue + newpath.append(cmd) + + if obj.LeadOut: + tmp = [] + tmp.append(layer[-2]) + tmp.append(layer[-1]) + temp = self.getLeadEnd(obj, tmp, action) + newpath.extend(temp) + + if not obj.KeepToolDown or idx == len(layers)-1: + extendcommand = Path.Command('G0', {"Z": op.ClearanceHeight.Value}) + newpath.append(extendcommand) + else: + action = 'layer' + + idx += 1 + commands = newpath return Path.Path(commands) - class ViewProviderDressup: def __init__(self, vobj): diff --git a/src/Mod/Path/PathScripts/PathDressupZCorrect.py b/src/Mod/Path/PathScripts/PathDressupZCorrect.py index c035451829..b3e766d89e 100644 --- a/src/Mod/Path/PathScripts/PathDressupZCorrect.py +++ b/src/Mod/Path/PathScripts/PathDressupZCorrect.py @@ -47,7 +47,7 @@ else: PathLog.setLevel(PathLog.Level.NOTICE, LOG_MODULE) -# Qt tanslation handling +# Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) diff --git a/src/Mod/Path/PathScripts/PathEngraveBase.py b/src/Mod/Path/PathScripts/PathEngraveBase.py index df5c2436b8..459e60a839 100644 --- a/src/Mod/Path/PathScripts/PathEngraveBase.py +++ b/src/Mod/Path/PathScripts/PathEngraveBase.py @@ -59,7 +59,7 @@ class ObjectOp(PathOp.ObjectOp): zValues.append(obj.FinalDepth.Value) return zValues - def buildpathocc(self, obj, wires, zValues, relZ=False, forward=True): + def buildpathocc(self, obj, wires, zValues, relZ=False, forward=True, start_idx=0): '''buildpathocc(obj, wires, zValues, relZ=False) ... internal helper function to generate engraving commands.''' PathLog.track(obj.Label, len(wires), zValues) @@ -78,6 +78,10 @@ class ObjectOp(PathOp.ObjectOp): self.appendCommand(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': last.z}), z, relZ, self.vertFeed) first = True + if start_idx > len(edges)-1: + start_idx = len(edges)-1 + + edges = edges[start_idx:] + edges[:start_idx] for edge in edges: if first and (not last or not wire.isClosed()): # we set the first move to our first point @@ -86,7 +90,7 @@ class ObjectOp(PathOp.ObjectOp): self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) self.commandlist.append(Path.Command('G0', {'X': last.x, 'Y': last.y, 'F': self.horizRapid})) self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - self.appendCommand(Path.Command('G1', {'Z': last.z}), z, relZ, self.vertFeed) + self.appendCommand(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': last.z}), z, relZ, self.vertFeed) first = False if PathGeom.pointsCoincide(last, edge.Vertexes[0].Point): diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index 98cf988106..b3ea83ccd8 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -45,41 +45,68 @@ if LOGLEVEL: else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + def updateInputField(obj, prop, widget, onBeforeChange=None): '''updateInputField(obj, prop, widget) ... update obj's property prop with the value of widget. -The property's value is only assigned if the new value differs from the current value. -This prevents onChanged notifications where the value didn't actually change. -Gui::InputField and Gui::QuantitySpinBox widgets are supported - and the property can -be of type Quantity or Float. -If onBeforeChange is specified it is called before a new value is assigned to the property. -Returns True if a new value was assigned, False otherwise (new value is the same as the current). -''' + The property's value is only assigned if the new value differs from the current value. + This prevents onChanged notifications where the value didn't actually change. + Gui::InputField and Gui::QuantitySpinBox widgets are supported - and the property can + be of type Quantity or Float. + If onBeforeChange is specified it is called before a new value is assigned to the property. + Returns True if a new value was assigned, False otherwise (new value is the same as the current). + ''' value = FreeCAD.Units.Quantity(widget.text()).Value attr = PathUtil.getProperty(obj, prop) attrValue = attr.Value if hasattr(attr, 'Value') else attr + + isDiff = False if not PathGeom.isRoughly(attrValue, value): + isDiff = True + else: + if hasattr(obj, 'ExpressionEngine'): + noExpr = True + for (prp, expr) in obj.ExpressionEngine: + if prp == prop: + noExpr = False + PathLog.debug('prop = "expression": {} = "{}"'.format(prp, expr)) + value = FreeCAD.Units.Quantity(obj.evalExpression(expr)).Value + if not PathGeom.isRoughly(attrValue, value): + isDiff = True + break + if noExpr: + widget.setReadOnly(False) + widget.setStyleSheet("color: black") + else: + widget.setReadOnly(True) + widget.setStyleSheet("color: gray") + widget.update() + + if isDiff: PathLog.debug("updateInputField(%s, %s): %.2f -> %.2f" % (obj.Label, prop, attr, value)) if onBeforeChange: onBeforeChange(obj) PathUtil.setProperty(obj, prop, value) return True + return False + class QuantitySpinBox: '''Controller class to interface a Gui::QuantitySpinBox. -The spin box gets bound to a given property and supports update in both directions. - QuatitySpinBox(widget, obj, prop, onBeforeChange=None) - widget ... expected to be reference to a Gui::QuantitySpinBox - obj ... document object - prop ... canonical name of the (sub-) property - onBeforeChange ... an optional callback being executed before the value of the property is changed -''' + The spin box gets bound to a given property and supports update in both directions. + QuatitySpinBox(widget, obj, prop, onBeforeChange=None) + widget ... expected to be reference to a Gui::QuantitySpinBox + obj ... document object + prop ... canonical name of the (sub-) property + onBeforeChange ... an optional callback being executed before the value of the property is changed + ''' def __init__(self, widget, obj, prop, onBeforeChange=None): self.obj = obj self.widget = widget self.prop = prop self.onBeforeChange = onBeforeChange + attr = PathUtil.getProperty(self.obj, self.prop) if attr is not None: if hasattr(attr, 'Value'): @@ -95,7 +122,7 @@ The spin box gets bound to a given property and supports update in both directio if self.valid: return self.widget.property('expression') return '' - + def setMinimum(self, quantity): if self.valid: value = quantity.Value if hasattr(quantity, 'Value') else quantity @@ -103,8 +130,8 @@ The spin box gets bound to a given property and supports update in both directio def updateSpinBox(self, quantity=None): '''updateSpinBox(quantity=None) ... update the display value of the spin box. -If no value is provided the value of the bound property is used. -quantity can be of type Quantity or Float.''' + If no value is provided the value of the bound property is used. + quantity can be of type Quantity or Float.''' if self.valid: if quantity is None: quantity = PathUtil.getProperty(self.obj, self.prop) @@ -116,4 +143,3 @@ quantity can be of type Quantity or Float.''' if self.valid: return updateInputField(self.obj, self.prop, self.widget, self.onBeforeChange) return None - diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 29272c0382..52116e608b 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -35,6 +35,7 @@ else: Processed = False + def Startup(): global Processed # pylint: disable=global-statement if not Processed: @@ -71,12 +72,13 @@ def Startup(): from PathScripts import PathSimpleCopy from PathScripts import PathSimulatorGui from PathScripts import PathStop + # from PathScripts import PathSurfaceGui # Added in initGui.py due to OCL dependency from PathScripts import PathToolController from PathScripts import PathToolControllerGui from PathScripts import PathToolLibraryManager from PathScripts import PathToolLibraryEditor from PathScripts import PathUtilsGui + # from PathScripts import PathWaterlineGui # Added in initGui.py due to OCL dependency Processed = True else: PathLog.debug('Skipping PathGui initialisation') - diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index 9fa300ef71..ed1fcfcbb0 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -99,7 +99,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): '''areaOpShapes(obj) ... return top face''' # Facing is done either against base objects holeShape = None - + if obj.Base: PathLog.debug("obj.Base: {}".format(obj.Base)) faces = [] @@ -147,17 +147,17 @@ class ObjectFace(PathPocketBase.ObjectPocket): # Find the correct shape depending on Boundary shape. PathLog.debug("Boundary Shape: {}".format(obj.BoundaryShape)) bb = planeshape.BoundBox - + # Apply offset for clearing edges offset = 0; if obj.ClearEdges == True: offset = self.radius + 0.1 - + bb.XMin = bb.XMin - offset bb.YMin = bb.YMin - offset bb.XMax = bb.XMax + offset bb.YMax = bb.YMax + offset - + if obj.BoundaryShape == 'Boundbox': bbperim = Part.makeBox(bb.XLength, bb.YLength, 1, FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Vector(0, 0, 1)) env = PathUtils.getEnvelope(partshape=bbperim, depthparams=self.depthparams) @@ -170,7 +170,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): elif obj.BoundaryShape == 'Stock': stock = PathUtils.findParentJob(obj).Stock.Shape env = stock - + if obj.ExcludeRaisedAreas is True and oneBase[1] is True: includedFaces = self.getAllIncludedFaces(oneBase[0], stock, faceZ=minHeight) if len(includedFaces) > 0: @@ -269,7 +269,6 @@ def SetupProperties(): setup.append("BoundaryShape") setup.append("ExcludeRaisedAreas") setup.append("ClearEdges") - return setup @@ -278,5 +277,4 @@ def Create(name, obj=None): if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = ObjectFace(obj, name) - return obj diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index 4a792322d3..9a8a100810 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -157,8 +157,6 @@ class ViewProvider(object): else: return ":/icons/Path-OpActive.svg" - #return self.OpIcon - def getTaskPanelOpPage(self, obj): '''getTaskPanelOpPage(obj) ... use the stored information to instantiate the receiver op's page controller.''' mod = importlib.import_module(self.OpPageModule) @@ -190,6 +188,7 @@ class ViewProvider(object): action.triggered.connect(self.setEdit) menu.addAction(action) + class TaskPanelPage(object): '''Base class for all task panel pages.''' @@ -377,7 +376,7 @@ class TaskPanelPage(object): combo.clear() combo.addItems(options) combo.blockSignals(False) - + if hasattr(obj, 'CoolantMode'): self.selectInComboBox(obj.CoolantMode, combo) @@ -704,10 +703,13 @@ class TaskPanelDepthsPage(TaskPanelPage): def haveStartDepth(self): return PathOp.FeatureDepths & self.features + def haveFinalDepth(self): return PathOp.FeatureDepths & self.features and not PathOp.FeatureNoFinalDepth & self.features + def haveFinishDepth(self): return PathOp.FeatureDepths & self.features and PathOp.FeatureFinishDepth & self.features + def haveStepDown(self): return PathOp.FeatureStepDown & self. features diff --git a/src/Mod/Path/PathScripts/PathOpTools.py b/src/Mod/Path/PathScripts/PathOpTools.py index 2fe95e1873..50a0484c36 100644 --- a/src/Mod/Path/PathScripts/PathOpTools.py +++ b/src/Mod/Path/PathScripts/PathOpTools.py @@ -141,7 +141,7 @@ def orientWire(w, forward=True): PathLog.track('orientWire - ok') return wire -def offsetWire(wire, base, offset, forward): +def offsetWire(wire, base, offset, forward, Side = None): '''offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly. The function tries to avoid most of the pitfalls of Part.makeOffset2D which is possible because all offsetting happens in the XY plane. @@ -195,8 +195,12 @@ def offsetWire(wire, base, offset, forward): if wire.isClosed(): if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset/2, True): PathLog.track('closed - outside') + if Side: + Side[0] = "Outside" return orientWire(owire, forward) PathLog.track('closed - inside') + if Side: + Side[0] = "Inside" try: owire = wire.makeOffset2D(-offset) except Exception: # pylint: disable=broad-except diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/PathScripts/PathPocketShape.py index 0271809bb5..7c98f71d9c 100644 --- a/src/Mod/Path/PathScripts/PathPocketShape.py +++ b/src/Mod/Path/PathScripts/PathPocketShape.py @@ -3,7 +3,6 @@ # *************************************************************************** # * * # * Copyright (c) 2017 sliptonic * -# * Copyright (c) 2020 russ4262 (Russell Johnson) * # * Copyright (c) 2020 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * @@ -42,7 +41,7 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Class and implementation of shape based Pocket operation." -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # PathLog.trackModule(PathLog.thisModule()) @@ -435,7 +434,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): if obj.Base: PathLog.debug('Processing... obj.Base') self.removalshapes = [] # pylint: disable=attribute-defined-outside-init - # ---------------------------------------------------------------------- + if obj.EnableRotation == 'Off': stock = PathUtils.findParentJob(obj).Stock for (base, subList) in obj.Base: @@ -450,11 +449,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket): (isLoop, norm, surf) = self.checkForFacesLoop(base, subsList) if isLoop is True: - PathLog.info("Common Surface.Axis or normalAt() value found for loop faces.") + PathLog.debug("Common Surface.Axis or normalAt() value found for loop faces.") rtn = False subCount += 1 (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable - PathLog.info("angle: {}; axis: {}".format(angle, axis)) + PathLog.debug("angle: {}; axis: {}".format(angle, axis)) if rtn is True: faceNums = "" @@ -471,15 +470,17 @@ class ObjectPocket(PathPocketBase.ObjectPocket): rtn = False PathLog.warning(translate("PathPocketShape", "Face appears to NOT be horizontal AFTER rotation applied.")) break + if rtn is False: + PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.")) if obj.InverseAngle is False: if obj.AttemptInverseAngle is True: - PathLog.debug("Applying the inverse angle.") (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) else: - PathLog.warning(translate("Path", "Consider toggling the InverseAngle property and recomputing the operation.")) + msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") + PathLog.warning(msg) - if angle < -180.0: + if angle < 0.0: angle += 360.0 tup = clnBase, subsList, angle, axis, clnStock @@ -518,6 +519,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): (norm, surf) = self.getFaceNormAndSurf(face) (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + PathLog.debug("initial {}".format(praInfo)) if rtn is True: faceNum = sub.replace('Face', '') @@ -525,20 +527,31 @@ class ObjectPocket(PathPocketBase.ObjectPocket): # Verify faces are correctly oriented - InverseAngle might be necessary faceIA = clnBase.Shape.getElement(sub) (norm, surf) = self.getFaceNormAndSurf(faceIA) - (rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + PathLog.debug("follow-up {}".format(praInfo2)) + + if abs(praAngle) == 180.0: + rtn = False + if self.isFaceUp(clnBase, faceIA) is False: + PathLog.debug('isFaceUp is False') + angle -= 180.0 if rtn is True: - PathLog.debug("Face not aligned after initial rotation.") + PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.")) if obj.InverseAngle is False: if obj.AttemptInverseAngle is True: - PathLog.debug("Applying the inverse angle.") (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) else: - PathLog.warning(translate("Path", "Consider toggling the InverseAngle property and recomputing the operation.")) + msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") + PathLog.warning(msg) + + if self.isFaceUp(clnBase, faceIA) is False: + PathLog.debug('isFaceUp is False') + angle += 180.0 else: PathLog.debug("Face appears to be oriented correctly.") - if angle < -180.0: + if angle < 0.0: angle += 360.0 tup = clnBase, [sub], angle, axis, clnStock @@ -650,8 +663,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket): if shpZMin > obj.FinalDepth.Value: afD = shpZMin if sD <= afD: - PathLog.error('Start Depth is lower than face depth.') sD = afD + 1.0 + msg = translate('PathPocketShape', 'Start Depth is lower than face depth. Setting to ') + PathLog.warning(msg + ' {} mm.'.format(sD)) else: face.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - shpZMin)) diff --git a/src/Mod/Path/PathScripts/PathProbe.py b/src/Mod/Path/PathScripts/PathProbe.py index c5e8b34f81..c540ac7b72 100644 --- a/src/Mod/Path/PathScripts/PathProbe.py +++ b/src/Mod/Path/PathScripts/PathProbe.py @@ -44,7 +44,7 @@ else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# Qt tanslation handling +# Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) diff --git a/src/Mod/Path/PathScripts/PathProbeGui.py b/src/Mod/Path/PathScripts/PathProbeGui.py index 9da563fa48..3a3657acc4 100644 --- a/src/Mod/Path/PathScripts/PathProbeGui.py +++ b/src/Mod/Path/PathScripts/PathProbeGui.py @@ -36,7 +36,7 @@ __url__ = "http://www.freecadweb.org" __doc__ = "Probing operation page controller and command implementation." -# Qt tanslation handling +# Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py index 5fce9b0774..e01ee25d4c 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ b/src/Mod/Path/PathScripts/PathProfileEdges.py @@ -112,18 +112,23 @@ class ObjectProfile(PathProfileBase.ObjectProfile): else: PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.')) else: - cutWireObjs = False - (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) - cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire) - if cutShp is not False: - cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp) - - if cutWireObjs is not False: - for cW in cutWireObjs: - shapes.append((cW, False)) - self.profileEdgesIsOpen = True + if self.JOB.GeometryTolerance.Value == 0.0: + msg = self.JOB.Label + '.GeometryTolerance = 0.0.' + msg += translate('PathProfileEdges', 'Please set to an acceptable value greater than zero.') + PathLog.error(msg) else: - PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.')) + cutWireObjs = False + (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) + cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire) + if cutShp is not False: + cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp) + + if cutWireObjs is not False: + for cW in cutWireObjs: + shapes.append((cW, False)) + self.profileEdgesIsOpen = True + else: + PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.')) # Delete the temporary objects if PathLog.getLevel(PathLog.thisModule()) != 4: @@ -134,7 +139,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile): if FreeCAD.GuiUp: import FreeCADGui FreeCADGui.ActiveDocument.getObject(tmpGrpNm).Visibility = False - + return shapes def _flattenWire(self, obj, wire, trgtDep): @@ -179,13 +184,13 @@ class ObjectProfile(PathProfileBase.ObjectProfile): return (OW, FW) + # Open-edges methods def _getCutAreaCrossSection(self, obj, base, origWire, flatWireObj): PathLog.debug('_getCutAreaCrossSection()') tmpGrp = self.tmpGrp FCAD = FreeCAD.ActiveDocument tolerance = self.JOB.GeometryTolerance.Value - # toolDiam = float(obj.ToolController.Tool.Diameter) - toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathprofileBase modules + toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathProfileBase modules minBfr = toolDiam * 1.25 bbBfr = (self.ofstRadius * 2) * 1.25 if bbBfr < minBfr: @@ -243,34 +248,33 @@ class ObjectProfile(PathProfileBase.ObjectProfile): # Cut model(selected edges) from extended edges boundbox cutArea = extBndboxEXT.Shape.cut(base.Shape) - CA = FCAD.addObject('Part::Feature', 'tmpBndboxCutByBase') - CA.Shape = cutArea - CA.purgeTouched() - tmpGrp.addObject(CA) # Get top and bottom faces of cut area (CA), and combine faces when necessary topFc = list() botFc = list() - bbZMax = CA.Shape.BoundBox.ZMax - bbZMin = CA.Shape.BoundBox.ZMin - for f in range(0, len(CA.Shape.Faces)): - Fc = CA.Shape.Faces[f] - if abs(Fc.BoundBox.ZMax - bbZMax) < tolerance and abs(Fc.BoundBox.ZMin - bbZMax) < tolerance: + bbZMax = cutArea.BoundBox.ZMax + bbZMin = cutArea.BoundBox.ZMin + for f in range(0, len(cutArea.Faces)): + FcBB = cutArea.Faces[f].BoundBox + if abs(FcBB.ZMax - bbZMax) < tolerance and abs(FcBB.ZMin - bbZMax) < tolerance: topFc.append(f) - if abs(Fc.BoundBox.ZMax - bbZMin) < tolerance and abs(Fc.BoundBox.ZMin - bbZMin) < tolerance: + if abs(FcBB.ZMax - bbZMin) < tolerance and abs(FcBB.ZMin - bbZMin) < tolerance: botFc.append(f) - topComp = Part.makeCompound([CA.Shape.Faces[f] for f in topFc]) + if len(topFc) == 0: + PathLog.error('Failed to identify top faces of cut area.') + return False + topComp = Part.makeCompound([cutArea.Faces[f] for f in topFc]) topComp.translate(FreeCAD.Vector(0, 0, fdv - topComp.BoundBox.ZMin)) # Translate face to final depth if len(botFc) > 1: PathLog.debug('len(botFc) > 1') bndboxFace = Part.Face(extBndbox.Shape.Wires[0]) tmpFace = Part.Face(extBndbox.Shape.Wires[0]) for f in botFc: - Q = tmpFace.cut(CA.Shape.Faces[f]) + Q = tmpFace.cut(cutArea.Faces[f]) tmpFace = Q botComp = bndboxFace.cut(tmpFace) else: - botComp = Part.makeCompound([CA.Shape.Faces[f] for f in botFc]) + botComp = Part.makeCompound([cutArea.Faces[f] for f in botFc]) # Part.makeCompound([CA.Shape.Faces[f] for f in botFc]) botComp.translate(FreeCAD.Vector(0, 0, fdv - botComp.BoundBox.ZMin)) # Translate face to final depth # Convert compound shapes to FC objects for use in multicommon operation diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py index 506d5a1d00..6f2233b215 100644 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ b/src/Mod/Path/PathScripts/PathProfileFaces.py @@ -3,7 +3,6 @@ # *************************************************************************** # * * # * Copyright (c) 2014 Yorik van Havre * -# * Copyright (c) 2020 russ4262 (Russell Johnson) * # * Copyright (c) 2020 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * @@ -43,6 +42,7 @@ __doc__ = "Path Profile operation based on faces." PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -132,22 +132,42 @@ class ObjectProfile(PathProfileBase.ObjectProfile): rtn = False (norm, surf) = self.getFaceNormAndSurf(shape) (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + PathLog.debug("initial faceRotationAnalysis: {}".format(praInfo)) if rtn is True: (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, subCount) # Verify faces are correctly oriented - InverseAngle might be necessary faceIA = getattr(clnBase.Shape, sub) (norm, surf) = self.getFaceNormAndSurf(faceIA) - (rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + PathLog.debug("follow-up faceRotationAnalysis: {}".format(praInfo2)) + + if abs(praAngle) == 180.0: + rtn = False + if self.isFaceUp(clnBase, faceIA) is False: + PathLog.debug('isFaceUp 1 is False') + angle -= 180.0 + if rtn is True: - PathLog.error(translate("Path", "Face appears misaligned after initial rotation.")) - if obj.AttemptInverseAngle is True and obj.InverseAngle is False: - (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) + PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.")) + if obj.InverseAngle is False: + if obj.AttemptInverseAngle is True: + (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) + else: + msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") + PathLog.warning(msg) + + if self.isFaceUp(clnBase, faceIA) is False: + PathLog.debug('isFaceUp 2 is False') + angle += 180.0 else: - msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") - PathLog.error(msg) + PathLog.debug(' isFaceUp') + else: PathLog.debug("Face appears to be oriented correctly.") + if angle < 0.0: + angle += 360.0 + tup = clnBase, sub, tag, angle, axis, clnStock else: if self.warnDisabledAxis(obj, axis) is False: @@ -157,21 +177,21 @@ class ObjectProfile(PathProfileBase.ObjectProfile): tag = base.Name + '_' + axis + str(angle).replace('.', '_') stock = PathUtils.findParentJob(obj).Stock tup = base, sub, tag, angle, axis, stock - + allTuples.append(tup) - + if subCount > 1: msg = translate('Path', "Multiple faces in Base Geometry.") + " " msg += translate('Path', "Depth settings will be applied to all faces.") PathLog.warning(msg) - + (Tags, Grps) = self.sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList) subList = [] for o in range(0, len(Tags)): subList = [] for (base, sub, tag, angle, axis, stock) in Grps[o]: subList.append(sub) - + pair = base, subList, angle, axis, stock baseSubsTuples.append(pair) # Efor @@ -196,7 +216,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile): if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face for wire in shape.Wires[1:]: holes.append((base.Shape, wire)) - + # Add face depth to list faceDepths.append(shape.BoundBox.ZMin) else: @@ -205,13 +225,12 @@ class ObjectProfile(PathProfileBase.ObjectProfile): PathLog.error(msg) FreeCAD.Console.PrintWarning(msg) - # Set initial Start and Final Depths and recalculate depthparams finDep = obj.FinalDepth.Value strDep = obj.StartDepth.Value if strDep > stock.Shape.BoundBox.ZMax: strDep = stock.Shape.BoundBox.ZMax - + startDepths.append(strDep) self.depthparams = self._customDepthParams(obj, strDep, finDep) @@ -230,31 +249,34 @@ class ObjectProfile(PathProfileBase.ObjectProfile): if obj.processPerimeter: if obj.HandleMultipleFeatures == 'Collectively': custDepthparams = self.depthparams + if obj.LimitDepthToFace is True and obj.EnableRotation != 'Off': if profileshape.BoundBox.ZMin > obj.FinalDepth.Value: finDep = profileshape.BoundBox.ZMin - custDepthparams = self._customDepthParams(obj, strDep, finDep - 0.5) # only an envelope + envDepthparams = self._customDepthParams(obj, strDep + 0.5, finDep) # only an envelope try: - env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=custDepthparams) + # env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=envDepthparams) + env = PathUtils.getEnvelope(profileshape, depthparams=envDepthparams) except Exception: # pylint: disable=broad-except # PathUtils.getEnvelope() failed to return an object. PathLog.error(translate('Path', 'Unable to create path for face(s).')) else: tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep shapes.append(tup) - + elif obj.HandleMultipleFeatures == 'Individually': for shape in faces: - profShape = Part.makeCompound([shape]) + # profShape = Part.makeCompound([shape]) finalDep = obj.FinalDepth.Value custDepthparams = self.depthparams if obj.Side == 'Inside': if finalDep < shape.BoundBox.ZMin: # Recalculate depthparams finalDep = shape.BoundBox.ZMin - custDepthparams = self._customDepthParams(obj, strDep, finalDep - 0.5) - - env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams) + custDepthparams = self._customDepthParams(obj, strDep + 0.5, finalDep) + + # env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams) + env = PathUtils.getEnvelope(shape, depthparams=custDepthparams) tup = env, False, 'pathProfileFaces', angle, axis, strDep, finalDep shapes.append(tup) @@ -262,11 +284,11 @@ class ObjectProfile(PathProfileBase.ObjectProfile): startDepth = max(startDepths) if obj.StartDepth.Value > startDepth: obj.StartDepth.Value = startDepth - + else: # Try to build targets from the job base if 1 == len(self.model): if hasattr(self.model[0], "Proxy"): - PathLog.info("hasattr() Proxy") + PathLog.debug("hasattr() Proxy") if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet if obj.processCircles or obj.processHoles: for shape in self.model[0].Proxy.getHoles(self.model[0], transform=True): @@ -302,7 +324,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile): obj.InverseAngle = False obj.AttemptInverseAngle = True obj.LimitDepthToFace = True - obj.HandleMultipleFeatures = 'Collectively' + obj.HandleMultipleFeatures = 'Individually' def SetupProperties(): @@ -321,6 +343,5 @@ def Create(name, obj=None): '''Create(name) ... Creates and returns a Profile based on faces operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectProfile(obj, name) return obj diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index 386ff1c29c..0cccde13b3 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -30,12 +30,14 @@ import PathScripts.PathUtils as PathUtils import math PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) + class PathBaseGate(object): # pylint: disable=no-init pass + class EGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument return sub and sub[0:4] == 'Edge' @@ -66,6 +68,7 @@ class ENGRAVEGate(PathBaseGate): return False + class CHAMFERGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument try: @@ -94,7 +97,7 @@ class DRILLGate(PathBaseGate): if hasattr(obj, "Shape") and sub: shape = obj.Shape subobj = shape.getElement(sub) - return PathUtils.isDrillable(shape, subobj, includePartials = True) + return PathUtils.isDrillable(shape, subobj, includePartials=True) else: return False @@ -159,6 +162,7 @@ class POCKETGate(PathBaseGate): return pocketable + class ADAPTIVEGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument @@ -170,6 +174,7 @@ class ADAPTIVEGate(PathBaseGate): return adaptive + class CONTOURGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument pass @@ -182,34 +187,42 @@ def contourselect(): FreeCADGui.Selection.addSelectionGate(CONTOURGate()) FreeCAD.Console.PrintWarning("Contour Select Mode\n") + def eselect(): FreeCADGui.Selection.addSelectionGate(EGate()) FreeCAD.Console.PrintWarning("Edge Select Mode\n") + def drillselect(): FreeCADGui.Selection.addSelectionGate(DRILLGate()) FreeCAD.Console.PrintWarning("Drilling Select Mode\n") + def engraveselect(): FreeCADGui.Selection.addSelectionGate(ENGRAVEGate()) FreeCAD.Console.PrintWarning("Engraving Select Mode\n") + def chamferselect(): FreeCADGui.Selection.addSelectionGate(CHAMFERGate()) FreeCAD.Console.PrintWarning("Deburr Select Mode\n") + def profileselect(): FreeCADGui.Selection.addSelectionGate(PROFILEGate()) FreeCAD.Console.PrintWarning("Profiling Select Mode\n") + def pocketselect(): FreeCADGui.Selection.addSelectionGate(POCKETGate()) FreeCAD.Console.PrintWarning("Pocketing Select Mode\n") + def adaptiveselect(): FreeCADGui.Selection.addSelectionGate(ADAPTIVEGate()) FreeCAD.Console.PrintWarning("Adaptive Select Mode\n") + def surfaceselect(): if(MESHGate() is True or PROFILEGate() is True): FreeCADGui.Selection.addSelectionGate(True) @@ -237,10 +250,12 @@ def select(op): opsel['Profile Edges'] = eselect opsel['Profile Faces'] = profileselect opsel['Surface'] = surfaceselect + opsel['Waterline'] = surfaceselect opsel['Adaptive'] = adaptiveselect opsel['Probe'] = probeselect return opsel[op] + def clear(): FreeCADGui.Selection.removeSelectionGate() FreeCAD.Console.PrintWarning("Free Select\n") diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 0c83884bc7..9e63b6f359 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -82,138 +82,171 @@ class ObjectSurface(PathOp.ObjectOp): return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces def initOperation(self, obj): - '''initPocketOp(obj) ... create facing specific properties''' - obj.addProperty("App::PropertyEnumeration", "Algorithm", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "The library to use to generate the path")) - obj.addProperty("App::PropertyEnumeration", "BoundBox", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")) - obj.addProperty("App::PropertyEnumeration", "DropCutterDir", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction along which dropcutter lines are created")) - obj.addProperty("App::PropertyVectorDistance", "DropCutterExtraOffset", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "Additional offset to the selected bounding box")) - obj.addProperty("App::PropertyEnumeration", "LayerMode", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")) - obj.addProperty("App::PropertyEnumeration", "ScanType", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.")) - - obj.addProperty("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")) - obj.addProperty("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")) - - obj.addProperty("App::PropertyFloat", "CutterTilt", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) - obj.addProperty("App::PropertyEnumeration", "RotationAxis", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "The model will be rotated around this axis.")) - obj.addProperty("App::PropertyFloat", "StartIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Start index(angle) for rotational scan")) - obj.addProperty("App::PropertyFloat", "StopIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) - - obj.addProperty("App::PropertyInteger", "AvoidLastX_Faces", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")) - obj.addProperty("App::PropertyBool", "AvoidLastX_InternalFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")) - obj.addProperty("App::PropertyDistance", "BoundaryAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")) - obj.addProperty("App::PropertyBool", "BoundaryEnforcement", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")) - obj.addProperty("App::PropertyDistance", "DepthOffset", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")) - obj.addProperty("App::PropertyEnumeration", "HandleMultipleFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")) - obj.addProperty("App::PropertyDistance", "InternalFeaturesAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")) - obj.addProperty("App::PropertyBool", "InternalFeaturesCut", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")) - obj.addProperty("App::PropertyEnumeration", "ProfileEdges", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")) - obj.addProperty("App::PropertyDistance", "SampleInterval", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")) - obj.addProperty("App::PropertyPercent", "StepOver", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")) - - obj.addProperty("App::PropertyVectorDistance", "CircularCenterCustom", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) - obj.addProperty("App::PropertyEnumeration", "CircularCenterAt", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "Choose what point to start the circular pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")) - obj.addProperty("App::PropertyEnumeration", "CutMode", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")) - obj.addProperty("App::PropertyEnumeration", "CutPattern", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")) - obj.addProperty("App::PropertyFloat", "CutPatternAngle", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")) - obj.addProperty("App::PropertyBool", "CutPatternReversed", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")) - - obj.addProperty("App::PropertyBool", "OptimizeLinearPaths", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")) - obj.addProperty("App::PropertyBool", "OptimizeStepOverTransitions", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")) - obj.addProperty("App::PropertyBool", "CircularUseG2G3", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")) - obj.addProperty("App::PropertyDistance", "GapThreshold", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")) - obj.addProperty("App::PropertyString", "GapSizes", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")) - - obj.addProperty("App::PropertyBool", "IgnoreWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore areas that proceed below specified depth.")) - obj.addProperty("App::PropertyFloat", "IgnoreWasteDepth", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Depth used to identify waste areas to ignore.")) - obj.addProperty("App::PropertyBool", "ReleaseFromWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Cut through waste to depth at model edge, releasing the model.")) - - obj.addProperty("App::PropertyVectorDistance", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) - obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make True, if specifying a Start Point")) + '''initPocketOp(obj) ... create operation specific properties''' + self.initOpProperties(obj) # For debugging - obj.addProperty('App::PropertyString', 'AreaParams', 'Debugging') - obj.setEditorMode('AreaParams', 2) # hide - obj.addProperty("App::PropertyBool", "ShowTempObjects", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")) if PathLog.getLevel(PathLog.thisModule()) != 4: obj.setEditorMode('ShowTempObjects', 2) # hide - obj.Algorithm = ['OCL Dropcutter', 'OCL Waterline'] - obj.BoundBox = ['BaseBoundBox', 'Stock'] - obj.CircularCenterAt = ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'] - obj.CutMode = ['Conventional', 'Climb'] - obj.CutPattern = ['Line', 'ZigZag', 'Circular', 'CircularZigZag'] # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] - obj.DropCutterDir = ['X', 'Y'] - obj.HandleMultipleFeatures = ['Collectively', 'Individually'] - obj.LayerMode = ['Single-pass', 'Multi-pass'] - obj.ProfileEdges = ['None', 'Only', 'First', 'Last'] - obj.RotationAxis = ['X', 'Y'] - obj.ScanType = ['Planar', 'Rotational'] - if not hasattr(obj, 'DoNotSetDefaultValues'): self.setEditorProperties(obj) + def initOpProperties(self, obj): + '''initOpProperties(obj) ... create operation specific properties''' + + PROPS = [ + ("App::PropertyBool", "ShowTempObjects", "Debug", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Show the temporary path construction objects when module is in DEBUG mode.")), + + ("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate mesh. Smaller values increase processing time a lot.")), + ("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate mesh. Smaller values do not increase processing time much.")), + + ("App::PropertyFloat", "CutterTilt", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")), + ("App::PropertyEnumeration", "DropCutterDir", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction along which dropcutter lines are created")), + ("App::PropertyVectorDistance", "DropCutterExtraOffset", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Additional offset to the selected bounding box")), + ("App::PropertyEnumeration", "RotationAxis", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The model will be rotated around this axis.")), + ("App::PropertyFloat", "StartIndex", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Start index(angle) for rotational scan")), + ("App::PropertyFloat", "StopIndex", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")), + + ("App::PropertyEnumeration", "ScanType", "Surface", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.")), + + ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")), + ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")), + ("App::PropertyDistance", "BoundaryAdjustment", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")), + ("App::PropertyBool", "BoundaryEnforcement", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")), + ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")), + ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")), + ("App::PropertyBool", "InternalFeaturesCut", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")), + + ("App::PropertyEnumeration", "BoundBox", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the overall boundary for the operation. ")), + ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the start point for circular cut patterns.")), + ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose location of the center point for starting the circular pattern.")), + ("App::PropertyEnumeration", "CutMode", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)")), + ("App::PropertyEnumeration", "CutPattern", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the geometric clearing pattern to use for the operation.")), + ("App::PropertyFloat", "CutPatternAngle", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The yaw angle used for certain clearing patterns")), + ("App::PropertyBool", "CutPatternReversed", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Reverse the cut order of the stepover paths. For circular cut patterns, begin at the outside and work toward the center.")), + ("App::PropertyDistance", "DepthOffset", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the Z-axis depth offset from the target surface.")), + ("App::PropertyEnumeration", "LayerMode", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Complete the operation in a single pass at depth, or mulitiple passes to final depth.")), + ("App::PropertyEnumeration", "ProfileEdges", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")), + ("App::PropertyDistance", "SampleInterval", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the sampling resolution. Smaller values quickly increase processing time.")), + ("App::PropertyPercent", "StepOver", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the stepover percentage, based on the tool's diameter.")), + + ("App::PropertyBool", "OptimizeLinearPaths", "Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")), + ("App::PropertyBool", "OptimizeStepOverTransitions", "Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")), + ("App::PropertyBool", "CircularUseG2G3", "Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")), + ("App::PropertyDistance", "GapThreshold", "Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")), + ("App::PropertyString", "GapSizes", "Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")), + + ("App::PropertyVectorDistance", "StartPoint", "Start Point", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The custom start point for the path of this operation")), + ("App::PropertyBool", "UseStartPoint", "Start Point", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point")) + ] + + missing = list() + for (prtyp, nm, grp, tt) in PROPS: + if not hasattr(obj, nm): + obj.addProperty(prtyp, nm, grp, tt) + missing.append(nm) + + # Set enumeration lists for enumeration properties + if len(missing) > 0: + ENUMS = self._propertyEnumerations() + for n in ENUMS: + if n in missing: + cmdStr = 'obj.{}={}'.format(n, ENUMS[n]) + exec(cmdStr) + self.addedAllProperties = True + def _propertyEnumerations(self): + # Enumeration lists for App::PropertyEnumeration properties + return { + 'BoundBox': ['BaseBoundBox', 'Stock'], + 'CircularCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'], + 'CutMode': ['Conventional', 'Climb'], + 'CutPattern': ['Line', 'Circular', 'CircularZigZag', 'ZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] + 'DropCutterDir': ['X', 'Y'], + 'HandleMultipleFeatures': ['Collectively', 'Individually'], + 'LayerMode': ['Single-pass', 'Multi-pass'], + 'ProfileEdges': ['None', 'Only', 'First', 'Last'], + 'RotationAxis': ['X', 'Y'], + 'ScanType': ['Planar', 'Rotational'] + } + def setEditorProperties(self, obj): # Used to hide inputs in properties list - if obj.Algorithm == 'OCL Dropcutter': - obj.setEditorMode('CutPattern', 0) - obj.setEditorMode('HandleMultipleFeatures', 0) - obj.setEditorMode('CircularCenterAt', 0) - obj.setEditorMode('CircularCenterCustom', 0) - obj.setEditorMode('CutPatternAngle', 0) - # obj.setEditorMode('BoundaryEnforcement', 0) - - if obj.ScanType == 'Planar': - obj.setEditorMode('DropCutterDir', 2) - obj.setEditorMode('DropCutterExtraOffset', 2) - obj.setEditorMode('RotationAxis', 2) # 2=hidden - obj.setEditorMode('StartIndex', 2) - obj.setEditorMode('StopIndex', 2) - obj.setEditorMode('CutterTilt', 2) - if obj.CutPattern == 'Circular' or obj.CutPattern == 'CircularZigZag': - obj.setEditorMode('CutPatternAngle', 2) - else: # if obj.CutPattern == 'Line' or obj.CutPattern == 'ZigZag': - obj.setEditorMode('CircularCenterAt', 2) - obj.setEditorMode('CircularCenterCustom', 2) - elif obj.ScanType == 'Rotational': - obj.setEditorMode('DropCutterDir', 0) - obj.setEditorMode('DropCutterExtraOffset', 0) - obj.setEditorMode('RotationAxis', 0) # 0=show & editable - obj.setEditorMode('StartIndex', 0) - obj.setEditorMode('StopIndex', 0) - obj.setEditorMode('CutterTilt', 0) - - elif obj.Algorithm == 'OCL Waterline': - obj.setEditorMode('DropCutterExtraOffset', 2) - obj.setEditorMode('DropCutterDir', 2) - obj.setEditorMode('HandleMultipleFeatures', 2) - obj.setEditorMode('CutPattern', 2) - obj.setEditorMode('CutPatternAngle', 2) - # obj.setEditorMode('BoundaryEnforcement', 2) - - # Disable IgnoreWaste feature - obj.setEditorMode('IgnoreWaste', 2) - obj.setEditorMode('IgnoreWasteDepth', 2) - obj.setEditorMode('ReleaseFromWaste', 2) + mode = 2 # 2=hidden + if obj.ScanType == 'Planar': + show = 0 + hide = 2 + # if obj.CutPattern in ['Line', 'ZigZag']: + if obj.CutPattern in ['Circular', 'CircularZigZag']: + show = 2 # hide + hide = 0 # show + obj.setEditorMode('CutPatternAngle', show) + obj.setEditorMode('CircularCenterAt', hide) + obj.setEditorMode('CircularCenterCustom', hide) + elif obj.ScanType == 'Rotational': + mode = 0 # show and editable + obj.setEditorMode('DropCutterDir', mode) + obj.setEditorMode('DropCutterExtraOffset', mode) + obj.setEditorMode('RotationAxis', mode) + obj.setEditorMode('StartIndex', mode) + obj.setEditorMode('StopIndex', mode) + obj.setEditorMode('CutterTilt', mode) def onChanged(self, obj, prop): if hasattr(self, 'addedAllProperties'): if self.addedAllProperties is True: - if prop == 'Algorithm': - self.setEditorProperties(obj) if prop == 'ScanType': self.setEditorProperties(obj) if prop == 'CutPattern': self.setEditorProperties(obj) def opOnDocumentRestored(self, obj): + self.initOpProperties(obj) + if PathLog.getLevel(PathLog.thisModule()) != 4: obj.setEditorMode('ShowTempObjects', 2) # hide else: obj.setEditorMode('ShowTempObjects', 0) # show - self.addedAllProperties = True + self.setEditorProperties(obj) def opSetDefaultValues(self, obj, job): @@ -221,8 +254,6 @@ class ObjectSurface(PathOp.ObjectOp): job = PathUtils.findParentJob(obj) obj.OptimizeLinearPaths = True - obj.IgnoreWaste = False - obj.ReleaseFromWaste = False obj.InternalFeaturesCut = True obj.OptimizeStepOverTransitions = False obj.CircularUseG2G3 = False @@ -241,7 +272,6 @@ class ObjectSurface(PathOp.ObjectOp): obj.CutPattern = 'Line' obj.HandleMultipleFeatures = 'Collectively' # 'Individually' obj.CircularCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom' - obj.AreaParams = '' obj.GapSizes = 'No gaps identified.' obj.StepOver = 100 obj.CutPatternAngle = 0.0 @@ -303,8 +333,8 @@ class ObjectSurface(PathOp.ObjectOp): obj.CutterTilt = 90.0 # Limit sample interval - if obj.SampleInterval.Value < 0.001: - obj.SampleInterval.Value = 0.001 + if obj.SampleInterval.Value < 0.0001: + obj.SampleInterval.Value = 0.0001 PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.')) if obj.SampleInterval.Value > 25.4: obj.SampleInterval.Value = 25.4 @@ -366,9 +396,6 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.info('\nBegin 3D Surface operation...') startTime = time.time() - # Disable(ignore) ReleaseFromWaste option(input) - obj.ReleaseFromWaste = False - # Identify parent Job JOB = PathUtils.findParentJob(obj) if JOB is None: @@ -389,12 +416,12 @@ class ObjectSurface(PathOp.ObjectOp): # ... and move cutter to clearance height and startpoint output = '' if obj.Comment != '': - output += '(' + str(obj.Comment) + ')\n' - output += '(' + obj.Label + ')\n' - output += '(Tool type: ' + str(obj.ToolController.Tool.ToolType) + ')\n' - output += '(Compensated Tool Path. Diameter: ' + str(obj.ToolController.Tool.Diameter) + ')\n' - output += '(Sample interval: ' + str(obj.SampleInterval.Value) + ')\n' - output += '(Step over %: ' + str(obj.StepOver) + ')\n' + self.commandlist.append(Path.Command('N ({})'.format(str(obj.Comment)), {})) + self.commandlist.append(Path.Command('N ({})'.format(obj.Label), {})) + self.commandlist.append(Path.Command('N (Tool type: {})'.format(str(obj.ToolController.Tool.ToolType)), {})) + self.commandlist.append(Path.Command('N (Compensated Tool Path. Diameter: {})'.format(str(obj.ToolController.Tool.Diameter)), {})) + self.commandlist.append(Path.Command('N (Sample interval: {})'.format(str(obj.SampleInterval.Value)), {})) + self.commandlist.append(Path.Command('N (Step over %: {})'.format(str(obj.StepOver)), {})) self.commandlist.append(Path.Command('N ({})'.format(output), {})) self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) if obj.UseStartPoint is True: @@ -480,16 +507,6 @@ class ObjectSurface(PathOp.ObjectOp): # ###### MAIN COMMANDS FOR OPERATION ###### - # If algorithm is `Waterline`, force certain property values - # Save initial value for restoration later. - if obj.Algorithm == 'OCL Waterline': - preCP = obj.CutPattern - preCPA = obj.CutPatternAngle - preRB = obj.BoundaryEnforcement - obj.CutPattern = 'Line' - obj.CutPatternAngle = 0.0 - obj.BoundaryEnforcement = False - # Begin processing obj.Base data and creating GCode # Process selected faces, if available pPM = self._preProcessModel(JOB, obj) @@ -513,19 +530,13 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.info('Working on Model.Group[{}]: {}'.format(m, Mdl.Label)) # make stock-model-voidShapes STL model for avoidance detection on transitions self._makeSafeSTL(JOB, obj, m, FACES[m], VOIDS[m]) - time.sleep(0.2) + #time.sleep(0.2) # Process model/faces - OCL objects must be ready CMDS.extend(self._processCutAreas(JOB, obj, m, FACES[m], VOIDS[m])) # Save gcode produced self.commandlist.extend(CMDS) - # If algorithm is `Waterline`, restore initial property values - if obj.Algorithm == 'OCL Waterline': - obj.CutPattern = preCP - obj.CutPatternAngle = preCPA - obj.BoundaryEnforcement = preRB - # ###### CLOSING COMMANDS FOR OPERATION ###### # Delete temporary objects @@ -750,7 +761,7 @@ class ObjectSurface(PathOp.ObjectOp): # Handle profile edges request if cont is True and obj.ProfileEdges != 'None': ofstVal = self._calculateOffsetValue(obj, isHole) - psOfst = self._extractFaceOffset(obj, cfsL, ofstVal) + psOfst = self._extractFaceOffset(cfsL, ofstVal) if psOfst is not False: mPS = [psOfst] if obj.ProfileEdges == 'Only': @@ -760,7 +771,7 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error(' -Failed to create profile geometry for selected faces.') cont = False - if cont is True: + if cont: if self.showDebugObjects is True: T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCollectiveShape') T.Shape = cfsL @@ -768,12 +779,12 @@ class ObjectSurface(PathOp.ObjectOp): self.tempGroup.addObject(T) ofstVal = self._calculateOffsetValue(obj, isHole) - faceOfstShp = self._extractFaceOffset(obj, cfsL, ofstVal) + faceOfstShp = self._extractFaceOffset(cfsL, ofstVal) if faceOfstShp is False: PathLog.error(' -Failed to create offset face.') cont = False - if cont is True: + if cont: lenIfL = len(ifL) if obj.InternalFeaturesCut is False: if lenIfL == 0: @@ -789,7 +800,7 @@ class ObjectSurface(PathOp.ObjectOp): C.purgeTouched() self.tempGroup.addObject(C) ofstVal = self._calculateOffsetValue(obj, isHole=True) - intOfstShp = self._extractFaceOffset(obj, casL, ofstVal) + intOfstShp = self._extractFaceOffset(casL, ofstVal) mIFS.append(intOfstShp) # faceOfstShp = faceOfstShp.cut(intOfstShp) @@ -825,7 +836,7 @@ class ObjectSurface(PathOp.ObjectOp): if obj.ProfileEdges != 'None': ofstVal = self._calculateOffsetValue(obj, isHole) - psOfst = self._extractFaceOffset(obj, outerFace, ofstVal) + psOfst = self._extractFaceOffset(outerFace, ofstVal) if psOfst is not False: if mPS is False: mPS = list() @@ -839,9 +850,9 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error(' -Failed to create profile geometry for Face{}.'.format(fNum)) cont = False - if cont is True: + if cont: ofstVal = self._calculateOffsetValue(obj, isHole) - faceOfstShp = self._extractFaceOffset(obj, outerFace, ofstVal) + faceOfstShp = self._extractFaceOffset(outerFace, ofstVal) lenIfl = len(ifL) if obj.InternalFeaturesCut is False and lenIfl > 0: @@ -851,7 +862,7 @@ class ObjectSurface(PathOp.ObjectOp): casL = Part.makeCompound(ifL) ofstVal = self._calculateOffsetValue(obj, isHole=True) - intOfstShp = self._extractFaceOffset(obj, casL, ofstVal) + intOfstShp = self._extractFaceOffset(casL, ofstVal) mIFS.append(intOfstShp) # faceOfstShp = faceOfstShp.cut(intOfstShp) @@ -907,7 +918,7 @@ class ObjectSurface(PathOp.ObjectOp): P.purgeTouched() self.tempGroup.addObject(P) - if cont is True: + if cont: if self.showDebugObjects is True: PathLog.debug('*** tmpVoidCompound') P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidCompound') @@ -916,12 +927,12 @@ class ObjectSurface(PathOp.ObjectOp): P.purgeTouched() self.tempGroup.addObject(P) ofstVal = self._calculateOffsetValue(obj, isHole, isVoid=True) - avdOfstShp = self._extractFaceOffset(obj, avoid, ofstVal) + avdOfstShp = self._extractFaceOffset(avoid, ofstVal) if avdOfstShp is False: PathLog.error('Failed to create collective offset avoid face.') cont = False - if cont is True: + if cont: avdShp = avdOfstShp if obj.AvoidLastX_InternalFeatures is False and len(intFEAT) > 0: @@ -930,7 +941,7 @@ class ObjectSurface(PathOp.ObjectOp): else: ifc = intFEAT[0] ofstVal = self._calculateOffsetValue(obj, isHole=True) - ifOfstShp = self._extractFaceOffset(obj, ifc, ofstVal) + ifOfstShp = self._extractFaceOffset(ifc, ofstVal) if ifOfstShp is False: PathLog.error('Failed to create collective offset avoid internal features.') else: @@ -997,9 +1008,9 @@ class ObjectSurface(PathOp.ObjectOp): except Exception as eee: PathLog.error(str(eee)) cont = False - time.sleep(0.2) + #time.sleep(0.2) - if cont is True: + if cont: csFaceShape = self._getShapeSlice(baseEnv) if csFaceShape is False: PathLog.debug('_getShapeSlice(baseEnv) failed') @@ -1014,7 +1025,7 @@ class ObjectSurface(PathOp.ObjectOp): if cont is True and obj.ProfileEdges != 'None': PathLog.debug(' -Attempting profile geometry for model base.') ofstVal = self._calculateOffsetValue(obj, isHole) - psOfst = self._extractFaceOffset(obj, csFaceShape, ofstVal) + psOfst = self._extractFaceOffset(csFaceShape, ofstVal) if psOfst is not False: if obj.ProfileEdges == 'Only': return (True, psOfst) @@ -1023,9 +1034,9 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error(' -Failed to create profile geometry.') cont = False - if cont is True: + if cont: ofstVal = self._calculateOffsetValue(obj, isHole) - faceOffsetShape = self._extractFaceOffset(obj, csFaceShape, ofstVal) + faceOffsetShape = self._extractFaceOffset(csFaceShape, ofstVal) if faceOffsetShape is False: PathLog.error('_extractFaceOffset() failed.') else: @@ -1076,7 +1087,7 @@ class ObjectSurface(PathOp.ObjectOp): WIRES.append((eArea, F.Wires[0], raised)) cont = False - if cont is True: + if cont: PathLog.debug(' -cont is True') # If only one wire and not checkEdges, return first wire if lenWrs == 1: @@ -1112,21 +1123,21 @@ class ObjectSurface(PathOp.ObjectOp): if isVoid is False: if isHole is True: offset = -1 * obj.InternalFeaturesAdjustment.Value - offset += self.radius # (self.radius + (tolrnc / 10.0)) + offset += self.radius + (tolrnc / 10.0) else: offset = -1 * obj.BoundaryAdjustment.Value if obj.BoundaryEnforcement is True: - offset += self.radius # (self.radius + (tolrnc / 10.0)) + offset += self.radius + (tolrnc / 10.0) else: - offset -= self.radius # (self.radius + (tolrnc / 10.0)) + offset -= self.radius + (tolrnc / 10.0) offset = 0.0 - offset else: offset = -1 * obj.BoundaryAdjustment.Value - offset += self.radius # (self.radius + (tolrnc / 10.0)) + offset += self.radius + (tolrnc / 10.0) return offset - def _extractFaceOffset(self, obj, fcShape, offset): + def _extractFaceOffset(self, fcShape, offset): '''_extractFaceOffset(fcShape, offset) ... internal function. Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' @@ -1151,10 +1162,6 @@ class ObjectSurface(PathOp.ObjectOp): area.add(fcShape) area.setParams(**areaParams) # set parameters - # Save parameters for debugging - # obj.AreaParams = str(area.getParams()) - # PathLog.debug("Area with params: {}".format(area.getParams())) - offsetShape = area.getShape() wCnt = len(offsetShape.Wires) if wCnt == 0: @@ -1409,37 +1416,20 @@ class ObjectSurface(PathOp.ObjectOp): # PathLog.debug(f" -self.modelTypes[{m}] == 'M'") if self.modelTypes[m] == 'M': - mesh = M.Mesh + #TODO: test if this works + facets = M.Mesh.Facets.Points else: - # base.Shape.tessellate(0.05) # 0.5 original value - # mesh = MeshPart.meshFromShape(base.Shape, Deflection=self.deflection) - mesh = MeshPart.meshFromShape(Shape=M.Shape, - LinearDeflection=obj.LinearDeflection.Value, - AngularDeflection=obj.AngularDeflection.Value, - Relative=False) + facets = Part.getFacets(M.Shape) if self.modelSTLs[m] is True: stl = ocl.STLSurf() - if obj.Algorithm == 'OCL Dropcutter': - for f in mesh.Facets: - p = f.Points[0] - q = f.Points[1] - r = f.Points[2] - t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), - ocl.Point(q[0], q[1], q[2]), - ocl.Point(r[0], r[1], r[2])) - stl.addTriangle(t) - self.modelSTLs[m] = stl - elif obj.Algorithm == 'OCL Waterline': - for f in mesh.Facets: - p = f.Points[0] - q = f.Points[1] - r = f.Points[2] - t = ocl.Triangle(ocl.Point(p[0], p[1], p[2] + obj.DepthOffset.Value), - ocl.Point(q[0], q[1], q[2] + obj.DepthOffset.Value), - ocl.Point(r[0], r[1], r[2] + obj.DepthOffset.Value)) - stl.addTriangle(t) - self.modelSTLs[m] = stl + + for tri in facets: + t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]), + ocl.Point(tri[1][0], tri[1][1], tri[1][2]), + ocl.Point(tri[2][0], tri[2][1], tri[2][2])) + stl.addTriangle(t) + self.modelSTLs[m] = stl return def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes): @@ -1479,7 +1469,7 @@ class ObjectSurface(PathOp.ObjectOp): except Exception as eee: PathLog.error(str(eee)) - if cont is True: + if cont: stckWst = JOB.Stock.Shape.cut(envBB) if obj.BoundaryAdjustment > 0.0: cmpndFS = Part.makeCompound(faceShapes) @@ -1490,8 +1480,7 @@ class ObjectSurface(PathOp.ObjectOp): fuseShapes.append(adjStckWst) else: PathLog.warning('Path transitions might not avoid the model. Verify paths.') - time.sleep(0.3) - + #time.sleep(0.3) else: # If boundbox is Job.Stock, add hidden pad under stock as base plate toolDiam = self.cutter.getDiameter() @@ -1510,11 +1499,7 @@ class ObjectSurface(PathOp.ObjectOp): voidEnv = PathUtils.getEnvelope(partshape=voidComp, depthparams=self.depthParams) # Produces .Shape fuseShapes.append(voidEnv) - f0 = fuseShapes.pop(0) - if len(fuseShapes) > 0: - fused = f0.fuse(fuseShapes) - else: - fused = f0 + fused = Part.makeCompound(fuseShapes) if self.showDebugObjects is True: T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'safeSTLShape') @@ -1522,20 +1507,13 @@ class ObjectSurface(PathOp.ObjectOp): T.purgeTouched() self.tempGroup.addObject(T) - # Extract mesh from fusion - meshFuse = MeshPart.meshFromShape(Shape=fused, - LinearDeflection=obj.LinearDeflection.Value, - AngularDeflection=obj.AngularDeflection.Value, - Relative=False) - time.sleep(0.2) + facets = Part.getFacets(fused) + stl = ocl.STLSurf() - for f in meshFuse.Facets: - p = f.Points[0] - q = f.Points[1] - r = f.Points[2] - t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), - ocl.Point(q[0], q[1], q[2]), - ocl.Point(r[0], r[1], r[2])) + for tri in facets: + t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]), + ocl.Point(tri[1][0], tri[1][1], tri[1][2]), + ocl.Point(tri[2][0], tri[2][1], tri[2][2])) stl.addTriangle(t) self.safeSTLs[mdlIdx] = stl @@ -1543,7 +1521,7 @@ class ObjectSurface(PathOp.ObjectOp): def _processCutAreas(self, JOB, obj, mdlIdx, FCS, VDS): '''_processCutAreas(JOB, obj, mdlIdx, FCS, VDS)... This method applies any avoided faces or regions to the selected faces. - It then calls the correct scan method depending on the Algorithm and ScanType properties.''' + It then calls the correct scan method depending on the ScanType property.''' PathLog.debug('_processCutAreas()') final = list() @@ -1561,10 +1539,7 @@ class ObjectSurface(PathOp.ObjectOp): else: COMP = ADD - if obj.Algorithm == 'OCL Waterline': - final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline - elif obj.ScanType == 'Planar': + if obj.ScanType == 'Planar': final.extend(self._processPlanarOp(JOB, obj, mdlIdx, COMP, 0)) elif obj.ScanType == 'Rotational': final.extend(self._processRotationalOp(obj, base, COMP)) @@ -1585,10 +1560,7 @@ class ObjectSurface(PathOp.ObjectOp): else: COMP = ADD - if obj.Algorithm == 'OCL Waterline': - final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline - elif obj.ScanType == 'Planar': + if obj.ScanType == 'Planar': final.extend(self._processPlanarOp(JOB, obj, mdlIdx, COMP, fsi)) elif obj.ScanType == 'Rotational': final.extend(self._processRotationalOp(JOB, obj, mdlIdx, COMP)) @@ -2013,7 +1985,7 @@ class ObjectSurface(PathOp.ObjectOp): for v in range(1, lenOS): nxt = OS[v + 1] if optimize is True: - iPOL = self.isPointOnLine(prev, nxt, pnt) + iPOL = prev.isOnLineSegment(nxt, pnt) if iPOL is True: pnt = nxt else: @@ -2067,7 +2039,7 @@ class ObjectSurface(PathOp.ObjectOp): ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (first / middle point) - iC = self.isPointOnLine(sp, ep, cp) + iC = sp.isOnLineSegment(ep, cp) if iC is True: inLine.append('BRK') chkGap = True @@ -2173,7 +2145,7 @@ class ObjectSurface(PathOp.ObjectOp): cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (start point of segment) ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point - iC = self.isPointOnLine(sp, ep, cp) + iC = sp.isOnLineSegment(ep, cp) if iC is True: inLine.append('BRK') chkGap = True @@ -2366,7 +2338,11 @@ class ObjectSurface(PathOp.ObjectOp): p1 = FreeCAD.Vector(v1.X, v1.Y, v1.Z) sp = (v1.X, v1.Y, 0.0) rad = p1.sub(COM).Length - tolrncAng = math.asin(space/rad) + spcRadRatio = space/rad + if spcRadRatio < 1.0: + tolrncAng = math.asin(spcRadRatio) + else: + tolrncAng = 0.999998 * math.pi X = COM.x + (rad * math.cos(tolrncAng)) Y = v1.Y - space # rad * math.sin(tolrncAng) @@ -2402,8 +2378,12 @@ class ObjectSurface(PathOp.ObjectOp): # Pop connected edge index values from arc segments index list iEi = EI.index(iE) iSi = EI.index(iS) - EI.pop(iEi) - EI.pop(iSi) + if iEi > iSi: + EI.pop(iEi) + EI.pop(iSi) + else: + EI.pop(iSi) + EI.pop(iEi) if len(EI) > 0: PRTS.append('BRK') chkGap = True @@ -2454,7 +2434,7 @@ class ObjectSurface(PathOp.ObjectOp): return ARCS def _planarDropCutScan(self, pdc, A, B): - PNTS = list() + #PNTS = list() (x1, y1) = A (x2, y2) = B path = ocl.Path() # create an empty path object @@ -2465,8 +2445,7 @@ class ObjectSurface(PathOp.ObjectOp): pdc.setPath(path) pdc.run() # run dropcutter algorithm on path CLP = pdc.getCLPoints() - for p in CLP: - PNTS.append(FreeCAD.Vector(p.x, p.y, p.z)) + PNTS = [FreeCAD.Vector(p.x, p.y, p.z) for p in CLP] return PNTS # pdc.getCLPoints() def _planarCircularDropCutScan(self, pdc, Arc, cMode): @@ -2581,44 +2560,14 @@ class ObjectSurface(PathOp.ObjectOp): return GCODE def _planarSinglepassProcess(self, obj, PNTS): - output = [] - optimize = obj.OptimizeLinearPaths - lenPNTS = len(PNTS) - lstIdx = lenPNTS - 1 - lop = None - onLine = False - - # Initialize first three points - nxt = None - pnt = PNTS[0] - prev = FreeCAD.Vector(-442064564.6, 258539656553.27, 3538553425.847) - - # Add temp end point - PNTS.append(FreeCAD.Vector(-4895747464.6, -25855763553.2, 35865763425)) - - # Begin processing ocl points list into gcode - for i in range(0, lenPNTS): - # Calculate next point for consideration with current point - nxt = PNTS[i + 1] - - # Process point - if optimize is True: - iPOL = self.isPointOnLine(prev, nxt, pnt) - if iPOL is True: - onLine = True - else: - onLine = False - output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed})) - else: - output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed})) - - # Rotate point data - if onLine is False: - prev = pnt - pnt = nxt - # Efor - - temp = PNTS.pop() # Remove temp end point + if obj.OptimizeLinearPaths: + # first item will be compared to the last point, but I think that should work + output = [Path.Command('G1', {'X': PNTS[i].x, 'Y': PNTS[i].y, 'Z': PNTS[i].z, 'F': self.horizFeed}) + for i in range(0, len(PNTS) - 1) + if not PNTS[i].isOnLineSegment(PNTS[i -1],PNTS[i + 1])] + output.append(Path.Command('G1', {'X': PNTS[-1].x, 'Y': PNTS[-1].y, 'Z': PNTS[-1].z, 'F': self.horizFeed})) + else: + output = [Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed}) for pnt in PNTS] return output @@ -2645,6 +2594,7 @@ class ObjectSurface(PathOp.ObjectOp): # Process each layer in depthparams prvLyrFirst = None prvLyrLast = None + lastPrvStpLast = None actvLyrs = 0 for lyr in range(0, lenDP): odd = True # ZigZag directional switch @@ -2653,8 +2603,12 @@ class ObjectSurface(PathOp.ObjectOp): actvSteps = 0 LYR = list() prvStpFirst = None + if lyr > 0: + if prvStpLast is not None: + lastPrvStpLast = prvStpLast prvStpLast = None lyrDep = depthparams[lyr] + PathLog.debug('Multi-pass lyrDep: {}'.format(round(lyrDep, 4))) # Cycle through step-over sections (line segments or arcs) for so in range(0, len(SCANDATA)): @@ -2695,6 +2649,7 @@ class ObjectSurface(PathOp.ObjectOp): # Manage step over transition and CircularZigZag direction if so > 0: + # PathLog.debug(' stepover index: {}'.format(so)) # Control ZigZag direction if obj.CutPattern == 'CircularZigZag': if odd is True: @@ -2702,6 +2657,8 @@ class ObjectSurface(PathOp.ObjectOp): else: odd = True # Control step over transition + if prvStpLast is None: + prvStpLast = lastPrvStpLast minTrnsHght = self._getMinSafeTravelHeight(safePDC, prvStpLast, first, minDep=None) # Check safe travel height against fullSTL transCmds.append(Path.Command('N (--Step {} transition)'.format(so), {})) transCmds.extend(self._stepTransitionCmds(obj, prvStpLast, first, minTrnsHght, tolrnc)) @@ -2714,6 +2671,7 @@ class ObjectSurface(PathOp.ObjectOp): for i in range(0, lenAdjPrts): prt = ADJPRTS[i] lenPrt = len(prt) + # PathLog.debug(' adj parts index - lenPrt: {} - {}'.format(i, lenPrt)) if prt == 'BRK' and prtsHasCmds is True: nxtStart = ADJPRTS[i + 1][0] minSTH = self._getMinSafeTravelHeight(safePDC, last, nxtStart, minDep=None) # Check safe travel height against fullSTL @@ -2886,7 +2844,7 @@ class ObjectSurface(PathOp.ObjectOp): # Process point if prcs is True: if optimize is True: - iPOL = self.isPointOnLine(prev, nxt, pnt) + iPOL = prev.isOnLineSegment(nxt, pnt) if iPOL is True: onLine = True else: @@ -3301,7 +3259,7 @@ class ObjectSurface(PathOp.ObjectOp): prevDepth = layDep lCnt += 1 # increment layer count PathLog.debug("--Layer " + str(lCnt) + ": " + str(len(advances)) + " OCL scans and gcode in " + str(time.time() - t_before) + " s") - time.sleep(0.2) + #time.sleep(0.2) # Eol return commands @@ -3460,7 +3418,7 @@ class ObjectSurface(PathOp.ObjectOp): self.holdPoint = ocl.Point(float("inf"), float("inf"), float("inf")) if self.onHold is False: - if not optimize or not self.isPointOnLine(FreeCAD.Vector(prev.x, prev.y, prev.z), FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): + if not optimize or not FreeCAD.Vector(prev.x, prev.y, prev.z).isOnLineSegment(FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed})) # elif i == lastCLP: # output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed})) @@ -3550,347 +3508,7 @@ class ObjectSurface(PathOp.ObjectOp): return output - # Main waterline functions - def _waterlineOp(self, JOB, obj, mdlIdx, subShp=None): - '''_waterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model.''' - commands = [] - t_begin = time.time() - # JOB = PathUtils.findParentJob(obj) - base = JOB.Model.Group[mdlIdx] - bb = self.boundBoxes[mdlIdx] - stl = self.modelSTLs[mdlIdx] - - # Prepare global holdpoint and layerEndPnt containers - if self.holdPoint is None: - self.holdPoint = ocl.Point(float("inf"), float("inf"), float("inf")) - if self.layerEndPnt is None: - self.layerEndPnt = ocl.Point(float("inf"), float("inf"), float("inf")) - - # Set extra offset to diameter of cutter to allow cutter to move around perimeter of model - # Need to make DropCutterExtraOffset available for waterline algorithm - # cdeoX = obj.DropCutterExtraOffset.x - # cdeoY = obj.DropCutterExtraOffset.y - toolDiam = self.cutter.getDiameter() - cdeoX = 0.6 * toolDiam - cdeoY = 0.6 * toolDiam - - if subShp is None: - # Get correct boundbox - if obj.BoundBox == 'Stock': - BS = JOB.Stock - bb = BS.Shape.BoundBox - elif obj.BoundBox == 'BaseBoundBox': - BS = base - bb = base.Shape.BoundBox - - env = PathUtils.getEnvelope(partshape=BS.Shape, depthparams=self.depthParams) # Produces .Shape - - xmin = bb.XMin - xmax = bb.XMax - ymin = bb.YMin - ymax = bb.YMax - zmin = bb.ZMin - zmax = bb.ZMax - else: - xmin = subShp.BoundBox.XMin - xmax = subShp.BoundBox.XMax - ymin = subShp.BoundBox.YMin - ymax = subShp.BoundBox.YMax - zmin = subShp.BoundBox.ZMin - zmax = subShp.BoundBox.ZMax - - smplInt = obj.SampleInterval.Value - minSampInt = 0.001 # value is mm - if smplInt < minSampInt: - smplInt = minSampInt - - # Determine bounding box length for the OCL scan - bbLength = math.fabs(ymax - ymin) - numScanLines = int(math.ceil(bbLength / smplInt) + 1) # Number of lines - - # Compute number and size of stepdowns, and final depth - if obj.LayerMode == 'Single-pass': - depthparams = [obj.FinalDepth.Value] - else: - depthparams = [dp for dp in self.depthParams] - lenDP = len(depthparams) - - # Prepare PathDropCutter objects with STL data - safePDC = self._planarGetPDC(self.safeSTLs[mdlIdx], - depthparams[lenDP - 1], obj.SampleInterval.Value, useSafeCutter=False) - - # Scan the piece to depth at smplInt - oclScan = [] - oclScan = self._waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, depthparams[lenDP - 1], numScanLines) - # oclScan = SCANS - lenOS = len(oclScan) - ptPrLn = int(lenOS / numScanLines) - - # Convert oclScan list of points to multi-dimensional list - scanLines = [] - for L in range(0, numScanLines): - scanLines.append([]) - for P in range(0, ptPrLn): - pi = L * ptPrLn + P - scanLines[L].append(oclScan[pi]) - lenSL = len(scanLines) - pntsPerLine = len(scanLines[0]) - PathLog.debug("--OCL scan: " + str(lenSL * pntsPerLine) + " points, with " + str(numScanLines) + " lines and " + str(pntsPerLine) + " pts/line") - - # Extract Wl layers per depthparams - lyr = 0 - cmds = [] - layTime = time.time() - self.topoMap = [] - for layDep in depthparams: - cmds = self._getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) - commands.extend(cmds) - lyr += 1 - PathLog.debug("--All layer scans combined took " + str(time.time() - layTime) + " s") - return commands - - def _waterlineDropCutScan(self, stl, smplInt, xmin, xmax, ymin, fd, numScanLines): - '''_waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, fd, numScanLines) ... - Perform OCL scan for waterline purpose.''' - pdc = ocl.PathDropCutter() # create a pdc - pdc.setSTL(stl) - pdc.setCutter(self.cutter) - pdc.setZ(fd) # set minimumZ (final / target depth value) - pdc.setSampling(smplInt) - - # Create line object as path - path = ocl.Path() # create an empty path object - for nSL in range(0, numScanLines): - yVal = ymin + (nSL * smplInt) - p1 = ocl.Point(xmin, yVal, fd) # start-point of line - p2 = ocl.Point(xmax, yVal, fd) # end-point of line - path.append(ocl.Line(p1, p2)) - # path.append(l) # add the line to the path - pdc.setPath(path) - pdc.run() # run drop-cutter on the path - - # return the list the points - return pdc.getCLPoints() - - def _getWaterline(self, obj, scanLines, layDep, lyr, lenSL, pntsPerLine): - '''_getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) ... Get waterline.''' - commands = [] - cmds = [] - loopList = [] - self.topoMap = [] - # Create topo map from scanLines (highs and lows) - self.topoMap = self._createTopoMap(scanLines, layDep, lenSL, pntsPerLine) - # Add buffer lines and columns to topo map - self._bufferTopoMap(lenSL, pntsPerLine) - # Identify layer waterline from OCL scan - self._highlightWaterline(4, 9) - # Extract waterline and convert to gcode - loopList = self._extractWaterlines(obj, scanLines, lyr, layDep) - # save commands - for loop in loopList: - cmds = self._loopToGcode(obj, layDep, loop) - commands.extend(cmds) - return commands - - def _createTopoMap(self, scanLines, layDep, lenSL, pntsPerLine): - '''_createTopoMap(scanLines, layDep, lenSL, pntsPerLine) ... Create topo map version of OCL scan data.''' - topoMap = [] - for L in range(0, lenSL): - topoMap.append([]) - for P in range(0, pntsPerLine): - if scanLines[L][P].z > layDep: - topoMap[L].append(2) - else: - topoMap[L].append(0) - return topoMap - - def _bufferTopoMap(self, lenSL, pntsPerLine): - '''_bufferTopoMap(lenSL, pntsPerLine) ... Add buffer boarder of zeros to all sides to topoMap data.''' - pre = [0, 0] - post = [0, 0] - for p in range(0, pntsPerLine): - pre.append(0) - post.append(0) - for l in range(0, lenSL): - self.topoMap[l].insert(0, 0) - self.topoMap[l].append(0) - self.topoMap.insert(0, pre) - self.topoMap.append(post) - return True - - def _highlightWaterline(self, extraMaterial, insCorn): - '''_highlightWaterline(extraMaterial, insCorn) ... Highlight the waterline data, separating from extra material.''' - TM = self.topoMap - lastPnt = len(TM[1]) - 1 - lastLn = len(TM) - 1 - highFlag = 0 - - # ("--Convert parallel data to ridges") - for lin in range(1, lastLn): - for pt in range(1, lastPnt): # Ignore first and last points - if TM[lin][pt] == 0: - if TM[lin][pt + 1] == 2: # step up - TM[lin][pt] = 1 - if TM[lin][pt - 1] == 2: # step down - TM[lin][pt] = 1 - - # ("--Convert perpendicular data to ridges and highlight ridges") - for pt in range(1, lastPnt): # Ignore first and last points - for lin in range(1, lastLn): - if TM[lin][pt] == 0: - highFlag = 0 - if TM[lin + 1][pt] == 2: # step up - TM[lin][pt] = 1 - if TM[lin - 1][pt] == 2: # step down - TM[lin][pt] = 1 - elif TM[lin][pt] == 2: - highFlag += 1 - if highFlag == 3: - if TM[lin - 1][pt - 1] < 2 or TM[lin - 1][pt + 1] < 2: - highFlag = 2 - else: - TM[lin - 1][pt] = extraMaterial - highFlag = 2 - - # ("--Square corners") - for pt in range(1, lastPnt): - for lin in range(1, lastLn): - if TM[lin][pt] == 1: # point == 1 - cont = True - if TM[lin + 1][pt] == 0: # forward == 0 - if TM[lin + 1][pt - 1] == 1: # forward left == 1 - if TM[lin][pt - 1] == 2: # left == 2 - TM[lin + 1][pt] = 1 # square the corner - cont = False - - if cont is True and TM[lin + 1][pt + 1] == 1: # forward right == 1 - if TM[lin][pt + 1] == 2: # right == 2 - TM[lin + 1][pt] = 1 # square the corner - cont = True - - if TM[lin - 1][pt] == 0: # back == 0 - if TM[lin - 1][pt - 1] == 1: # back left == 1 - if TM[lin][pt - 1] == 2: # left == 2 - TM[lin - 1][pt] = 1 # square the corner - cont = False - - if cont is True and TM[lin - 1][pt + 1] == 1: # back right == 1 - if TM[lin][pt + 1] == 2: # right == 2 - TM[lin - 1][pt] = 1 # square the corner - - # remove inside corners - for pt in range(1, lastPnt): - for lin in range(1, lastLn): - if TM[lin][pt] == 1: # point == 1 - if TM[lin][pt + 1] == 1: - if TM[lin - 1][pt + 1] == 1 or TM[lin + 1][pt + 1] == 1: - TM[lin][pt + 1] = insCorn - elif TM[lin][pt - 1] == 1: - if TM[lin - 1][pt - 1] == 1 or TM[lin + 1][pt - 1] == 1: - TM[lin][pt - 1] = insCorn - - return True - - def _extractWaterlines(self, obj, oclScan, lyr, layDep): - '''_extractWaterlines(obj, oclScan, lyr, layDep) ... Extract water lines from OCL scan data.''' - srch = True - lastPnt = len(self.topoMap[0]) - 1 - lastLn = len(self.topoMap) - 1 - maxSrchs = 5 - srchCnt = 1 - loopList = [] - loop = [] - loopNum = 0 - - if self.CutClimb is True: - lC = [-1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0] - pC = [-1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1] - else: - lC = [1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0] - pC = [-1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1] - - while srch is True: - srch = False - if srchCnt > maxSrchs: - PathLog.debug("Max search scans, " + str(maxSrchs) + " reached\nPossible incomplete waterline result!") - break - for L in range(1, lastLn): - for P in range(1, lastPnt): - if self.topoMap[L][P] == 1: - # start loop follow - srch = True - loopNum += 1 - loop = self._trackLoop(oclScan, lC, pC, L, P, loopNum) - self.topoMap[L][P] = 0 # Mute the starting point - loopList.append(loop) - srchCnt += 1 - PathLog.debug("Search count for layer " + str(lyr) + " is " + str(srchCnt) + ", with " + str(loopNum) + " loops.") - return loopList - - def _trackLoop(self, oclScan, lC, pC, L, P, loopNum): - '''_trackLoop(oclScan, lC, pC, L, P, loopNum) ... Track the loop direction.''' - loop = [oclScan[L - 1][P - 1]] # Start loop point list - cur = [L, P, 1] - prv = [L, P - 1, 1] - nxt = [L, P + 1, 1] - follow = True - ptc = 0 - ptLmt = 200000 - while follow is True: - ptc += 1 - if ptc > ptLmt: - PathLog.debug("Loop number " + str(loopNum) + " at [" + str(nxt[0]) + ", " + str(nxt[1]) + "] pnt count exceeds, " + str(ptLmt) + ". Stopped following loop.") - break - nxt = self._findNextWlPoint(lC, pC, cur[0], cur[1], prv[0], prv[1]) # get next point - loop.append(oclScan[nxt[0] - 1][nxt[1] - 1]) # add it to loop point list - self.topoMap[nxt[0]][nxt[1]] = nxt[2] # Mute the point, if not Y stem - if nxt[0] == L and nxt[1] == P: # check if loop complete - follow = False - elif nxt[0] == cur[0] and nxt[1] == cur[1]: # check if line cannot be detected - follow = False - prv = cur - cur = nxt - return loop - - def _findNextWlPoint(self, lC, pC, cl, cp, pl, pp): - '''_findNextWlPoint(lC, pC, cl, cp, pl, pp) ... - Find the next waterline point in the point cloud layer provided.''' - dl = cl - pl - dp = cp - pp - num = 0 - i = 3 - s = 0 - mtch = 0 - found = False - while mtch < 8: # check all 8 points around current point - if lC[i] == dl: - if pC[i] == dp: - s = i - 3 - found = True - # Check for y branch where current point is connection between branches - for y in range(1, mtch): - if lC[i + y] == dl: - if pC[i + y] == dp: - num = 1 - break - break - i += 1 - mtch += 1 - if found is False: - # ("_findNext: No start point found.") - return [cl, cp, num] - - for r in range(0, 8): - l = cl + lC[s + r] - p = cp + pC[s + r] - if self.topoMap[l][p] == 1: - return [l, p, num] - - # ("_findNext: No next pnt found") - return [cl, cp, num] - - def _loopToGcode(self, obj, layDep, loop): '''_loopToGcode(obj, layDep, loop) ... Convert set of loop points to Gcode.''' # generate the path commands output = [] @@ -3921,7 +3539,7 @@ class ObjectSurface(PathOp.ObjectOp): else: optimize = False - if not optimize or not self.isPointOnLine(FreeCAD.Vector(prev.x, prev.y, prev.z), FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): + if not optimize or not FreeCAD.Vector(prev.x, prev.y, prev.z).isOnLineSegment(FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'F': self.horizFeed})) # Rotate point data @@ -3939,26 +3557,6 @@ class ObjectSurface(PathOp.ObjectOp): return output - # Support functions for both dropcutter and waterline operations - def isPointOnLine(self, strtPnt, endPnt, pointP): - '''isPointOnLine(strtPnt, endPnt, pointP) ... Determine if a given point is on the line defined by start and end points.''' - tolerance = 1e-6 - vectorAB = endPnt - strtPnt - vectorAC = pointP - strtPnt - crossproduct = vectorAB.cross(vectorAC) - dotproduct = vectorAB.dot(vectorAC) - - if crossproduct.Length > tolerance: - return False - - if dotproduct < 0: - return False - - if dotproduct > vectorAB.Length * vectorAB.Length: - return False - - return True - def holdStopCmds(self, obj, zMax, pd, p2, txt): '''holdStopCmds(obj, zMax, pd, p2, txt) ... Gcode commands to be executed at beginning of hold.''' cmds = [] @@ -3971,54 +3569,6 @@ class ObjectSurface(PathOp.ObjectOp): cmds.append(Path.Command('G0', {'Z': p2.z, 'F': self.vertFeed})) # drop cutter down to current Z depth, returning to normal cut path and speed return cmds - def holdStopEndCmds(self, obj, p2, txt): - '''holdStopEndCmds(obj, p2, txt) ... Gcode commands to be executed at end of hold stop.''' - cmds = [] - msg = 'N (' + txt + ')' - cmds.append(Path.Command(msg, {})) # Raise cutter rapid to zMax in line of travel - cmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) # Raise cutter rapid to zMax in line of travel - # cmds.append(Path.Command('G0', {'X': p2.x, 'Y': p2.y, 'F': self.horizRapid})) # horizontal rapid to current XY coordinate - return cmds - - def subsectionCLP(self, CLP, xmin, ymin, xmax, ymax): - '''subsectionCLP(CLP, xmin, ymin, xmax, ymax) ... - This function returns a subsection of the CLP scan, limited to the min/max values supplied.''' - section = list() - lenCLP = len(CLP) - for i in range(0, lenCLP): - if CLP[i].x < xmax: - if CLP[i].y < ymax: - if CLP[i].x > xmin: - if CLP[i].y > ymin: - section.append(CLP[i]) - return section - - def getMaxHeightBetweenPoints(self, finalDepth, p1, p2, cutter, CLP): - ''' getMaxHeightBetweenPoints(finalDepth, p1, p2, cutter, CLP) ... - This function connects two HOLD points with line. - Each point within the subsection point list is tested to determinie if it is under cutter. - Points determined to be under the cutter on line are tested for z height. - The highest z point is the requirement for clearance between p1 and p2, and returned as zMax with 2 mm extra. - ''' - dx = (p2.x - p1.x) - if dx == 0.0: - dx = 0.00001 # Need to employ a global tolerance here - m = (p2.y - p1.y) / dx - b = p1.y - (m * p1.x) - - avoidTool = round(cutter * 0.75, 1) # 1/2 diam. of cutter is theoretically safe, but 3/4 diam is used for extra clearance - zMax = finalDepth - lenCLP = len(CLP) - for i in range(0, lenCLP): - mSqrd = m**2 - if mSqrd < 0.0000001: # Need to employ a global tolerance here - mSqrd = 0.0000001 - perpDist = math.sqrt((CLP[i].y - (m * CLP[i].x) - b)**2 / (1 + 1 / (mSqrd))) - if perpDist < avoidTool: # if point within cutter reach on line of travel, test z height and update as needed - if CLP[i].z > zMax: - zMax = CLP[i].z - return zMax + 2.0 - def resetOpVariables(self, all=True): '''resetOpVariables() ... Reset class variables used for instance of operation.''' self.holdPoint = None @@ -4131,39 +3681,11 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error('Unable to set OCL cutter.') return False - def determineVectDirect(self, pnt, nxt, travVect): - if nxt.x == pnt.x: - travVect.x = 0 - elif nxt.x < pnt.x: - travVect.x = -1 - else: - travVect.x = 1 - - if nxt.y == pnt.y: - travVect.y = 0 - elif nxt.y < pnt.y: - travVect.y = -1 - else: - travVect.y = 1 - return travVect - - def determineLineOfTravel(self, travVect): - if travVect.x == 0 and travVect.y != 0: - lineOfTravel = "Y" - elif travVect.y == 0 and travVect.x != 0: - lineOfTravel = "X" - else: - lineOfTravel = "O" # used for turns - return lineOfTravel - def _getMinSafeTravelHeight(self, pdc, p1, p2, minDep=None): A = (p1.x, p1.y) B = (p2.x, p2.y) LINE = self._planarDropCutScan(pdc, A, B) - zMax = LINE[0].z - for p in LINE: - if p.z > zMax: - zMax = p.z + zMax = max([obj.z for obj in LINE]) if minDep is not None: if zMax < minDep: zMax = minDep @@ -4173,7 +3695,6 @@ class ObjectSurface(PathOp.ObjectOp): def SetupProperties(): ''' SetupProperties() ... Return list of properties required for operation.''' setup = [] - setup.append('Algorithm') setup.append('AvoidLastX_Faces') setup.append('AvoidLastX_InternalFeatures') setup.append('BoundBox') @@ -4208,12 +3729,7 @@ def SetupProperties(): setup.append('AngularDeflection') setup.append('LinearDeflection') # For debugging - setup.append('AreaParams') setup.append('ShowTempObjects') - # Targeted for possible removal - setup.append('IgnoreWaste') - setup.append('IgnoreWasteDepth') - setup.append('ReleaseFromWaste') return setup diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index 40feff2c70..41f11f6007 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -39,89 +39,120 @@ __doc__ = "Surface operation page controller and command implementation." class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''Page controller class for the Surface operation.''' + def initPage(self, obj): + self.setTitle("3D Surface") + self.updateVisibility() + def getForm(self): '''getForm() ... returns UI''' return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSurfaceEdit.ui") def getFields(self, obj): '''getFields(obj) ... transfers values from UI to obj's proprties''' + self.updateToolController(obj, self.form.toolController) + self.updateCoolant(obj, self.form.coolantController) + PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset) PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) - if obj.StepOver != self.form.stepOver.value(): - obj.StepOver = self.form.stepOver.value() - - if obj.Algorithm != str(self.form.algorithmSelect.currentText()): - obj.Algorithm = str(self.form.algorithmSelect.currentText()) - if obj.BoundBox != str(self.form.boundBoxSelect.currentText()): obj.BoundBox = str(self.form.boundBoxSelect.currentText()) - if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()): - obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText()) + if obj.ScanType != str(self.form.scanType.currentText()): + obj.ScanType = str(self.form.scanType.currentText()) + + if obj.StepOver != self.form.stepOver.value(): + obj.StepOver = self.form.stepOver.value() + + if obj.LayerMode != str(self.form.layerMode.currentText()): + obj.LayerMode = str(self.form.layerMode.currentText()) + + if obj.CutPattern != str(self.form.cutPattern.currentText()): + obj.CutPattern = str(self.form.cutPattern.currentText()) obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value + if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()): + obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText()) + + PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset) + PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + + if obj.UseStartPoint != self.form.useStartPoint.isChecked(): + obj.UseStartPoint = self.form.useStartPoint.isChecked() + if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() - self.updateToolController(obj, self.form.toolController) - self.updateCoolant(obj, self.form.coolantController) + if obj.OptimizeStepOverTransitions != self.form.optimizeStepOverTransitions.isChecked(): + obj.OptimizeStepOverTransitions = self.form.optimizeStepOverTransitions.isChecked() def setFields(self, obj): '''setFields(obj) ... transfers obj's property values to UI''' - self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect) + self.setupToolController(obj, self.form.toolController) + self.setupCoolant(obj, self.form.coolantController) self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) + self.selectInComboBox(obj.ScanType, self.form.scanType) + self.selectInComboBox(obj.LayerMode, self.form.layerMode) + self.selectInComboBox(obj.CutPattern, self.form.cutPattern) + self.form.boundBoxExtraOffsetX.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.x, FreeCAD.Units.Length).UserString) + self.form.boundBoxExtraOffsetY.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.y, FreeCAD.Units.Length).UserString) self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) - - self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x)) - self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y)) self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString) - self.form.sampleInterval.setText(str(obj.SampleInterval)) self.form.stepOver.setValue(obj.StepOver) + self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString) + + if obj.UseStartPoint: + self.form.useStartPoint.setCheckState(QtCore.Qt.Checked) + else: + self.form.useStartPoint.setCheckState(QtCore.Qt.Unchecked) if obj.OptimizeLinearPaths: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked) else: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked) - self.setupToolController(obj, self.form.toolController) - self.setupCoolant(obj, self.form.coolantController) + if obj.OptimizeStepOverTransitions: + self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Checked) + else: + self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Unchecked) def getSignalsForUpdate(self, obj): '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] signals.append(self.form.toolController.currentIndexChanged) - signals.append(self.form.algorithmSelect.currentIndexChanged) + signals.append(self.form.coolantController.currentIndexChanged) signals.append(self.form.boundBoxSelect.currentIndexChanged) - signals.append(self.form.dropCutterDirSelect.currentIndexChanged) + signals.append(self.form.scanType.currentIndexChanged) + signals.append(self.form.layerMode.currentIndexChanged) + signals.append(self.form.cutPattern.currentIndexChanged) signals.append(self.form.boundBoxExtraOffsetX.editingFinished) signals.append(self.form.boundBoxExtraOffsetY.editingFinished) - signals.append(self.form.sampleInterval.editingFinished) - signals.append(self.form.stepOver.editingFinished) + signals.append(self.form.dropCutterDirSelect.currentIndexChanged) signals.append(self.form.depthOffset.editingFinished) + signals.append(self.form.stepOver.editingFinished) + signals.append(self.form.sampleInterval.editingFinished) + signals.append(self.form.useStartPoint.stateChanged) signals.append(self.form.optimizeEnabled.stateChanged) - signals.append(self.form.coolantController.currentIndexChanged) + signals.append(self.form.optimizeStepOverTransitions.stateChanged) return signals def updateVisibility(self): - if self.form.algorithmSelect.currentText() == "OCL Dropcutter": - self.form.boundBoxExtraOffsetX.setEnabled(True) - self.form.boundBoxExtraOffsetY.setEnabled(True) - self.form.boundBoxSelect.setEnabled(True) - self.form.dropCutterDirSelect.setEnabled(True) - self.form.stepOver.setEnabled(True) - else: + if self.form.scanType.currentText() == "Planar": + self.form.cutPattern.setEnabled(True) self.form.boundBoxExtraOffsetX.setEnabled(False) self.form.boundBoxExtraOffsetY.setEnabled(False) - self.form.boundBoxSelect.setEnabled(False) self.form.dropCutterDirSelect.setEnabled(False) - self.form.stepOver.setEnabled(False) + else: + self.form.cutPattern.setEnabled(False) + self.form.boundBoxExtraOffsetX.setEnabled(True) + self.form.boundBoxExtraOffsetY.setEnabled(True) + self.form.dropCutterDirSelect.setEnabled(True) def registerSignalHandlers(self, obj): - self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility) + self.form.scanType.currentIndexChanged.connect(self.updateVisibility) Command = PathOpGui.SetupOperation('Surface', diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py new file mode 100644 index 0000000000..0362680580 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -0,0 +1,3481 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2019 Russell Johnson (russ4262) * +# * Copyright (c) 2019 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +from __future__ import print_function + +import FreeCAD +import MeshPart +import Path +import PathScripts.PathLog as PathLog +import PathScripts.PathUtils as PathUtils +import PathScripts.PathOp as PathOp + +from PySide import QtCore +import time +import math +import Part +import Draft + +if FreeCAD.GuiUp: + import FreeCADGui + +__title__ = "Path Waterline Operation" +__author__ = "russ4262 (Russell Johnson), sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Class and implementation of Mill Facing operation." + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) + + +# Qt translation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + +# OCL must be installed +try: + import ocl +except ImportError: + FreeCAD.Console.PrintError( + translate("Path_Waterline", "This operation requires OpenCamLib to be installed.") + "\n") + import sys + sys.exit(translate("Path_Waterline", "This operation requires OpenCamLib to be installed.")) + + +class ObjectWaterline(PathOp.ObjectOp): + '''Proxy object for Surfacing operation.''' + + def baseObject(self): + '''baseObject() ... returns super of receiver + Used to call base implementation in overwritten functions.''' + return super(self.__class__, self) + + def opFeatures(self, obj): + '''opFeatures(obj) ... return all standard features and edges based geomtries''' + return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces + + def initOperation(self, obj): + '''initPocketOp(obj) ... + Initialize the operation - property creation and property editor status.''' + self.initOpProperties(obj) + + # For debugging + if PathLog.getLevel(PathLog.thisModule()) != 4: + obj.setEditorMode('ShowTempObjects', 2) # hide + + if not hasattr(obj, 'DoNotSetDefaultValues'): + self.setEditorProperties(obj) + + def initOpProperties(self, obj): + '''initOpProperties(obj) ... create operation specific properties''' + PROPS = [ + ("App::PropertyBool", "ShowTempObjects", "Debug", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Show the temporary path construction objects when module is in DEBUG mode.")), + + ("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")), + ("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")), + + ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")), + ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")), + ("App::PropertyDistance", "BoundaryAdjustment", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")), + ("App::PropertyBool", "BoundaryEnforcement", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")), + ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")), + ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")), + ("App::PropertyBool", "InternalFeaturesCut", "Selected Geometry Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")), + + ("App::PropertyEnumeration", "Algorithm", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the algorithm to use: OCL Dropcutter*, or Experimental.")), + ("App::PropertyEnumeration", "BoundBox", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the overall boundary for the operation. ")), + ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the start point for circular cut patterns.")), + ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose location of the center point for starting the circular pattern.")), + ("App::PropertyEnumeration", "ClearLastLayer", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set to clear last layer in a `Multi-pass` operation.")), + ("App::PropertyEnumeration", "CutMode", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)")), + ("App::PropertyEnumeration", "CutPattern", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the geometric clearing pattern to use for the operation.")), + ("App::PropertyFloat", "CutPatternAngle", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The yaw angle used for certain clearing patterns")), + ("App::PropertyBool", "CutPatternReversed", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Reverse the cut order of the stepover paths. For circular cut patterns, begin at the outside and work toward the center.")), + ("App::PropertyDistance", "DepthOffset", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the Z-axis depth offset from the target surface.")), + ("App::PropertyEnumeration", "LayerMode", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Complete the operation in a single pass at depth, or mulitiple passes to final depth.")), + ("App::PropertyEnumeration", "ProfileEdges", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")), + ("App::PropertyDistance", "SampleInterval", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the sampling resolution. Smaller values quickly increase processing time.")), + ("App::PropertyPercent", "StepOver", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the stepover percentage, based on the tool's diameter.")), + + ("App::PropertyBool", "OptimizeLinearPaths", "Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")), + ("App::PropertyBool", "OptimizeStepOverTransitions", "Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")), + ("App::PropertyDistance", "GapThreshold", "Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")), + ("App::PropertyString", "GapSizes", "Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")), + + ("App::PropertyVectorDistance", "StartPoint", "Start Point", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The custom start point for the path of this operation")), + ("App::PropertyBool", "UseStartPoint", "Start Point", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point")) + ] + + missing = list() + for (prtyp, nm, grp, tt) in PROPS: + if not hasattr(obj, nm): + obj.addProperty(prtyp, nm, grp, tt) + missing.append(nm) + + # Set enumeration lists for enumeration properties + if len(missing) > 0: + ENUMS = self._propertyEnumerations() + for n in ENUMS: + if n in missing: + cmdStr = 'obj.{}={}'.format(n, ENUMS[n]) + exec(cmdStr) + + self.addedAllProperties = True + + def _propertyEnumerations(self): + # Enumeration lists for App::PropertyEnumeration properties + return { + 'Algorithm': ['OCL Dropcutter', 'Experimental'], + 'BoundBox': ['BaseBoundBox', 'Stock'], + 'CircularCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'], + 'ClearLastLayer': ['Off', 'Line', 'Circular', 'CircularZigZag', 'Offset', 'ZigZag'], + 'CutMode': ['Conventional', 'Climb'], + 'CutPattern': ['None', 'Line', 'Circular', 'CircularZigZag', 'Offset', 'ZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] + 'HandleMultipleFeatures': ['Collectively', 'Individually'], + 'LayerMode': ['Single-pass', 'Multi-pass'], + 'ProfileEdges': ['None', 'Only', 'First', 'Last'], + } + + def setEditorProperties(self, obj): + # Used to hide inputs in properties list + show = 0 + hide = 2 + cpShow = 0 + expMode = 0 + obj.setEditorMode('BoundaryEnforcement', hide) + obj.setEditorMode('ProfileEdges', hide) + obj.setEditorMode('InternalFeaturesAdjustment', hide) + obj.setEditorMode('InternalFeaturesCut', hide) + obj.setEditorMode('GapSizes', hide) + obj.setEditorMode('GapThreshold', hide) + obj.setEditorMode('AvoidLastX_Faces', hide) + obj.setEditorMode('AvoidLastX_InternalFeatures', hide) + obj.setEditorMode('BoundaryAdjustment', hide) + obj.setEditorMode('HandleMultipleFeatures', hide) + if hasattr(obj, 'EnableRotation'): + obj.setEditorMode('EnableRotation', hide) + if obj.CutPattern == 'None': + show = 2 + hide = 2 + cpShow = 2 + # elif obj.CutPattern in ['Line', 'ZigZag']: + # show = 0 + # hide = 2 + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + show = 2 # hide + hide = 0 # show + # obj.setEditorMode('StepOver', cpShow) + obj.setEditorMode('CutPatternAngle', show) + obj.setEditorMode('CircularCenterAt', hide) + obj.setEditorMode('CircularCenterCustom', hide) + if obj.Algorithm == 'Experimental': + expMode = 2 + obj.setEditorMode('SampleInterval', expMode) + obj.setEditorMode('LinearDeflection', expMode) + obj.setEditorMode('AngularDeflection', expMode) + + def onChanged(self, obj, prop): + if hasattr(self, 'addedAllProperties'): + if self.addedAllProperties is True: + if prop in ['Algorithm', 'CutPattern']: + self.setEditorProperties(obj) + + def opOnDocumentRestored(self, obj): + self.initOpProperties(obj) + + if PathLog.getLevel(PathLog.thisModule()) != 4: + obj.setEditorMode('ShowTempObjects', 2) # hide + else: + obj.setEditorMode('ShowTempObjects', 0) # show + + self.setEditorProperties(obj) + + def opSetDefaultValues(self, obj, job): + '''opSetDefaultValues(obj, job) ... initialize defaults''' + job = PathUtils.findParentJob(obj) + + obj.OptimizeLinearPaths = True + obj.InternalFeaturesCut = True + obj.OptimizeStepOverTransitions = False + obj.BoundaryEnforcement = True + obj.UseStartPoint = False + obj.AvoidLastX_InternalFeatures = True + obj.CutPatternReversed = False + obj.StartPoint.x = 0.0 + obj.StartPoint.y = 0.0 + obj.StartPoint.z = obj.ClearanceHeight.Value + obj.Algorithm = 'OCL Dropcutter' + obj.ProfileEdges = 'None' + obj.LayerMode = 'Single-pass' + obj.CutMode = 'Conventional' + obj.CutPattern = 'None' + obj.HandleMultipleFeatures = 'Collectively' # 'Individually' + obj.CircularCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom' + obj.GapSizes = 'No gaps identified.' + obj.ClearLastLayer = 'Off' + obj.StepOver = 100 + obj.CutPatternAngle = 0.0 + obj.DepthOffset.Value = 0.0 + obj.SampleInterval.Value = 1.0 + obj.BoundaryAdjustment.Value = 0.0 + obj.InternalFeaturesAdjustment.Value = 0.0 + obj.AvoidLastX_Faces = 0 + obj.CircularCenterCustom.x = 0.0 + obj.CircularCenterCustom.y = 0.0 + obj.CircularCenterCustom.z = 0.0 + obj.GapThreshold.Value = 0.005 + obj.LinearDeflection.Value = 0.0001 + obj.AngularDeflection.Value = 0.25 + # For debugging + obj.ShowTempObjects = False + + # need to overwrite the default depth calculations for facing + d = None + if job: + if job.Stock: + d = PathUtils.guessDepths(job.Stock.Shape, None) + PathLog.debug("job.Stock exists") + else: + PathLog.debug("job.Stock NOT exist") + else: + PathLog.debug("job NOT exist") + + if d is not None: + obj.OpFinalDepth.Value = d.final_depth + obj.OpStartDepth.Value = d.start_depth + else: + obj.OpFinalDepth.Value = -10 + obj.OpStartDepth.Value = 10 + + PathLog.debug('Default OpFinalDepth: {}'.format(obj.OpFinalDepth.Value)) + PathLog.debug('Defualt OpStartDepth: {}'.format(obj.OpStartDepth.Value)) + + def opApplyPropertyLimits(self, obj): + '''opApplyPropertyLimits(obj) ... Apply necessary limits to user input property values before performing main operation.''' + # Limit sample interval + if obj.SampleInterval.Value < 0.001: + obj.SampleInterval.Value = 0.001 + PathLog.error(translate('PathWaterline', 'Sample interval limits are 0.001 to 25.4 millimeters.')) + if obj.SampleInterval.Value > 25.4: + obj.SampleInterval.Value = 25.4 + PathLog.error(translate('PathWaterline', 'Sample interval limits are 0.001 to 25.4 millimeters.')) + + # Limit cut pattern angle + if obj.CutPatternAngle < -360.0: + obj.CutPatternAngle = 0.0 + PathLog.error(translate('PathWaterline', 'Cut pattern angle limits are +-360 degrees.')) + if obj.CutPatternAngle >= 360.0: + obj.CutPatternAngle = 0.0 + PathLog.error(translate('PathWaterline', 'Cut pattern angle limits are +- 360 degrees.')) + + # Limit StepOver to natural number percentage + if obj.StepOver > 100: + obj.StepOver = 100 + if obj.StepOver < 1: + obj.StepOver = 1 + + # Limit AvoidLastX_Faces to zero and positive values + if obj.AvoidLastX_Faces < 0: + obj.AvoidLastX_Faces = 0 + PathLog.error(translate('PathWaterline', 'AvoidLastX_Faces: Only zero or positive values permitted.')) + if obj.AvoidLastX_Faces > 100: + obj.AvoidLastX_Faces = 100 + PathLog.error(translate('PathWaterline', 'AvoidLastX_Faces: Avoid last X faces count limited to 100.')) + + def opExecute(self, obj): + '''opExecute(obj) ... process surface operation''' + PathLog.track() + + self.modelSTLs = list() + self.safeSTLs = list() + self.modelTypes = list() + self.boundBoxes = list() + self.profileShapes = list() + self.collectiveShapes = list() + self.individualShapes = list() + self.avoidShapes = list() + self.geoTlrnc = None + self.tempGroup = None + self.CutClimb = False + self.closedGap = False + self.gaps = [0.1, 0.2, 0.3] + CMDS = list() + modelVisibility = list() + FCAD = FreeCAD.ActiveDocument + + # Set debugging behavior + self.showDebugObjects = False # Set to true if you want a visual DocObjects created for some path construction objects + self.showDebugObjects = obj.ShowTempObjects + deleteTempsFlag = True # Set to False for debugging + if PathLog.getLevel(PathLog.thisModule()) == 4: + deleteTempsFlag = False + else: + self.showDebugObjects = False + + # mark beginning of operation and identify parent Job + PathLog.info('\nBegin Waterline operation...') + startTime = time.time() + + # Identify parent Job + JOB = PathUtils.findParentJob(obj) + if JOB is None: + PathLog.error(translate('PathWaterline', "No JOB")) + return + self.stockZMin = JOB.Stock.Shape.BoundBox.ZMin + + # set cut mode; reverse as needed + if obj.CutMode == 'Climb': + self.CutClimb = True + if obj.CutPatternReversed is True: + if self.CutClimb is True: + self.CutClimb = False + else: + self.CutClimb = True + + # Begin GCode for operation with basic information + # ... and move cutter to clearance height and startpoint + output = '' + if obj.Comment != '': + self.commandlist.append(Path.Command('N ({})'.format(str(obj.Comment)), {})) + self.commandlist.append(Path.Command('N ({})'.format(obj.Label), {})) + self.commandlist.append(Path.Command('N (Tool type: {})'.format(str(obj.ToolController.Tool.ToolType)), {})) + self.commandlist.append(Path.Command('N (Compensated Tool Path. Diameter: {})'.format(str(obj.ToolController.Tool.Diameter)), {})) + self.commandlist.append(Path.Command('N (Sample interval: {})'.format(str(obj.SampleInterval.Value)), {})) + self.commandlist.append(Path.Command('N (Step over %: {})'.format(str(obj.StepOver)), {})) + self.commandlist.append(Path.Command('N ({})'.format(output), {})) + self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + if obj.UseStartPoint is True: + self.commandlist.append(Path.Command('G0', {'X': obj.StartPoint.x, 'Y': obj.StartPoint.y, 'F': self.horizRapid})) + + # Instantiate additional class operation variables + self.resetOpVariables() + + # Impose property limits + self.opApplyPropertyLimits(obj) + + # Create temporary group for temporary objects, removing existing + # if self.showDebugObjects is True: + tempGroupName = 'tempPathWaterlineGroup' + if FCAD.getObject(tempGroupName): + for to in FCAD.getObject(tempGroupName).Group: + FCAD.removeObject(to.Name) + FCAD.removeObject(tempGroupName) # remove temp directory if already exists + if FCAD.getObject(tempGroupName + '001'): + for to in FCAD.getObject(tempGroupName + '001').Group: + FCAD.removeObject(to.Name) + FCAD.removeObject(tempGroupName + '001') # remove temp directory if already exists + tempGroup = FCAD.addObject('App::DocumentObjectGroup', tempGroupName) + tempGroupName = tempGroup.Name + self.tempGroup = tempGroup + tempGroup.purgeTouched() + # Add temp object to temp group folder with following code: + # ... self.tempGroup.addObject(OBJ) + + # Setup cutter for OCL and cutout value for operation - based on tool controller properties + self.cutter = self.setOclCutter(obj) + self.safeCutter = self.setOclCutter(obj, safe=True) + if self.cutter is False or self.safeCutter is False: + PathLog.error(translate('PathWaterline', "Canceling Waterline operation. Error creating OCL cutter.")) + return + toolDiam = self.cutter.getDiameter() + self.cutOut = (toolDiam * (float(obj.StepOver) / 100.0)) + self.radius = toolDiam / 2.0 + self.gaps = [toolDiam, toolDiam, toolDiam] + + # Get height offset values for later use + self.SafeHeightOffset = JOB.SetupSheet.SafeHeightOffset.Value + self.ClearHeightOffset = JOB.SetupSheet.ClearanceHeightOffset.Value + + # Set deflection values for mesh generation + useDGT = False + try: # try/except is for Path Jobs created before GeometryTolerance + self.geoTlrnc = JOB.GeometryTolerance.Value + if self.geoTlrnc == 0.0: + useDGT = True + except AttributeError as ee: + PathLog.warning('{}\nPlease set Job.GeometryTolerance to an acceptable value. Using PathPreferences.defaultGeometryTolerance().'.format(ee)) + useDGT = True + if useDGT: + import PathScripts.PathPreferences as PathPreferences + self.geoTlrnc = PathPreferences.defaultGeometryTolerance() + + # Calculate default depthparams for operation + self.depthParams = PathUtils.depth_params(obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown.Value, 0.0, obj.FinalDepth.Value) + self.midDep = (obj.StartDepth.Value + obj.FinalDepth.Value) / 2.0 + + # make circle for workplane + self.wpc = Part.makeCircle(2.0) + + # Save model visibilities for restoration + if FreeCAD.GuiUp: + for m in range(0, len(JOB.Model.Group)): + mNm = JOB.Model.Group[m].Name + modelVisibility.append(FreeCADGui.ActiveDocument.getObject(mNm).Visibility) + + # Setup STL, model type, and bound box containers for each model in Job + for m in range(0, len(JOB.Model.Group)): + M = JOB.Model.Group[m] + self.modelSTLs.append(False) + self.safeSTLs.append(False) + self.profileShapes.append(False) + # Set bound box + if obj.BoundBox == 'BaseBoundBox': + if M.TypeId.startswith('Mesh'): + self.modelTypes.append('M') # Mesh + self.boundBoxes.append(M.Mesh.BoundBox) + else: + self.modelTypes.append('S') # Solid + self.boundBoxes.append(M.Shape.BoundBox) + elif obj.BoundBox == 'Stock': + self.modelTypes.append('S') # Solid + self.boundBoxes.append(JOB.Stock.Shape.BoundBox) + + # ###### MAIN COMMANDS FOR OPERATION ###### + + # Begin processing obj.Base data and creating GCode + # Process selected faces, if available + pPM = self._preProcessModel(JOB, obj) + if pPM is False: + PathLog.error('Unable to pre-process obj.Base.') + else: + (FACES, VOIDS) = pPM + + # Create OCL.stl model objects + if obj.Algorithm == 'OCL Dropcutter': + self._prepareModelSTLs(JOB, obj) + PathLog.debug('obj.LinearDeflection.Value: {}'.format(obj.LinearDeflection.Value)) + PathLog.debug('obj.AngularDeflection.Value: {}'.format(obj.AngularDeflection.Value)) + + for m in range(0, len(JOB.Model.Group)): + Mdl = JOB.Model.Group[m] + if FACES[m] is False: + PathLog.error('No data for model base: {}'.format(JOB.Model.Group[m].Label)) + else: + if m > 0: + # Raise to clearance between models + CMDS.append(Path.Command('N (Transition to base: {}.)'.format(Mdl.Label))) + CMDS.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + PathLog.info('Working on Model.Group[{}]: {}'.format(m, Mdl.Label)) + # make stock-model-voidShapes STL model for avoidance detection on transitions + if obj.Algorithm == 'OCL Dropcutter': + self._makeSafeSTL(JOB, obj, m, FACES[m], VOIDS[m]) + # time.sleep(0.2) + # Process model/faces - OCL objects must be ready + CMDS.extend(self._processCutAreas(JOB, obj, m, FACES[m], VOIDS[m])) + + # Save gcode produced + self.commandlist.extend(CMDS) + + # ###### CLOSING COMMANDS FOR OPERATION ###### + + # Delete temporary objects + # Restore model visibilities for restoration + if FreeCAD.GuiUp: + FreeCADGui.ActiveDocument.getObject(tempGroupName).Visibility = False + for m in range(0, len(JOB.Model.Group)): + M = JOB.Model.Group[m] + M.Visibility = modelVisibility[m] + + if deleteTempsFlag is True: + for to in tempGroup.Group: + if hasattr(to, 'Group'): + for go in to.Group: + FCAD.removeObject(go.Name) + FCAD.removeObject(to.Name) + FCAD.removeObject(tempGroupName) + else: + if len(tempGroup.Group) == 0: + FCAD.removeObject(tempGroupName) + else: + tempGroup.purgeTouched() + + # Provide user feedback for gap sizes + gaps = list() + for g in self.gaps: + if g != toolDiam: + gaps.append(g) + if len(gaps) > 0: + obj.GapSizes = '{} mm'.format(gaps) + else: + if self.closedGap is True: + obj.GapSizes = 'Closed gaps < Gap Threshold.' + else: + obj.GapSizes = 'No gaps identified.' + + # clean up class variables + self.resetOpVariables() + self.deleteOpVariables() + + self.modelSTLs = None + self.safeSTLs = None + self.modelTypes = None + self.boundBoxes = None + self.gaps = None + self.closedGap = None + self.SafeHeightOffset = None + self.ClearHeightOffset = None + self.depthParams = None + self.midDep = None + self.wpc = None + del self.modelSTLs + del self.safeSTLs + del self.modelTypes + del self.boundBoxes + del self.gaps + del self.closedGap + del self.SafeHeightOffset + del self.ClearHeightOffset + del self.depthParams + del self.midDep + del self.wpc + + execTime = time.time() - startTime + PathLog.info('Operation time: {} sec.'.format(execTime)) + + return True + + # Methods for constructing the cut area + def _preProcessModel(self, JOB, obj): + PathLog.debug('_preProcessModel()') + + FACES = list() + VOIDS = list() + fShapes = list() + vShapes = list() + preProcEr = translate('PathWaterline', 'Error pre-processing Face') + warnFinDep = translate('PathWaterline', 'Final Depth might need to be lower. Internal features detected in Face') + GRP = JOB.Model.Group + lenGRP = len(GRP) + + # Crete place holders for each base model in Job + for m in range(0, lenGRP): + FACES.append(False) + VOIDS.append(False) + fShapes.append(False) + vShapes.append(False) + + # The user has selected subobjects from the base. Pre-Process each. + if obj.Base and len(obj.Base) > 0: + PathLog.debug(' -obj.Base exists. Pre-processing for selected faces.') + + (FACES, VOIDS) = self._identifyFacesAndVoids(JOB, obj, FACES, VOIDS) + + # Cycle through each base model, processing faces for each + for m in range(0, lenGRP): + base = GRP[m] + (mFS, mVS, mPS) = self._preProcessFacesAndVoids(obj, base, m, FACES, VOIDS) + fShapes[m] = mFS + vShapes[m] = mVS + self.profileShapes[m] = mPS + else: + PathLog.debug(' -No obj.Base data.') + for m in range(0, lenGRP): + self.modelSTLs[m] = True + + # Process each model base, as a whole, as needed + # PathLog.debug(' -Pre-processing all models in Job.') + for m in range(0, lenGRP): + if fShapes[m] is False: + PathLog.debug(' -Pre-processing {} as a whole.'.format(GRP[m].Label)) + if obj.BoundBox == 'BaseBoundBox': + base = GRP[m] + elif obj.BoundBox == 'Stock': + base = JOB.Stock + + pPEB = self._preProcessEntireBase(obj, base, m) + if pPEB is False: + PathLog.error(' -Failed to pre-process base as a whole.') + else: + (fcShp, prflShp) = pPEB + if fcShp is not False: + if fcShp is True: + PathLog.debug(' -fcShp is True.') + fShapes[m] = True + else: + fShapes[m] = [fcShp] + if prflShp is not False: + if fcShp is not False: + PathLog.debug('vShapes[{}]: {}'.format(m, vShapes[m])) + if vShapes[m] is not False: + PathLog.debug(' -Cutting void from base profile shape.') + adjPS = prflShp.cut(vShapes[m][0]) + self.profileShapes[m] = [adjPS] + else: + PathLog.debug(' -vShapes[m] is False.') + self.profileShapes[m] = [prflShp] + else: + PathLog.debug(' -Saving base profile shape.') + self.profileShapes[m] = [prflShp] + PathLog.debug('self.profileShapes[{}]: {}'.format(m, self.profileShapes[m])) + # Efor + + return (fShapes, vShapes) + + def _identifyFacesAndVoids(self, JOB, obj, F, V): + TUPS = list() + GRP = JOB.Model.Group + lenGRP = len(GRP) + + # Separate selected faces into (base, face) tuples and flag model(s) for STL creation + for (bs, SBS) in obj.Base: + for sb in SBS: + # Flag model for STL creation + mdlIdx = None + for m in range(0, lenGRP): + if bs is GRP[m]: + self.modelSTLs[m] = True + mdlIdx = m + break + TUPS.append((mdlIdx, bs, sb)) # (model idx, base, sub) + + # Apply `AvoidXFaces` value + faceCnt = len(TUPS) + add = faceCnt - obj.AvoidLastX_Faces + for bst in range(0, faceCnt): + (m, base, sub) = TUPS[bst] + shape = getattr(base.Shape, sub) + if isinstance(shape, Part.Face): + faceIdx = int(sub[4:]) - 1 + if bst < add: + if F[m] is False: + F[m] = list() + F[m].append((shape, faceIdx)) + else: + if V[m] is False: + V[m] = list() + V[m].append((shape, faceIdx)) + return (F, V) + + def _preProcessFacesAndVoids(self, obj, base, m, FACES, VOIDS): + mFS = False + mVS = False + mPS = False + mIFS = list() + BB = base.Shape.BoundBox + + if FACES[m] is not False: + isHole = False + if obj.HandleMultipleFeatures == 'Collectively': + cont = True + fsL = list() # face shape list + ifL = list() # avoid shape list + outFCS = list() + + # Get collective envelope slice of selected faces + for (fcshp, fcIdx) in FACES[m]: + fNum = fcIdx + 1 + fsL.append(fcshp) + gFW = self._getFaceWires(base, fcshp, fcIdx) + if gFW is False: + PathLog.debug('Failed to get wires from Face{}'.format(fNum)) + elif gFW[0] is False: + PathLog.debug('Cannot process Face{}. Check that it has horizontal surface exposure.'.format(fNum)) + else: + ((otrFace, raised), intWires) = gFW + outFCS.append(otrFace) + if obj.InternalFeaturesCut is False: + if intWires is not False: + for (iFace, rsd) in intWires: + ifL.append(iFace) + + PathLog.debug('Attempting to get cross-section of collective faces.') + if len(outFCS) == 0: + PathLog.error('Cannot process selected faces. Check horizontal surface exposure.'.format(fNum)) + cont = False + else: + cfsL = Part.makeCompound(outFCS) + + # Handle profile edges request + if cont is True and obj.ProfileEdges != 'None': + ofstVal = self._calculateOffsetValue(obj, isHole) + psOfst = self._extractFaceOffset(obj, cfsL, ofstVal) + if psOfst is not False: + mPS = [psOfst] + if obj.ProfileEdges == 'Only': + mFS = True + cont = False + else: + PathLog.error(' -Failed to create profile geometry for selected faces.') + cont = False + + if cont is True: + if self.showDebugObjects is True: + T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCollectiveShape') + T.Shape = cfsL + T.purgeTouched() + self.tempGroup.addObject(T) + + ofstVal = self._calculateOffsetValue(obj, isHole) + faceOfstShp = self._extractFaceOffset(obj, cfsL, ofstVal) + if faceOfstShp is False: + PathLog.error(' -Failed to create offset face.') + cont = False + + if cont is True: + lenIfL = len(ifL) + if obj.InternalFeaturesCut is False: + if lenIfL == 0: + PathLog.debug(' -No internal features saved.') + else: + if lenIfL == 1: + casL = ifL[0] + else: + casL = Part.makeCompound(ifL) + if self.showDebugObjects is True: + C = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCompoundIntFeat') + C.Shape = casL + C.purgeTouched() + self.tempGroup.addObject(C) + ofstVal = self._calculateOffsetValue(obj, isHole=True) + intOfstShp = self._extractFaceOffset(obj, casL, ofstVal) + mIFS.append(intOfstShp) + # faceOfstShp = faceOfstShp.cut(intOfstShp) + + mFS = [faceOfstShp] + # Eif + + elif obj.HandleMultipleFeatures == 'Individually': + for (fcshp, fcIdx) in FACES[m]: + cont = True + fsL = list() # face shape list + ifL = list() # avoid shape list + fNum = fcIdx + 1 + outerFace = False + + gFW = self._getFaceWires(base, fcshp, fcIdx) + if gFW is False: + PathLog.debug('Failed to get wires from Face{}'.format(fNum)) + cont = False + elif gFW[0] is False: + PathLog.debug('Cannot process Face{}. Check that it has horizontal surface exposure.'.format(fNum)) + cont = False + outerFace = False + else: + ((otrFace, raised), intWires) = gFW + outerFace = otrFace + if obj.InternalFeaturesCut is False: + if intWires is not False: + for (iFace, rsd) in intWires: + ifL.append(iFace) + + if outerFace is not False: + PathLog.debug('Attempting to create offset face of Face{}'.format(fNum)) + + if obj.ProfileEdges != 'None': + ofstVal = self._calculateOffsetValue(obj, isHole) + psOfst = self._extractFaceOffset(obj, outerFace, ofstVal) + if psOfst is not False: + if mPS is False: + mPS = list() + mPS.append(psOfst) + if obj.ProfileEdges == 'Only': + if mFS is False: + mFS = list() + mFS.append(True) + cont = False + else: + PathLog.error(' -Failed to create profile geometry for Face{}.'.format(fNum)) + cont = False + + if cont is True: + ofstVal = self._calculateOffsetValue(obj, isHole) + faceOfstShp = self._extractFaceOffset(obj, slc, ofstVal) + + lenIfl = len(ifL) + if obj.InternalFeaturesCut is False and lenIfl > 0: + if lenIfl == 1: + casL = ifL[0] + else: + casL = Part.makeCompound(ifL) + + ofstVal = self._calculateOffsetValue(obj, isHole=True) + intOfstShp = self._extractFaceOffset(obj, casL, ofstVal) + mIFS.append(intOfstShp) + # faceOfstShp = faceOfstShp.cut(intOfstShp) + + if mFS is False: + mFS = list() + mFS.append(faceOfstShp) + # Eif + # Efor + # Eif + # Eif + + if len(mIFS) > 0: + if mVS is False: + mVS = list() + for ifs in mIFS: + mVS.append(ifs) + + if VOIDS[m] is not False: + PathLog.debug('Processing avoid faces.') + cont = True + isHole = False + outFCS = list() + intFEAT = list() + + for (fcshp, fcIdx) in VOIDS[m]: + fNum = fcIdx + 1 + gFW = self._getFaceWires(base, fcshp, fcIdx) + if gFW is False: + PathLog.debug('Failed to get wires from avoid Face{}'.format(fNum)) + cont = False + else: + ((otrFace, raised), intWires) = gFW + outFCS.append(otrFace) + if obj.AvoidLastX_InternalFeatures is False: + if intWires is not False: + for (iFace, rsd) in intWires: + intFEAT.append(iFace) + + lenOtFcs = len(outFCS) + if lenOtFcs == 0: + cont = False + else: + if lenOtFcs == 1: + avoid = outFCS[0] + else: + avoid = Part.makeCompound(outFCS) + + if self.showDebugObjects is True: + PathLog.debug('*** tmpAvoidArea') + P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidEnvelope') + P.Shape = avoid + # P.recompute() + P.purgeTouched() + self.tempGroup.addObject(P) + + if cont is True: + if self.showDebugObjects is True: + PathLog.debug('*** tmpVoidCompound') + P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidCompound') + P.Shape = avoid + # P.recompute() + P.purgeTouched() + self.tempGroup.addObject(P) + ofstVal = self._calculateOffsetValue(obj, isHole, isVoid=True) + avdOfstShp = self._extractFaceOffset(obj, avoid, ofstVal) + if avdOfstShp is False: + PathLog.error('Failed to create collective offset avoid face.') + cont = False + + if cont is True: + avdShp = avdOfstShp + + if obj.AvoidLastX_InternalFeatures is False and len(intFEAT) > 0: + if len(intFEAT) > 1: + ifc = Part.makeCompound(intFEAT) + else: + ifc = intFEAT[0] + ofstVal = self._calculateOffsetValue(obj, isHole=True) + ifOfstShp = self._extractFaceOffset(obj, ifc, ofstVal) + if ifOfstShp is False: + PathLog.error('Failed to create collective offset avoid internal features.') + else: + avdShp = avdOfstShp.cut(ifOfstShp) + + if mVS is False: + mVS = list() + mVS.append(avdShp) + + + return (mFS, mVS, mPS) + + def _getFaceWires(self, base, fcshp, fcIdx): + outFace = False + INTFCS = list() + fNum = fcIdx + 1 + # preProcEr = translate('PathWaterline', 'Error pre-processing Face') + warnFinDep = translate('PathWaterline', 'Final Depth might need to be lower. Internal features detected in Face') + + PathLog.debug('_getFaceWires() from Face{}'.format(fNum)) + WIRES = self._extractWiresFromFace(base, fcshp) + if WIRES is False: + PathLog.error('Failed to extract wires from Face{}'.format(fNum)) + return False + + # Process remaining internal features, adding to FCS list + lenW = len(WIRES) + for w in range(0, lenW): + (wire, rsd) = WIRES[w] + PathLog.debug('Processing Wire{} in Face{}. isRaised: {}'.format(w + 1, fNum, rsd)) + if wire.isClosed() is False: + PathLog.debug(' -wire is not closed.') + else: + slc = self._flattenWireToFace(wire) + if slc is False: + PathLog.error('FAILED to identify horizontal exposure on Face{}.'.format(fNum)) + else: + if w == 0: + outFace = (slc, rsd) + else: + # add to VOIDS so cutter avoids area. + PathLog.warning(warnFinDep + str(fNum) + '.') + INTFCS.append((slc, rsd)) + if len(INTFCS) == 0: + return (outFace, False) + else: + return (outFace, INTFCS) + + def _preProcessEntireBase(self, obj, base, m): + cont = True + isHole = False + prflShp = False + # Create envelope, extract cross-section and make offset co-planar shape + # baseEnv = PathUtils.getEnvelope(base.Shape, subshape=None, depthparams=self.depthParams) + + try: + baseEnv = PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthParams) # Produces .Shape + except Exception as ee: + PathLog.error(str(ee)) + shell = base.Shape.Shells[0] + solid = Part.makeSolid(shell) + try: + baseEnv = PathUtils.getEnvelope(partshape=solid, subshape=None, depthparams=self.depthParams) # Produces .Shape + except Exception as eee: + PathLog.error(str(eee)) + cont = False + # time.sleep(0.2) + + if cont is True: + csFaceShape = self._getShapeSlice(baseEnv) + if csFaceShape is False: + PathLog.debug('_getShapeSlice(baseEnv) failed') + csFaceShape = self._getCrossSection(baseEnv) + if csFaceShape is False: + PathLog.debug('_getCrossSection(baseEnv) failed') + csFaceShape = self._getSliceFromEnvelope(baseEnv) + if csFaceShape is False: + PathLog.error('Failed to slice baseEnv shape.') + cont = False + + if cont is True and obj.ProfileEdges != 'None': + PathLog.debug(' -Attempting profile geometry for model base.') + ofstVal = self._calculateOffsetValue(obj, isHole) + psOfst = self._extractFaceOffset(obj, csFaceShape, ofstVal) + if psOfst is not False: + if obj.ProfileEdges == 'Only': + return (True, psOfst) + prflShp = psOfst + else: + PathLog.error(' -Failed to create profile geometry.') + cont = False + + if cont is True: + ofstVal = self._calculateOffsetValue(obj, isHole) + faceOffsetShape = self._extractFaceOffset(obj, csFaceShape, ofstVal) + if faceOffsetShape is False: + PathLog.error('_extractFaceOffset() failed.') + else: + faceOffsetShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - faceOffsetShape.BoundBox.ZMin)) + return (faceOffsetShape, prflShp) + return False + + def _extractWiresFromFace(self, base, fc): + '''_extractWiresFromFace(base, fc) ... + Attempts to return all closed wires within a parent face, including the outer most wire of the parent. + The wires are ordered by area. Each wire is also categorized as a pocket(False) or raised protrusion(True). + ''' + PathLog.debug('_extractWiresFromFace()') + + WIRES = list() + lenWrs = len(fc.Wires) + PathLog.debug(' -Wire count: {}'.format(lenWrs)) + + def index0(tup): + return tup[0] + + # Cycle through wires in face + for w in range(0, lenWrs): + PathLog.debug(' -Analyzing wire_{}'.format(w + 1)) + wire = fc.Wires[w] + checkEdges = False + cont = True + + # Check for closed edges (circles, ellipses, etc...) + for E in wire.Edges: + if E.isClosed() is True: + checkEdges = True + break + + if checkEdges is True: + PathLog.debug(' -checkEdges is True') + for e in range(0, len(wire.Edges)): + edge = wire.Edges[e] + if edge.isClosed() is True and edge.Mass > 0.01: + PathLog.debug(' -Found closed edge') + raised = False + ip = self._isPocket(base, fc, edge) + if ip is False: + raised = True + ebb = edge.BoundBox + eArea = ebb.XLength * ebb.YLength + F = Part.Face(Part.Wire([edge])) + WIRES.append((eArea, F.Wires[0], raised)) + cont = False + + if cont is True: + PathLog.debug(' -cont is True') + # If only one wire and not checkEdges, return first wire + if lenWrs == 1: + return [(wire, False)] + + raised = False + wbb = wire.BoundBox + wArea = wbb.XLength * wbb.YLength + if w > 0: + ip = self._isPocket(base, fc, wire) + if ip is False: + raised = True + WIRES.append((wArea, Part.Wire(wire.Edges), raised)) + + nf = len(WIRES) + if nf > 0: + PathLog.debug(' -number of wires found is {}'.format(nf)) + if nf == 1: + (area, W, raised) = WIRES[0] + return [(W, raised)] + else: + sortedWIRES = sorted(WIRES, key=index0, reverse=True) + return [(W, raised) for (area, W, raised) in sortedWIRES] # outer, then inner by area size + + return False + + def _calculateOffsetValue(self, obj, isHole, isVoid=False): + '''_calculateOffsetValue(obj, isHole, isVoid) ... internal function. + Calculate the offset for the Path.Area() function.''' + JOB = PathUtils.findParentJob(obj) + tolrnc = JOB.GeometryTolerance.Value + + if isVoid is False: + if isHole is True: + offset = -1 * obj.InternalFeaturesAdjustment.Value + offset += self.radius # (self.radius + (tolrnc / 10.0)) + else: + offset = -1 * obj.BoundaryAdjustment.Value + if obj.BoundaryEnforcement is True: + offset += self.radius # (self.radius + (tolrnc / 10.0)) + else: + offset -= self.radius # (self.radius + (tolrnc / 10.0)) + offset = 0.0 - offset + else: + offset = -1 * obj.BoundaryAdjustment.Value + offset += self.radius # (self.radius + (tolrnc / 10.0)) + + return offset + + def _extractFaceOffset(self, obj, fcShape, offset, makeComp=True): + '''_extractFaceOffset(fcShape, offset) ... internal function. + Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. + Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' + PathLog.debug('_extractFaceOffset()') + + if fcShape.BoundBox.ZMin != 0.0: + fcShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - fcShape.BoundBox.ZMin)) + + areaParams = {} + areaParams['Offset'] = offset + areaParams['Fill'] = 1 # 1 + areaParams['Coplanar'] = 0 + areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections + areaParams['Reorient'] = True + areaParams['OpenMode'] = 0 + areaParams['MaxArcPoints'] = 400 # 400 + areaParams['Project'] = True + # areaParams['Tolerance'] = 0.001 + + area = Path.Area() # Create instance of Area() class object + # area.setPlane(PathUtils.makeWorkplane(fcShape)) # Set working plane + area.setPlane(PathUtils.makeWorkplane(self.wpc)) # Set working plane to normal at Z=1 + area.add(fcShape) + area.setParams(**areaParams) # set parameters + + # Save parameters for debugging + # obj.AreaParams = str(area.getParams()) + # PathLog.debug("Area with params: {}".format(area.getParams())) + + offsetShape = area.getShape() + wCnt = len(offsetShape.Wires) + if wCnt == 0: + return False + elif wCnt == 1: + ofstFace = Part.Face(offsetShape.Wires[0]) + if not makeComp: + ofstFace = [ofstFace] + else: + W = list() + for wr in offsetShape.Wires: + W.append(Part.Face(wr)) + if makeComp: + ofstFace = Part.makeCompound(W) + else: + ofstFace = W + + return ofstFace # offsetShape + + def _isPocket(self, b, f, w): + '''_isPocket(b, f, w)... + Attempts to determine if the wire(w) in face(f) of base(b) is a pocket or raised protrusion. + Returns True if pocket, False if raised protrusion.''' + e = w.Edges[0] + for fi in range(0, len(b.Shape.Faces)): + face = b.Shape.Faces[fi] + for ei in range(0, len(face.Edges)): + edge = face.Edges[ei] + if e.isSame(edge) is True: + if f is face: + # Alternative: run loop to see if all edges are same + pass # same source face, look for another + else: + if face.CenterOfMass.z < f.CenterOfMass.z: + return True + return False + + def _flattenWireToFace(self, wire): + PathLog.debug('_flattenWireToFace()') + if wire.isClosed() is False: + PathLog.debug(' -wire.isClosed() is False') + return False + + # If wire is planar horizontal, convert to a face and return + if wire.BoundBox.ZLength == 0.0: + slc = Part.Face(wire) + return slc + + # Attempt to create a new wire for manipulation, if not, use original + newWire = Part.Wire(wire.Edges) + if newWire.isClosed() is True: + nWire = newWire + else: + PathLog.debug(' -newWire.isClosed() is False') + nWire = wire + + # Attempt extrusion, and then try a manual slice and then cross-section + ext = self._getExtrudedShape(nWire) + if ext is False: + PathLog.debug('_getExtrudedShape() failed') + else: + slc = self._getShapeSlice(ext) + if slc is not False: + return slc + cs = self._getCrossSection(ext, True) + if cs is not False: + return cs + + # Attempt creating an envelope, and then try a manual slice and then cross-section + env = self._getShapeEnvelope(nWire) + if env is False: + PathLog.debug('_getShapeEnvelope() failed') + else: + slc = self._getShapeSlice(env) + if slc is not False: + return slc + cs = self._getCrossSection(env, True) + if cs is not False: + return cs + + # Attempt creating a projection + slc = self._getProjectedFace(nWire) + if slc is False: + PathLog.debug('_getProjectedFace() failed') + else: + return slc + + return False + + def _getExtrudedShape(self, wire): + PathLog.debug('_getExtrudedShape()') + wBB = wire.BoundBox + extFwd = math.floor(2.0 * wBB.ZLength) + 10.0 + + try: + # slower, but renders collective faces correctly. Method 5 in TESTING + shell = wire.extrude(FreeCAD.Vector(0.0, 0.0, extFwd)) + except Exception as ee: + PathLog.error(' -extrude wire failed: \n{}'.format(ee)) + return False + + SHP = Part.makeSolid(shell) + return SHP + + def _getShapeSlice(self, shape): + PathLog.debug('_getShapeSlice()') + + bb = shape.BoundBox + mid = (bb.ZMin + bb.ZMax) / 2.0 + xmin = bb.XMin - 1.0 + xmax = bb.XMax + 1.0 + ymin = bb.YMin - 1.0 + ymax = bb.YMax + 1.0 + p1 = FreeCAD.Vector(xmin, ymin, mid) + p2 = FreeCAD.Vector(xmax, ymin, mid) + p3 = FreeCAD.Vector(xmax, ymax, mid) + p4 = FreeCAD.Vector(xmin, ymax, mid) + + e1 = Part.makeLine(p1, p2) + e2 = Part.makeLine(p2, p3) + e3 = Part.makeLine(p3, p4) + e4 = Part.makeLine(p4, p1) + face = Part.Face(Part.Wire([e1, e2, e3, e4])) + fArea = face.BoundBox.XLength * face.BoundBox.YLength # face.Wires[0].Area + sArea = shape.BoundBox.XLength * shape.BoundBox.YLength + midArea = (fArea + sArea) / 2.0 + + slcShp = shape.common(face) + slcArea = slcShp.BoundBox.XLength * slcShp.BoundBox.YLength + + if slcArea < midArea: + for W in slcShp.Wires: + if W.isClosed() is False: + PathLog.debug(' -wire.isClosed() is False') + return False + if len(slcShp.Wires) == 1: + wire = slcShp.Wires[0] + slc = Part.Face(wire) + slc.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - slc.BoundBox.ZMin)) + return slc + else: + fL = list() + for W in slcShp.Wires: + slc = Part.Face(W) + slc.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - slc.BoundBox.ZMin)) + fL.append(slc) + comp = Part.makeCompound(fL) + if self.showDebugObjects is True: + PathLog.debug('*** tmpSliceCompound') + P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpSliceCompound') + P.Shape = comp + # P.recompute() + P.purgeTouched() + self.tempGroup.addObject(P) + return comp + + PathLog.debug(' -slcArea !< midArea') + PathLog.debug(' -slcShp.Edges count: {}. Might be a vertically oriented face.'.format(len(slcShp.Edges))) + return False + + def _getProjectedFace(self, wire): + PathLog.debug('_getProjectedFace()') + F = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpProjectionWire') + F.Shape = wire + F.purgeTouched() + self.tempGroup.addObject(F) + try: + prj = Draft.makeShape2DView(F, FreeCAD.Vector(0, 0, 1)) + prj.recompute() + prj.purgeTouched() + self.tempGroup.addObject(prj) + except Exception as ee: + PathLog.error(str(ee)) + return False + else: + pWire = Part.Wire(prj.Shape.Edges) + if pWire.isClosed() is False: + # PathLog.debug(' -pWire.isClosed() is False') + return False + slc = Part.Face(pWire) + slc.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - slc.BoundBox.ZMin)) + return slc + return False + + def _getCrossSection(self, shape, withExtrude=False): + PathLog.debug('_getCrossSection()') + wires = list() + bb = shape.BoundBox + mid = (bb.ZMin + bb.ZMax) / 2.0 + + for i in shape.slice(FreeCAD.Vector(0, 0, 1), mid): + wires.append(i) + + if len(wires) > 0: + comp = Part.Compound(wires) # produces correct cross-section wire ! + comp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - comp.BoundBox.ZMin)) + csWire = comp.Wires[0] + if csWire.isClosed() is False: + PathLog.debug(' -comp.Wires[0] is not closed') + return False + if withExtrude is True: + ext = self._getExtrudedShape(csWire) + CS = self._getShapeSlice(ext) + else: + CS = Part.Face(csWire) + CS.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - CS.BoundBox.ZMin)) + return CS + else: + PathLog.debug(' -No wires from .slice() method') + + return False + + def _getShapeEnvelope(self, shape): + PathLog.debug('_getShapeEnvelope()') + + wBB = shape.BoundBox + extFwd = wBB.ZLength + 10.0 + minz = wBB.ZMin + maxz = wBB.ZMin + extFwd + stpDwn = (maxz - minz) / 4.0 + dep_par = PathUtils.depth_params(maxz + 5.0, maxz + 3.0, maxz, stpDwn, 0.0, minz) + + try: + env = PathUtils.getEnvelope(partshape=shape, depthparams=dep_par) # Produces .Shape + except Exception as ee: + PathLog.error('try: PathUtils.getEnvelope() failed.\n' + str(ee)) + return False + else: + return env + + return False + + def _getSliceFromEnvelope(self, env): + PathLog.debug('_getSliceFromEnvelope()') + eBB = env.BoundBox + extFwd = eBB.ZLength + 10.0 + maxz = eBB.ZMin + extFwd + + maxMax = env.Edges[0].BoundBox.ZMin + emax = math.floor(maxz - 1.0) + E = list() + for e in range(0, len(env.Edges)): + emin = env.Edges[e].BoundBox.ZMin + if emin > emax: + E.append(env.Edges[e]) + tf = Part.Face(Part.Wire(Part.__sortEdges__(E))) + tf.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - tf.BoundBox.ZMin)) + + return tf + + def _prepareModelSTLs(self, JOB, obj): + PathLog.debug('_prepareModelSTLs()') + for m in range(0, len(JOB.Model.Group)): + M = JOB.Model.Group[m] + + # PathLog.debug(f" -self.modelTypes[{m}] == 'M'") + if self.modelTypes[m] == 'M': + #TODO: test if this works + facets = M.Mesh.Facets.Points + else: + facets = Path.getFacets(M.Shape) + + if self.modelSTLs[m] is True: + stl = ocl.STLSurf() + + for tri in facets: + t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2] + obj.DepthOffset.Value), + ocl.Point(tri[1][0], tri[1][1], tri[1][2] + obj.DepthOffset.Value), + ocl.Point(tri[2][0], tri[2][1], tri[2][2] + obj.DepthOffset.Value)) + stl.addTriangle(t) + self.modelSTLs[m] = stl + return + + def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes): + '''_makeSafeSTL(JOB, obj, mdlIdx, faceShapes, voidShapes)... + Creates and OCL.stl object with combined data with waste stock, + model, and avoided faces. Travel lines can be checked against this + STL object to determine minimum travel height to clear stock and model.''' + PathLog.debug('_makeSafeSTL()') + + fuseShapes = list() + Mdl = JOB.Model.Group[mdlIdx] + FCAD = FreeCAD.ActiveDocument + mBB = Mdl.Shape.BoundBox + sBB = JOB.Stock.Shape.BoundBox + + # add Model shape to safeSTL shape + fuseShapes.append(Mdl.Shape) + + if obj.BoundBox == 'BaseBoundBox': + cont = False + extFwd = (sBB.ZLength) + zmin = mBB.ZMin + zmax = mBB.ZMin + extFwd + stpDwn = (zmax - zmin) / 4.0 + dep_par = PathUtils.depth_params(zmax + 5.0, zmax + 3.0, zmax, stpDwn, 0.0, zmin) + + try: + envBB = PathUtils.getEnvelope(partshape=Mdl.Shape, depthparams=dep_par) # Produces .Shape + cont = True + except Exception as ee: + PathLog.error(str(ee)) + shell = Mdl.Shape.Shells[0] + solid = Part.makeSolid(shell) + try: + envBB = PathUtils.getEnvelope(partshape=solid, depthparams=dep_par) # Produces .Shape + cont = True + except Exception as eee: + PathLog.error(str(eee)) + + if cont is True: + stckWst = JOB.Stock.Shape.cut(envBB) + if obj.BoundaryAdjustment > 0.0: + cmpndFS = Part.makeCompound(faceShapes) + baBB = PathUtils.getEnvelope(partshape=cmpndFS, depthparams=self.depthParams) # Produces .Shape + adjStckWst = stckWst.cut(baBB) + else: + adjStckWst = stckWst + fuseShapes.append(adjStckWst) + else: + PathLog.warning('Path transitions might not avoid the model. Verify paths.') + # time.sleep(0.3) + + else: + # If boundbox is Job.Stock, add hidden pad under stock as base plate + toolDiam = self.cutter.getDiameter() + zMin = JOB.Stock.Shape.BoundBox.ZMin + xMin = JOB.Stock.Shape.BoundBox.XMin - toolDiam + yMin = JOB.Stock.Shape.BoundBox.YMin - toolDiam + bL = JOB.Stock.Shape.BoundBox.XLength + (2 * toolDiam) + bW = JOB.Stock.Shape.BoundBox.YLength + (2 * toolDiam) + bH = 1.0 + crnr = FreeCAD.Vector(xMin, yMin, zMin - 1.0) + B = Part.makeBox(bL, bW, bH, crnr, FreeCAD.Vector(0, 0, 1)) + fuseShapes.append(B) + + if voidShapes is not False: + voidComp = Part.makeCompound(voidShapes) + voidEnv = PathUtils.getEnvelope(partshape=voidComp, depthparams=self.depthParams) # Produces .Shape + fuseShapes.append(voidEnv) + + fused = Part.makeCompound(fuseShapes) + + if self.showDebugObjects is True: + T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'safeSTLShape') + T.Shape = fused + T.purgeTouched() + self.tempGroup.addObject(T) + + facets = Path.getFacets(fused) + + stl = ocl.STLSurf() + for tri in facets: + t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]), + ocl.Point(tri[1][0], tri[1][1], tri[1][2]), + ocl.Point(tri[2][0], tri[2][1], tri[2][2])) + stl.addTriangle(t) + + self.safeSTLs[mdlIdx] = stl + + def _processCutAreas(self, JOB, obj, mdlIdx, FCS, VDS): + '''_processCutAreas(JOB, obj, mdlIdx, FCS, VDS)... + This method applies any avoided faces or regions to the selected faces. + It then calls the correct method.''' + PathLog.debug('_processCutAreas()') + + final = list() + base = JOB.Model.Group[mdlIdx] + + # Process faces Collectively or Individually + if obj.HandleMultipleFeatures == 'Collectively': + if FCS is True: + COMP = False + else: + ADD = Part.makeCompound(FCS) + if VDS is not False: + DEL = Part.makeCompound(VDS) + COMP = ADD.cut(DEL) + else: + COMP = ADD + + final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + if obj.Algorithm == 'OCL Dropcutter': + final.extend(self._oclWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + else: + final.extend(self._experimentalWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + + elif obj.HandleMultipleFeatures == 'Individually': + for fsi in range(0, len(FCS)): + fShp = FCS[fsi] + # self.deleteOpVariables(all=False) + self.resetOpVariables(all=False) + + if fShp is True: + COMP = False + else: + ADD = Part.makeCompound([fShp]) + if VDS is not False: + DEL = Part.makeCompound(VDS) + COMP = ADD.cut(DEL) + else: + COMP = ADD + + final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + if obj.Algorithm == 'OCL Dropcutter': + final.extend(self._oclWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + else: + final.extend(self._experimentalWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + COMP = None + # Eif + + return final + + # Methods for creating path geometry + def _planarMakePathGeom(self, obj, faceShp): + '''_planarMakePathGeom(obj, faceShp)... + Creates the line/arc cut pattern geometry and returns the intersection with the received faceShp. + The resulting intersecting line/arc geometries are then converted to lines or arcs for OCL.''' + PathLog.debug('_planarMakePathGeom()') + GeoSet = list() + + # Apply drop cutter extra offset and set the max and min XY area of the operation + xmin = faceShp.BoundBox.XMin + xmax = faceShp.BoundBox.XMax + ymin = faceShp.BoundBox.YMin + ymax = faceShp.BoundBox.YMax + zmin = faceShp.BoundBox.ZMin + zmax = faceShp.BoundBox.ZMax + + # Compute weighted center of mass of all faces combined + fCnt = 0 + totArea = 0.0 + zeroCOM = FreeCAD.Vector(0.0, 0.0, 0.0) + for F in faceShp.Faces: + comF = F.CenterOfMass + areaF = F.Area + totArea += areaF + fCnt += 1 + zeroCOM = zeroCOM.add(FreeCAD.Vector(comF.x, comF.y, 0.0).multiply(areaF)) + if fCnt == 0: + PathLog.error(translate('PathSurface', 'Cannot calculate the Center Of Mass. Using Center of Boundbox.')) + zeroCOM = FreeCAD.Vector((xmin + xmax) / 2.0, (ymin + ymax) / 2.0, 0.0) + else: + avgArea = totArea / fCnt + zeroCOM.multiply(1 / fCnt) + zeroCOM.multiply(1 / avgArea) + COM = FreeCAD.Vector(zeroCOM.x, zeroCOM.y, 0.0) + + # get X, Y, Z spans; Compute center of rotation + deltaX = abs(xmax-xmin) + deltaY = abs(ymax-ymin) + deltaZ = abs(zmax-zmin) + deltaC = math.sqrt(deltaX**2 + deltaY**2) + lineLen = deltaC + (2.0 * self.cutter.getDiameter()) # Line length to span boundbox diag with 2x cutter diameter extra on each end + halfLL = math.ceil(lineLen / 2.0) + cutPasses = math.ceil(lineLen / self.cutOut) + 1 # Number of lines(passes) required to cover lineLen + halfPasses = math.ceil(cutPasses / 2.0) + bbC = faceShp.BoundBox.Center + + # Generate the Draft line/circle sets to be intersected with the cut-face-area + if obj.CutPattern in ['ZigZag', 'Line']: + MaxLC = -1 + centRot = FreeCAD.Vector(0.0, 0.0, 0.0) # Bottom left corner of face/selection/model + cAng = math.atan(deltaX / deltaY) # BoundaryBox angle + + # Determine end points and create top lines + x1 = centRot.x - halfLL + x2 = centRot.x + halfLL + diag = None + if obj.CutPatternAngle == 0 or obj.CutPatternAngle == 180: + MaxLC = math.floor(deltaY / self.cutOut) + diag = deltaY + elif obj.CutPatternAngle == 90 or obj.CutPatternAngle == 270: + MaxLC = math.floor(deltaX / self.cutOut) + diag = deltaX + else: + perpDist = math.cos(cAng - math.radians(obj.CutPatternAngle)) * deltaC + MaxLC = math.floor(perpDist / self.cutOut) + diag = perpDist + y1 = centRot.y + diag + # y2 = y1 + + p1 = FreeCAD.Vector(x1, y1, 0.0) + p2 = FreeCAD.Vector(x2, y1, 0.0) + topLineTuple = (p1, p2) + ny1 = centRot.y - diag + n1 = FreeCAD.Vector(x1, ny1, 0.0) + n2 = FreeCAD.Vector(x2, ny1, 0.0) + negTopLineTuple = (n1, n2) + + # Create end points for set of lines to intersect with cross-section face + pntTuples = list() + for lc in range((-1 * (halfPasses - 1)), halfPasses + 1): + # if lc == (cutPasses - MaxLC - 1): + # pntTuples.append(negTopLineTuple) + # if lc == (MaxLC + 1): + # pntTuples.append(topLineTuple) + x1 = centRot.x - halfLL + x2 = centRot.x + halfLL + y1 = centRot.y + (lc * self.cutOut) + # y2 = y1 + p1 = FreeCAD.Vector(x1, y1, 0.0) + p2 = FreeCAD.Vector(x2, y1, 0.0) + pntTuples.append( (p1, p2) ) + + # Convert end points to lines + for (p1, p2) in pntTuples: + line = Part.makeLine(p1, p2) + GeoSet.append(line) + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + zTgt = faceShp.BoundBox.ZMin + axisRot = FreeCAD.Vector(0.0, 0.0, 1.0) + cntr = FreeCAD.Placement() + cntr.Rotation = FreeCAD.Rotation(axisRot, 0.0) + + if obj.CircularCenterAt == 'CenterOfMass': + cntr.Base = FreeCAD.Vector(COM.x, COM.y, zTgt) # COM # Use center of Mass + elif obj.CircularCenterAt == 'CenterOfBoundBox': + cent = faceShp.BoundBox.Center + cntr.Base = FreeCAD.Vector(cent.x, cent.y, zTgt) + elif obj.CircularCenterAt == 'XminYmin': + cntr.Base = FreeCAD.Vector(faceShp.BoundBox.XMin, faceShp.BoundBox.YMin, zTgt) + elif obj.CircularCenterAt == 'Custom': + newCent = FreeCAD.Vector(obj.CircularCenterCustom.x, obj.CircularCenterCustom.y, zTgt) + cntr.Base = newCent + + # recalculate cutPasses value, if need be + radialPasses = halfPasses + if obj.CircularCenterAt != 'CenterOfBoundBox': + # make 4 corners of boundbox in XY plane, find which is greatest distance to new circular center + EBB = faceShp.BoundBox + CORNERS = [ + FreeCAD.Vector(EBB.XMin, EBB.YMin, 0.0), + FreeCAD.Vector(EBB.XMin, EBB.YMax, 0.0), + FreeCAD.Vector(EBB.XMax, EBB.YMax, 0.0), + FreeCAD.Vector(EBB.XMax, EBB.YMin, 0.0), + ] + dMax = 0.0 + for c in range(0, 4): + dist = CORNERS[c].sub(cntr.Base).Length + if dist > dMax: + dMax = dist + lineLen = dMax + (2.0 * self.cutter.getDiameter()) # Line length to span boundbox diag with 2x cutter diameter extra on each end + radialPasses = math.ceil(lineLen / self.cutOut) + 1 # Number of lines(passes) required to cover lineLen + + # Update COM point and current CircularCenter + if obj.CircularCenterAt != 'Custom': + obj.CircularCenterCustom = cntr.Base + + minRad = self.cutter.getDiameter() * 0.45 + siX3 = 3 * obj.SampleInterval.Value + minRadSI = (siX3 / 2.0) / math.pi + if minRad < minRadSI: + minRad = minRadSI + + # Make small center circle to start pattern + if obj.StepOver > 50: + circle = Part.makeCircle(minRad, cntr.Base) + GeoSet.append(circle) + + for lc in range(1, radialPasses + 1): + rad = (lc * self.cutOut) + if rad >= minRad: + circle = Part.makeCircle(rad, cntr.Base) + GeoSet.append(circle) + # Efor + COM = cntr.Base + # Eif + + if obj.CutPatternReversed is True: + GeoSet.reverse() + + if faceShp.BoundBox.ZMin != 0.0: + faceShp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - faceShp.BoundBox.ZMin)) + + # Create compound object to bind all lines in Lineset + geomShape = Part.makeCompound(GeoSet) + + # Position and rotate the Line and ZigZag geometry + if obj.CutPattern in ['Line', 'ZigZag']: + if obj.CutPatternAngle != 0.0: + geomShape.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), obj.CutPatternAngle) + geomShape.Placement.Base = FreeCAD.Vector(bbC.x, bbC.y, 0.0 - geomShape.BoundBox.ZMin) + + if self.showDebugObjects is True: + F = FreeCAD.ActiveDocument.addObject('Part::Feature','tmpGeometrySet') + F.Shape = geomShape + F.purgeTouched() + self.tempGroup.addObject(F) + + # Identify intersection of cross-section face and lineset + cmnShape = faceShp.common(geomShape) + + if self.showDebugObjects is True: + F = FreeCAD.ActiveDocument.addObject('Part::Feature','tmpPathGeometry') + F.Shape = cmnShape + F.purgeTouched() + self.tempGroup.addObject(F) + + self.tmpCOM = FreeCAD.Vector(COM.x, COM.y, faceShp.BoundBox.ZMin) + return cmnShape + + def _pathGeomToLinesPointSet(self, obj, compGeoShp): + '''_pathGeomToLinesPointSet(obj, compGeoShp)... + Convert a compound set of sequential line segments to directionally-oriented collinear groupings.''' + PathLog.debug('_pathGeomToLinesPointSet()') + # Extract intersection line segments for return value as list() + LINES = list() + inLine = list() + chkGap = False + lnCnt = 0 + ec = len(compGeoShp.Edges) + cutClimb = self.CutClimb + toolDiam = 2.0 * self.radius + cpa = obj.CutPatternAngle + + edg0 = compGeoShp.Edges[0] + p1 = (edg0.Vertexes[0].X, edg0.Vertexes[0].Y) + p2 = (edg0.Vertexes[1].X, edg0.Vertexes[1].Y) + if cutClimb is True: + tup = (p2, p1) + lst = FreeCAD.Vector(p1[0], p1[1], 0.0) + else: + tup = (p1, p2) + lst = FreeCAD.Vector(p2[0], p2[1], 0.0) + inLine.append(tup) + sp = FreeCAD.Vector(p1[0], p1[1], 0.0) # start point + + for ei in range(1, ec): + chkGap = False + edg = compGeoShp.Edges[ei] # Get edge for vertexes + v1 = (edg.Vertexes[0].X, edg.Vertexes[0].Y) # vertex 0 + v2 = (edg.Vertexes[1].X, edg.Vertexes[1].Y) # vertex 1 + + ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point + cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (first / middle point) + iC = sp.isOnLineSegment(ep, cp) + if iC is True: + inLine.append('BRK') + chkGap = True + else: + if cutClimb is True: + inLine.reverse() + LINES.append(inLine) # Save inLine segments + lnCnt += 1 + inLine = list() # reset collinear container + if cutClimb is True: + sp = cp # FreeCAD.Vector(v1[0], v1[1], 0.0) + else: + sp = ep + + if cutClimb is True: + tup = (v2, v1) + if chkGap is True: + gap = abs(toolDiam - lst.sub(ep).Length) + lst = cp + else: + tup = (v1, v2) + if chkGap is True: + gap = abs(toolDiam - lst.sub(cp).Length) + lst = ep + + if chkGap is True: + if gap < obj.GapThreshold.Value: + b = inLine.pop() # pop off 'BRK' marker + (vA, vB) = inLine.pop() # pop off previous line segment for combining with current + tup = (vA, tup[1]) + self.closedGap = True + else: + # PathLog.debug('---- Gap: {} mm'.format(gap)) + gap = round(gap, 6) + if gap < self.gaps[0]: + self.gaps.insert(0, gap) + self.gaps.pop() + inLine.append(tup) + # Efor + lnCnt += 1 + if cutClimb is True: + inLine.reverse() + LINES.append(inLine) # Save inLine segments + + # Handle last inLine set, reversing it. + if obj.CutPatternReversed is True: + if cpa != 0.0 and cpa % 90.0 == 0.0: + F = LINES.pop(0) + rev = list() + for iL in F: + if iL == 'BRK': + rev.append(iL) + else: + (p1, p2) = iL + rev.append((p2, p1)) + rev.reverse() + LINES.insert(0, rev) + + isEven = lnCnt % 2 + if isEven == 0: + PathLog.debug('Line count is ODD.') + else: + PathLog.debug('Line count is even.') + + return LINES + + def _pathGeomToZigzagPointSet(self, obj, compGeoShp): + '''_pathGeomToZigzagPointSet(obj, compGeoShp)... + Convert a compound set of sequential line segments to directionally-oriented collinear groupings + with a ZigZag directional indicator included for each collinear group.''' + PathLog.debug('_pathGeomToZigzagPointSet()') + # Extract intersection line segments for return value as list() + LINES = list() + inLine = list() + lnCnt = 0 + chkGap = False + ec = len(compGeoShp.Edges) + toolDiam = 2.0 * self.radius + + if self.CutClimb is True: + dirFlg = -1 + else: + dirFlg = 1 + + edg0 = compGeoShp.Edges[0] + p1 = (edg0.Vertexes[0].X, edg0.Vertexes[0].Y) + p2 = (edg0.Vertexes[1].X, edg0.Vertexes[1].Y) + if dirFlg == 1: + tup = (p1, p2) + lst = FreeCAD.Vector(p2[0], p2[1], 0.0) + sp = FreeCAD.Vector(p1[0], p1[1], 0.0) # start point + else: + tup = (p2, p1) + lst = FreeCAD.Vector(p1[0], p1[1], 0.0) + sp = FreeCAD.Vector(p2[0], p2[1], 0.0) # start point + inLine.append(tup) + otr = lst + + for ei in range(1, ec): + edg = compGeoShp.Edges[ei] + v1 = (edg.Vertexes[0].X, edg.Vertexes[0].Y) + v2 = (edg.Vertexes[1].X, edg.Vertexes[1].Y) + + cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (start point of segment) + ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point + iC = sp.isOnLineSegment(ep, cp) + if iC is True: + inLine.append('BRK') + chkGap = True + gap = abs(toolDiam - lst.sub(cp).Length) + else: + chkGap = False + if dirFlg == -1: + inLine.reverse() + LINES.append((dirFlg, inLine)) + lnCnt += 1 + dirFlg = -1 * dirFlg # Change zig to zag + inLine = list() # reset collinear container + sp = cp # FreeCAD.Vector(v1[0], v1[1], 0.0) + otr = ep + + lst = ep + if dirFlg == 1: + tup = (v1, v2) + else: + tup = (v2, v1) + + if chkGap is True: + if gap < obj.GapThreshold.Value: + b = inLine.pop() # pop off 'BRK' marker + (vA, vB) = inLine.pop() # pop off previous line segment for combining with current + if dirFlg == 1: + tup = (vA, tup[1]) + else: + #tup = (vA, tup[1]) + #tup = (tup[1], vA) + tup = (tup[0], vB) + self.closedGap = True + else: + gap = round(gap, 6) + if gap < self.gaps[0]: + self.gaps.insert(0, gap) + self.gaps.pop() + inLine.append(tup) + # Efor + lnCnt += 1 + + # Fix directional issue with LAST line when line count is even + isEven = lnCnt % 2 + if isEven == 0: # Changed to != with 90 degree CutPatternAngle + PathLog.debug('Line count is even.') + else: + PathLog.debug('Line count is ODD.') + dirFlg = -1 * dirFlg + if obj.CutPatternReversed is False: + if self.CutClimb is True: + dirFlg = -1 * dirFlg + + if obj.CutPatternReversed is True: + dirFlg = -1 * dirFlg + + # Handle last inLine list + if dirFlg == 1: + rev = list() + for iL in inLine: + if iL == 'BRK': + rev.append(iL) + else: + (p1, p2) = iL + rev.append((p2, p1)) + + if obj.CutPatternReversed is False: + rev.reverse() + else: + rev2 = list() + for iL in rev: + if iL == 'BRK': + rev2.append(iL) + else: + (p1, p2) = iL + rev2.append((p2, p1)) + rev2.reverse() + rev = rev2 + + LINES.append((dirFlg, rev)) + else: + LINES.append((dirFlg, inLine)) + + return LINES + + def _pathGeomToArcPointSet(self, obj, compGeoShp): + '''_pathGeomToArcPointSet(obj, compGeoShp)... + Convert a compound set of arcs/circles to a set of directionally-oriented arc end points + and the corresponding center point.''' + # Extract intersection line segments for return value as list() + PathLog.debug('_pathGeomToArcPointSet()') + ARCS = list() + stpOvrEI = list() + segEI = list() + isSame = False + sameRad = None + COM = self.tmpCOM + toolDiam = 2.0 * self.radius + ec = len(compGeoShp.Edges) + + def gapDist(sp, ep): + X = (ep[0] - sp[0])**2 + Y = (ep[1] - sp[1])**2 + Z = (ep[2] - sp[2])**2 + # return math.sqrt(X + Y + Z) + return math.sqrt(X + Y) # the 'z' value is zero in both points + + # Separate arc data into Loops and Arcs + for ei in range(0, ec): + edg = compGeoShp.Edges[ei] + if edg.Closed is True: + stpOvrEI.append(('L', ei, False)) + else: + if isSame is False: + segEI.append(ei) + isSame = True + pnt = FreeCAD.Vector(edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0) + sameRad = pnt.sub(COM).Length + else: + # Check if arc is co-radial to current SEGS + pnt = FreeCAD.Vector(edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0) + if abs(sameRad - pnt.sub(COM).Length) > 0.00001: + isSame = False + + if isSame is True: + segEI.append(ei) + else: + # Move co-radial arc segments + stpOvrEI.append(['A', segEI, False]) + # Start new list of arc segments + segEI = [ei] + isSame = True + pnt = FreeCAD.Vector(edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0) + sameRad = pnt.sub(COM).Length + # Process trailing `segEI` data, if available + if isSame is True: + stpOvrEI.append(['A', segEI, False]) + + # Identify adjacent arcs with y=0 start/end points that connect + for so in range(0, len(stpOvrEI)): + SO = stpOvrEI[so] + if SO[0] == 'A': + startOnAxis = list() + endOnAxis = list() + EI = SO[1] # list of corresponding compGeoShp.Edges indexes + + # Identify startOnAxis and endOnAxis arcs + for i in range(0, len(EI)): + ei = EI[i] # edge index + E = compGeoShp.Edges[ei] # edge object + if abs(COM.y - E.Vertexes[0].Y) < 0.00001: + startOnAxis.append((i, ei, E.Vertexes[0])) + elif abs(COM.y - E.Vertexes[1].Y) < 0.00001: + endOnAxis.append((i, ei, E.Vertexes[1])) + + # Look for connections between startOnAxis and endOnAxis arcs. Consolidate data when connected + lenSOA = len(startOnAxis) + lenEOA = len(endOnAxis) + if lenSOA > 0 and lenEOA > 0: + delIdxs = list() + lstFindIdx = 0 + for soa in range(0, lenSOA): + (iS, eiS, vS) = startOnAxis[soa] + for eoa in range(0, len(endOnAxis)): + (iE, eiE, vE) = endOnAxis[eoa] + dist = vE.X - vS.X + if abs(dist) < 0.00001: # They connect on axis at same radius + SO[2] = (eiE, eiS) + break + elif dist > 0: + break # stop searching + # Eif + # Eif + # Efor + + # Construct arc data tuples for OCL + dirFlg = 1 + # cutPat = obj.CutPattern + if self.CutClimb is False: # True yields Climb when set to Conventional + dirFlg = -1 + + # Cycle through stepOver data + for so in range(0, len(stpOvrEI)): + SO = stpOvrEI[so] + if SO[0] == 'L': # L = Loop/Ring/Circle + # PathLog.debug("SO[0] == 'Loop'") + lei = SO[1] # loop Edges index + v1 = compGeoShp.Edges[lei].Vertexes[0] + + # space = obj.SampleInterval.Value / 2.0 + space = 0.0000001 + + # p1 = FreeCAD.Vector(v1.X, v1.Y, v1.Z) + p1 = FreeCAD.Vector(v1.X, v1.Y, 0.0) + rad = p1.sub(COM).Length + spcRadRatio = space/rad + if spcRadRatio < 1.0: + tolrncAng = math.asin(spcRadRatio) + else: + tolrncAng = 0.9999998 * math.pi + EX = COM.x + (rad * math.cos(tolrncAng)) + EY = v1.Y - space # rad * math.sin(tolrncAng) + + sp = (v1.X, v1.Y, 0.0) + ep = (EX, EY, 0.0) + cp = (COM.x, COM.y, 0.0) + if dirFlg == 1: + arc = (sp, ep, cp) + else: + arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + ARCS.append(('L', dirFlg, [arc])) + else: # SO[0] == 'A' A = Arc + # PathLog.debug("SO[0] == 'Arc'") + PRTS = list() + EI = SO[1] # list of corresponding Edges indexes + CONN = SO[2] # list of corresponding connected edges tuples (iE, iS) + chkGap = False + lst = None + + if CONN is not False: + (iE, iS) = CONN + v1 = compGeoShp.Edges[iE].Vertexes[0] + v2 = compGeoShp.Edges[iS].Vertexes[1] + sp = (v1.X, v1.Y, 0.0) + ep = (v2.X, v2.Y, 0.0) + cp = (COM.x, COM.y, 0.0) + if dirFlg == 1: + arc = (sp, ep, cp) + lst = ep + else: + arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + lst = sp + PRTS.append(arc) + # Pop connected edge index values from arc segments index list + iEi = EI.index(iE) + iSi = EI.index(iS) + if iEi > iSi: + EI.pop(iEi) + EI.pop(iSi) + else: + EI.pop(iSi) + EI.pop(iEi) + if len(EI) > 0: + PRTS.append('BRK') + chkGap = True + cnt = 0 + for ei in EI: + if cnt > 0: + PRTS.append('BRK') + chkGap = True + v1 = compGeoShp.Edges[ei].Vertexes[0] + v2 = compGeoShp.Edges[ei].Vertexes[1] + sp = (v1.X, v1.Y, 0.0) + ep = (v2.X, v2.Y, 0.0) + cp = (COM.x, COM.y, 0.0) + if dirFlg == 1: + arc = (sp, ep, cp) + if chkGap is True: + gap = abs(toolDiam - gapDist(lst, sp)) # abs(toolDiam - lst.sub(sp).Length) + lst = ep + else: + arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + if chkGap is True: + gap = abs(toolDiam - gapDist(lst, ep)) # abs(toolDiam - lst.sub(ep).Length) + lst = sp + if chkGap is True: + if gap < obj.GapThreshold.Value: + b = PRTS.pop() # pop off 'BRK' marker + (vA, vB, vC) = PRTS.pop() # pop off previous arc segment for combining with current + arc = (vA, arc[1], vC) + self.closedGap = True + else: + # PathLog.debug('---- Gap: {} mm'.format(gap)) + gap = round(gap, 6) + if gap < self.gaps[0]: + self.gaps.insert(0, gap) + self.gaps.pop() + PRTS.append(arc) + cnt += 1 + + if dirFlg == -1: + PRTS.reverse() + + ARCS.append(('A', dirFlg, PRTS)) + # Eif + if obj.CutPattern == 'CircularZigZag': + dirFlg = -1 * dirFlg + # Efor + + return ARCS + + def _getExperimentalWaterlinePaths(self, obj, PNTSET, csHght): + '''_getExperimentalWaterlinePaths(obj, PNTSET, csHght)... + Switching function for calling the appropriate path-geometry to OCL points conversion function + for the various cut patterns.''' + PathLog.debug('_getExperimentalWaterlinePaths()') + SCANS = list() + + if obj.CutPattern == 'Line': + stpOvr = list() + for D in PNTSET: + for SEG in D: + if SEG == 'BRK': + stpOvr.append(SEG) + else: + # D format is ((p1, p2), (p3, p4)) + (A, B) = SEG + P1 = FreeCAD.Vector(A[0], A[1], csHght) + P2 = FreeCAD.Vector(B[0], B[1], csHght) + stpOvr.append((P1, P2)) + SCANS.append(stpOvr) + stpOvr = list() + elif obj.CutPattern == 'ZigZag': + stpOvr = list() + for (dirFlg, LNS) in PNTSET: + for SEG in LNS: + if SEG == 'BRK': + stpOvr.append(SEG) + else: + # D format is ((p1, p2), (p3, p4)) + (A, B) = SEG + P1 = FreeCAD.Vector(A[0], A[1], csHght) + P2 = FreeCAD.Vector(B[0], B[1], csHght) + stpOvr.append((P1, P2)) + SCANS.append(stpOvr) + stpOvr = list() + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + # PNTSET is list, by stepover. + # Each stepover is a list containing arc/loop descriptions, (sp, ep, cp) + for so in range(0, len(PNTSET)): + stpOvr = list() + erFlg = False + (aTyp, dirFlg, ARCS) = PNTSET[so] + + if dirFlg == 1: # 1 + cMode = True # Climb mode + else: + cMode = False + + for a in range(0, len(ARCS)): + Arc = ARCS[a] + if Arc == 'BRK': + stpOvr.append('BRK') + else: + (sp, ep, cp) = Arc + S = FreeCAD.Vector(sp[0], sp[1], csHght) + E = FreeCAD.Vector(ep[0], ep[1], csHght) + C = FreeCAD.Vector(cp[0], cp[1], csHght) + scan = (S, E, C, cMode) + if scan is False: + erFlg = True + else: + ##if aTyp == 'L': + ## stpOvr.append(FreeCAD.Vector(scan[0][0].x, scan[0][0].y, scan[0][0].z)) + stpOvr.append(scan) + if erFlg is False: + SCANS.append(stpOvr) + + return SCANS + + # Main planar scan functions + def _stepTransitionCmds(self, obj, lstPnt, first, minSTH, tolrnc): + cmds = list() + rtpd = False + horizGC = 'G0' + hSpeed = self.horizRapid + height = obj.SafeHeight.Value + + if obj.CutPattern in ['Line', 'Circular']: + if obj.OptimizeStepOverTransitions is True: + height = minSTH + 2.0 + # if obj.LayerMode == 'Multi-pass': + # rtpd = minSTH + elif obj.CutPattern in ['ZigZag', 'CircularZigZag']: + if obj.OptimizeStepOverTransitions is True: + zChng = first.z - lstPnt.z + # PathLog.debug('first.z: {}'.format(first.z)) + # PathLog.debug('lstPnt.z: {}'.format(lstPnt.z)) + # PathLog.debug('zChng: {}'.format(zChng)) + # PathLog.debug('minSTH: {}'.format(minSTH)) + if abs(zChng) < tolrnc: # transitions to same Z height + # PathLog.debug('abs(zChng) < tolrnc') + if (minSTH - first.z) > tolrnc: + # PathLog.debug('(minSTH - first.z) > tolrnc') + height = minSTH + 2.0 + else: + # PathLog.debug('ELSE (minSTH - first.z) > tolrnc') + horizGC = 'G1' + height = first.z + elif (minSTH + (2.0 * tolrnc)) >= max(first.z, lstPnt.z): + height = False # allow end of Zig to cut to beginning of Zag + + + # Create raise, shift, and optional lower commands + if height is not False: + cmds.append(Path.Command('G0', {'Z': height, 'F': self.vertRapid})) + cmds.append(Path.Command(horizGC, {'X': first.x, 'Y': first.y, 'F': hSpeed})) + if rtpd is not False: # ReturnToPreviousDepth + cmds.append(Path.Command('G0', {'Z': rtpd, 'F': self.vertRapid})) + + return cmds + + def _breakCmds(self, obj, lstPnt, first, minSTH, tolrnc): + cmds = list() + rtpd = False + horizGC = 'G0' + hSpeed = self.horizRapid + height = obj.SafeHeight.Value + + if obj.CutPattern in ['Line', 'Circular']: + if obj.OptimizeStepOverTransitions is True: + height = minSTH + 2.0 + elif obj.CutPattern in ['ZigZag', 'CircularZigZag']: + if obj.OptimizeStepOverTransitions is True: + zChng = first.z - lstPnt.z + if abs(zChng) < tolrnc: # transitions to same Z height + if (minSTH - first.z) > tolrnc: + height = minSTH + 2.0 + else: + height = first.z + 2.0 # first.z + + cmds.append(Path.Command('G0', {'Z': height, 'F': self.vertRapid})) + cmds.append(Path.Command(horizGC, {'X': first.x, 'Y': first.y, 'F': hSpeed})) + if rtpd is not False: # ReturnToPreviousDepth + cmds.append(Path.Command('G0', {'Z': rtpd, 'F': self.vertRapid})) + + return cmds + + def _planarGetPDC(self, stl, finalDep, SampleInterval, useSafeCutter=False): + pdc = ocl.PathDropCutter() # create a pdc [PathDropCutter] object + pdc.setSTL(stl) # add stl model + if useSafeCutter is True: + pdc.setCutter(self.safeCutter) # add safeCutter + else: + pdc.setCutter(self.cutter) # add cutter + pdc.setZ(finalDep) # set minimumZ (final / target depth value) + pdc.setSampling(SampleInterval) # set sampling size + return pdc + + # Main waterline functions + def _oclWaterlineOp(self, JOB, obj, mdlIdx, subShp=None): + '''_oclWaterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model.''' + commands = [] + + t_begin = time.time() + # JOB = PathUtils.findParentJob(obj) + base = JOB.Model.Group[mdlIdx] + bb = self.boundBoxes[mdlIdx] + stl = self.modelSTLs[mdlIdx] + + # Prepare global holdpoint and layerEndPnt containers + if self.holdPoint is None: + self.holdPoint = ocl.Point(float("inf"), float("inf"), float("inf")) + if self.layerEndPnt is None: + self.layerEndPnt = ocl.Point(float("inf"), float("inf"), float("inf")) + + # Set extra offset to diameter of cutter to allow cutter to move around perimeter of model + toolDiam = self.cutter.getDiameter() + cdeoX = 0.6 * toolDiam + cdeoY = 0.6 * toolDiam + + if subShp is None: + # Get correct boundbox + if obj.BoundBox == 'Stock': + BS = JOB.Stock + bb = BS.Shape.BoundBox + elif obj.BoundBox == 'BaseBoundBox': + BS = base + bb = base.Shape.BoundBox + + env = PathUtils.getEnvelope(partshape=BS.Shape, depthparams=self.depthParams) # Produces .Shape + + xmin = bb.XMin + xmax = bb.XMax + ymin = bb.YMin + ymax = bb.YMax + zmin = bb.ZMin + zmax = bb.ZMax + else: + xmin = subShp.BoundBox.XMin + xmax = subShp.BoundBox.XMax + ymin = subShp.BoundBox.YMin + ymax = subShp.BoundBox.YMax + zmin = subShp.BoundBox.ZMin + zmax = subShp.BoundBox.ZMax + + smplInt = obj.SampleInterval.Value + minSampInt = 0.001 # value is mm + if smplInt < minSampInt: + smplInt = minSampInt + + # Determine bounding box length for the OCL scan + bbLength = math.fabs(ymax - ymin) + numScanLines = int(math.ceil(bbLength / smplInt) + 1) # Number of lines + + # Compute number and size of stepdowns, and final depth + if obj.LayerMode == 'Single-pass': + depthparams = [obj.FinalDepth.Value] + else: + depthparams = [dp for dp in self.depthParams] + lenDP = len(depthparams) + + # Prepare PathDropCutter objects with STL data + safePDC = self._planarGetPDC(self.safeSTLs[mdlIdx], + depthparams[lenDP - 1], obj.SampleInterval.Value, useSafeCutter=False) + + # Scan the piece to depth at smplInt + oclScan = [] + oclScan = self._waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, depthparams[lenDP - 1], numScanLines) + # oclScan = SCANS + lenOS = len(oclScan) + ptPrLn = int(lenOS / numScanLines) + + # Convert oclScan list of points to multi-dimensional list + scanLines = [] + for L in range(0, numScanLines): + scanLines.append([]) + for P in range(0, ptPrLn): + pi = L * ptPrLn + P + scanLines[L].append(oclScan[pi]) + lenSL = len(scanLines) + pntsPerLine = len(scanLines[0]) + PathLog.debug("--OCL scan: " + str(lenSL * pntsPerLine) + " points, with " + str(numScanLines) + " lines and " + str(pntsPerLine) + " pts/line") + + # Extract Wl layers per depthparams + lyr = 0 + cmds = [] + layTime = time.time() + self.topoMap = [] + for layDep in depthparams: + cmds = self._getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) + commands.extend(cmds) + lyr += 1 + PathLog.debug("--All layer scans combined took " + str(time.time() - layTime) + " s") + return commands + + def _waterlineDropCutScan(self, stl, smplInt, xmin, xmax, ymin, fd, numScanLines): + '''_waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, fd, numScanLines) ... + Perform OCL scan for waterline purpose.''' + pdc = ocl.PathDropCutter() # create a pdc + pdc.setSTL(stl) + pdc.setCutter(self.cutter) + pdc.setZ(fd) # set minimumZ (final / target depth value) + pdc.setSampling(smplInt) + + # Create line object as path + path = ocl.Path() # create an empty path object + for nSL in range(0, numScanLines): + yVal = ymin + (nSL * smplInt) + p1 = ocl.Point(xmin, yVal, fd) # start-point of line + p2 = ocl.Point(xmax, yVal, fd) # end-point of line + path.append(ocl.Line(p1, p2)) + # path.append(l) # add the line to the path + pdc.setPath(path) + pdc.run() # run drop-cutter on the path + + # return the list the points + return pdc.getCLPoints() + + def _getWaterline(self, obj, scanLines, layDep, lyr, lenSL, pntsPerLine): + '''_getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) ... Get waterline.''' + commands = [] + cmds = [] + loopList = [] + self.topoMap = [] + # Create topo map from scanLines (highs and lows) + self.topoMap = self._createTopoMap(scanLines, layDep, lenSL, pntsPerLine) + # Add buffer lines and columns to topo map + self._bufferTopoMap(lenSL, pntsPerLine) + # Identify layer waterline from OCL scan + self._highlightWaterline(4, 9) + # Extract waterline and convert to gcode + loopList = self._extractWaterlines(obj, scanLines, lyr, layDep) + # save commands + for loop in loopList: + cmds = self._loopToGcode(obj, layDep, loop) + commands.extend(cmds) + return commands + + def _createTopoMap(self, scanLines, layDep, lenSL, pntsPerLine): + '''_createTopoMap(scanLines, layDep, lenSL, pntsPerLine) ... Create topo map version of OCL scan data.''' + topoMap = [] + for L in range(0, lenSL): + topoMap.append([]) + for P in range(0, pntsPerLine): + if scanLines[L][P].z > layDep: + topoMap[L].append(2) + else: + topoMap[L].append(0) + return topoMap + + def _bufferTopoMap(self, lenSL, pntsPerLine): + '''_bufferTopoMap(lenSL, pntsPerLine) ... Add buffer boarder of zeros to all sides to topoMap data.''' + pre = [0, 0] + post = [0, 0] + for p in range(0, pntsPerLine): + pre.append(0) + post.append(0) + for l in range(0, lenSL): + self.topoMap[l].insert(0, 0) + self.topoMap[l].append(0) + self.topoMap.insert(0, pre) + self.topoMap.append(post) + return True + + def _highlightWaterline(self, extraMaterial, insCorn): + '''_highlightWaterline(extraMaterial, insCorn) ... Highlight the waterline data, separating from extra material.''' + TM = self.topoMap + lastPnt = len(TM[1]) - 1 + lastLn = len(TM) - 1 + highFlag = 0 + + # ("--Convert parallel data to ridges") + for lin in range(1, lastLn): + for pt in range(1, lastPnt): # Ignore first and last points + if TM[lin][pt] == 0: + if TM[lin][pt + 1] == 2: # step up + TM[lin][pt] = 1 + if TM[lin][pt - 1] == 2: # step down + TM[lin][pt] = 1 + + # ("--Convert perpendicular data to ridges and highlight ridges") + for pt in range(1, lastPnt): # Ignore first and last points + for lin in range(1, lastLn): + if TM[lin][pt] == 0: + highFlag = 0 + if TM[lin + 1][pt] == 2: # step up + TM[lin][pt] = 1 + if TM[lin - 1][pt] == 2: # step down + TM[lin][pt] = 1 + elif TM[lin][pt] == 2: + highFlag += 1 + if highFlag == 3: + if TM[lin - 1][pt - 1] < 2 or TM[lin - 1][pt + 1] < 2: + highFlag = 2 + else: + TM[lin - 1][pt] = extraMaterial + highFlag = 2 + + # ("--Square corners") + for pt in range(1, lastPnt): + for lin in range(1, lastLn): + if TM[lin][pt] == 1: # point == 1 + cont = True + if TM[lin + 1][pt] == 0: # forward == 0 + if TM[lin + 1][pt - 1] == 1: # forward left == 1 + if TM[lin][pt - 1] == 2: # left == 2 + TM[lin + 1][pt] = 1 # square the corner + cont = False + + if cont is True and TM[lin + 1][pt + 1] == 1: # forward right == 1 + if TM[lin][pt + 1] == 2: # right == 2 + TM[lin + 1][pt] = 1 # square the corner + cont = True + + if TM[lin - 1][pt] == 0: # back == 0 + if TM[lin - 1][pt - 1] == 1: # back left == 1 + if TM[lin][pt - 1] == 2: # left == 2 + TM[lin - 1][pt] = 1 # square the corner + cont = False + + if cont is True and TM[lin - 1][pt + 1] == 1: # back right == 1 + if TM[lin][pt + 1] == 2: # right == 2 + TM[lin - 1][pt] = 1 # square the corner + + # remove inside corners + for pt in range(1, lastPnt): + for lin in range(1, lastLn): + if TM[lin][pt] == 1: # point == 1 + if TM[lin][pt + 1] == 1: + if TM[lin - 1][pt + 1] == 1 or TM[lin + 1][pt + 1] == 1: + TM[lin][pt + 1] = insCorn + elif TM[lin][pt - 1] == 1: + if TM[lin - 1][pt - 1] == 1 or TM[lin + 1][pt - 1] == 1: + TM[lin][pt - 1] = insCorn + + return True + + def _extractWaterlines(self, obj, oclScan, lyr, layDep): + '''_extractWaterlines(obj, oclScan, lyr, layDep) ... Extract water lines from OCL scan data.''' + srch = True + lastPnt = len(self.topoMap[0]) - 1 + lastLn = len(self.topoMap) - 1 + maxSrchs = 5 + srchCnt = 1 + loopList = [] + loop = [] + loopNum = 0 + + if self.CutClimb is True: + lC = [-1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0] + pC = [-1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1] + else: + lC = [1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0] + pC = [-1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1] + + while srch is True: + srch = False + if srchCnt > maxSrchs: + PathLog.debug("Max search scans, " + str(maxSrchs) + " reached\nPossible incomplete waterline result!") + break + for L in range(1, lastLn): + for P in range(1, lastPnt): + if self.topoMap[L][P] == 1: + # start loop follow + srch = True + loopNum += 1 + loop = self._trackLoop(oclScan, lC, pC, L, P, loopNum) + self.topoMap[L][P] = 0 # Mute the starting point + loopList.append(loop) + srchCnt += 1 + PathLog.debug("Search count for layer " + str(lyr) + " is " + str(srchCnt) + ", with " + str(loopNum) + " loops.") + return loopList + + def _trackLoop(self, oclScan, lC, pC, L, P, loopNum): + '''_trackLoop(oclScan, lC, pC, L, P, loopNum) ... Track the loop direction.''' + loop = [oclScan[L - 1][P - 1]] # Start loop point list + cur = [L, P, 1] + prv = [L, P - 1, 1] + nxt = [L, P + 1, 1] + follow = True + ptc = 0 + ptLmt = 200000 + while follow is True: + ptc += 1 + if ptc > ptLmt: + PathLog.debug("Loop number " + str(loopNum) + " at [" + str(nxt[0]) + ", " + str(nxt[1]) + "] pnt count exceeds, " + str(ptLmt) + ". Stopped following loop.") + break + nxt = self._findNextWlPoint(lC, pC, cur[0], cur[1], prv[0], prv[1]) # get next point + loop.append(oclScan[nxt[0] - 1][nxt[1] - 1]) # add it to loop point list + self.topoMap[nxt[0]][nxt[1]] = nxt[2] # Mute the point, if not Y stem + if nxt[0] == L and nxt[1] == P: # check if loop complete + follow = False + elif nxt[0] == cur[0] and nxt[1] == cur[1]: # check if line cannot be detected + follow = False + prv = cur + cur = nxt + return loop + + def _findNextWlPoint(self, lC, pC, cl, cp, pl, pp): + '''_findNextWlPoint(lC, pC, cl, cp, pl, pp) ... + Find the next waterline point in the point cloud layer provided.''' + dl = cl - pl + dp = cp - pp + num = 0 + i = 3 + s = 0 + mtch = 0 + found = False + while mtch < 8: # check all 8 points around current point + if lC[i] == dl: + if pC[i] == dp: + s = i - 3 + found = True + # Check for y branch where current point is connection between branches + for y in range(1, mtch): + if lC[i + y] == dl: + if pC[i + y] == dp: + num = 1 + break + break + i += 1 + mtch += 1 + if found is False: + # ("_findNext: No start point found.") + return [cl, cp, num] + + for r in range(0, 8): + l = cl + lC[s + r] + p = cp + pC[s + r] + if self.topoMap[l][p] == 1: + return [l, p, num] + + # ("_findNext: No next pnt found") + return [cl, cp, num] + + def _loopToGcode(self, obj, layDep, loop): + '''_loopToGcode(obj, layDep, loop) ... Convert set of loop points to Gcode.''' + # generate the path commands + output = [] + optimize = obj.OptimizeLinearPaths + + prev = ocl.Point(float("inf"), float("inf"), float("inf")) + nxt = ocl.Point(float("inf"), float("inf"), float("inf")) + pnt = ocl.Point(float("inf"), float("inf"), float("inf")) + + # Create first point + pnt.x = loop[0].x + pnt.y = loop[0].y + pnt.z = layDep + + # Position cutter to begin loop + output.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + output.append(Path.Command('G0', {'X': pnt.x, 'Y': pnt.y, 'F': self.horizRapid})) + output.append(Path.Command('G1', {'Z': pnt.z, 'F': self.vertFeed})) + + lenCLP = len(loop) + lastIdx = lenCLP - 1 + # Cycle through each point on loop + for i in range(0, lenCLP): + if i < lastIdx: + nxt.x = loop[i + 1].x + nxt.y = loop[i + 1].y + nxt.z = layDep + else: + optimize = False + + if not optimize or not FreeCAD.Vector(prev.x, prev.y, prev.z).isOnLineSegment(FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): + output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'F': self.horizFeed})) + + # Rotate point data + prev.x = pnt.x + prev.y = pnt.y + prev.z = pnt.z + pnt.x = nxt.x + pnt.y = nxt.y + pnt.z = nxt.z + + # Save layer end point for use in transitioning to next layer + self.layerEndPnt.x = pnt.x + self.layerEndPnt.y = pnt.y + self.layerEndPnt.z = pnt.z + + return output + + # Main waterline functions + def _experimentalWaterlineOp(self, JOB, obj, mdlIdx, subShp=None): + '''_waterlineOp(JOB, obj, mdlIdx, subShp=None) ... + Main waterline function to perform waterline extraction from model.''' + PathLog.debug('_experimentalWaterlineOp()') + + msg = translate('PathWaterline', 'Experimental Waterline does not currently support selected faces.') + PathLog.info('\n..... ' + msg) + + commands = [] + t_begin = time.time() + base = JOB.Model.Group[mdlIdx] + bb = self.boundBoxes[mdlIdx] + stl = self.modelSTLs[mdlIdx] + safeSTL = self.safeSTLs[mdlIdx] + self.endVector = None + + finDep = obj.FinalDepth.Value + (self.geoTlrnc / 10.0) + depthParams = PathUtils.depth_params(obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown.Value, 0.0, finDep) + + # Compute number and size of stepdowns, and final depth + if obj.LayerMode == 'Single-pass': + depthparams = [finDep] + else: + depthparams = [dp for dp in depthParams] + lenDP = len(depthparams) + PathLog.debug('Experimental Waterline depthparams:\n{}'.format(depthparams)) + + # Prepare PathDropCutter objects with STL data + # safePDC = self._planarGetPDC(safeSTL, depthparams[lenDP - 1], obj.SampleInterval.Value, useSafeCutter=False) + + buffer = self.cutter.getDiameter() * 2.0 + borderFace = Part.Face(self._makeExtendedBoundBox(JOB.Stock.Shape.BoundBox, buffer, 0.0)) + + # Get correct boundbox + if obj.BoundBox == 'Stock': + stockEnv = self._getShapeEnvelope(JOB.Stock.Shape) + bbFace = self._getCrossSection(stockEnv) # returned at Z=0.0 + elif obj.BoundBox == 'BaseBoundBox': + baseEnv = self._getShapeEnvelope(base.Shape) + bbFace = self._getCrossSection(baseEnv) # returned at Z=0.0 + + trimFace = borderFace.cut(bbFace) + if self.showDebugObjects is True: + TF = FreeCAD.ActiveDocument.addObject('Part::Feature', 'trimFace') + TF.Shape = trimFace + TF.purgeTouched() + self.tempGroup.addObject(TF) + + # Cycle through layer depths + CUTAREAS = self._getCutAreas(base.Shape, depthparams, bbFace, trimFace, borderFace) + if not CUTAREAS: + PathLog.error('No cross-section cut areas identified.') + return commands + + caCnt = 0 + ofst = obj.BoundaryAdjustment.Value + ofst -= self.radius # (self.radius + (tolrnc / 10.0)) + caLen = len(CUTAREAS) + lastCA = caLen - 1 + lastClearArea = None + lastCsHght = None + clearLastLayer = True + for ca in range(0, caLen): + area = CUTAREAS[ca] + csHght = area.BoundBox.ZMin + csHght += obj.DepthOffset.Value + cont = False + caCnt += 1 + if area.Area > 0.0: + cont = True + caWireCnt = len(area.Wires) - 1 # first wire is boundFace wire + PathLog.debug('cutAreaWireCnt: {}'.format(caWireCnt)) + if self.showDebugObjects is True: + CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'cutArea_{}'.format(caCnt)) + CA.Shape = area + CA.purgeTouched() + self.tempGroup.addObject(CA) + else: + PathLog.error('Cut area at {} is zero.'.format(round(csHght, 4))) + + # get offset wire(s) based upon cross-section cut area + if cont: + area.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - area.BoundBox.ZMin)) + activeArea = area.cut(trimFace) + activeAreaWireCnt = len(activeArea.Wires) # first wire is boundFace wire + PathLog.debug('activeAreaWireCnt: {}'.format(activeAreaWireCnt)) + if self.showDebugObjects is True: + CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'activeArea_{}'.format(caCnt)) + CA.Shape = activeArea + CA.purgeTouched() + self.tempGroup.addObject(CA) + ofstArea = self._extractFaceOffset(obj, activeArea, ofst, makeComp=False) + if not ofstArea: + PathLog.error('No offset area returned for cut area depth: {}'.format(csHght)) + cont = False + + if cont: + # Identify solid areas in the offset data + ofstSolidFacesList = self._getSolidAreasFromPlanarFaces(ofstArea) + if ofstSolidFacesList: + clearArea = Part.makeCompound(ofstSolidFacesList) + if self.showDebugObjects is True: + CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'clearArea_{}'.format(caCnt)) + CA.Shape = clearArea + CA.purgeTouched() + self.tempGroup.addObject(CA) + else: + cont = False + PathLog.error('ofstSolids is False.') + + if cont: + # Make waterline path for current CUTAREA depth (csHght) + commands.extend(self._wiresToWaterlinePath(obj, clearArea, csHght)) + clearArea.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - clearArea.BoundBox.ZMin)) + lastClearArea = clearArea + lastCsHght = csHght + + # Clear layer as needed + (useOfst, usePat, clearLastLayer) = self._clearLayer(obj, ca, lastCA, clearLastLayer) + ##if self.showDebugObjects is True and (usePat or useOfst): + ## OA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'clearPatternArea_{}'.format(round(csHght, 2))) + ## OA.Shape = clearArea + ## OA.purgeTouched() + ## self.tempGroup.addObject(OA) + if usePat: + commands.extend(self._makeCutPatternLayerPaths(JOB, obj, clearArea, csHght)) + if useOfst: + commands.extend(self._makeOffsetLayerPaths(JOB, obj, clearArea, csHght)) + # Efor + + if clearLastLayer: + (useOfst, usePat, cLL) = self._clearLayer(obj, 1, 1, False) + clearArea.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - lastClearArea.BoundBox.ZMin)) + if usePat: + commands.extend(self._makeCutPatternLayerPaths(JOB, obj, lastClearArea, lastCsHght)) + + if useOfst: + commands.extend(self._makeOffsetLayerPaths(JOB, obj, lastClearArea, lastCsHght)) + + PathLog.info("Waterline: All layer scans combined took " + str(time.time() - t_begin) + " s") + return commands + + def _getCutAreas(self, shape, depthparams, bbFace, trimFace, borderFace): + '''_getCutAreas(JOB, shape, depthparams, bbFace, borderFace) ... + Takes shape, depthparams and base-envelope-cross-section, and + returns a list of cut areas - one for each depth.''' + PathLog.debug('_getCutAreas()') + + CUTAREAS = list() + lastLayComp = None + isFirst = True + lenDP = len(depthparams) + + # Cycle through layer depths + for dp in range(0, lenDP): + csHght = depthparams[dp] + PathLog.debug('Depth {} is {}'.format(dp + 1, csHght)) + + # Get slice at depth of shape + csFaces = self._getModelCrossSection(shape, csHght) # returned at Z=0.0 + if not csFaces: + PathLog.error('No cross-section wires at {}'.format(csHght)) + else: + PathLog.debug('cross-section face count {}'.format(len(csFaces))) + if len(csFaces) > 0: + useFaces = self._getSolidAreasFromPlanarFaces(csFaces) + else: + useFaces = False + + if useFaces: + PathLog.debug('useFacesCnt: {}'.format(len(useFaces))) + compAdjFaces = Part.makeCompound(useFaces) + + if self.showDebugObjects is True: + CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpSolids_{}'.format(dp + 1)) + CA.Shape = compAdjFaces + CA.purgeTouched() + self.tempGroup.addObject(CA) + + if isFirst: + allPrevComp = compAdjFaces + cutArea = borderFace.cut(compAdjFaces) + else: + preCutArea = borderFace.cut(compAdjFaces) + cutArea = preCutArea.cut(allPrevComp) # cut out higher layers to avoid cutting recessed areas + allPrevComp = allPrevComp.fuse(compAdjFaces) + cutArea.translate(FreeCAD.Vector(0.0, 0.0, csHght - cutArea.BoundBox.ZMin)) + CUTAREAS.append(cutArea) + isFirst = False + else: + PathLog.error('No waterline at depth: {} mm.'.format(csHght)) + # Efor + + if len(CUTAREAS) > 0: + return CUTAREAS + + return False + + def _wiresToWaterlinePath(self, obj, ofstPlnrShp, csHght): + PathLog.debug('_wiresToWaterlinePath()') + commands = list() + + # Translate path geometry to layer height + ofstPlnrShp.translate(FreeCAD.Vector(0.0, 0.0, csHght - ofstPlnrShp.BoundBox.ZMin)) + if self.showDebugObjects is True: + OA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'waterlinePathArea_{}'.format(round(csHght, 2))) + OA.Shape = ofstPlnrShp + OA.purgeTouched() + self.tempGroup.addObject(OA) + + commands.append(Path.Command('N (Cut Area {}.)'.format(round(csHght, 2)))) + for w in range(0, len(ofstPlnrShp.Wires)): + wire = ofstPlnrShp.Wires[w] + V = wire.Vertexes + if obj.CutMode == 'Climb': + lv = len(V) - 1 + startVect = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z) + else: + startVect = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z) + + commands.append(Path.Command('N (Wire {}.)'.format(w))) + (cmds, endVect) = self._wireToPath(obj, wire, startVect) + commands.extend(cmds) + commands.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + + return commands + + def _makeCutPatternLayerPaths(self, JOB, obj, clrAreaShp, csHght): + PathLog.debug('_makeCutPatternLayerPaths()') + commands = [] + + clrAreaShp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - clrAreaShp.BoundBox.ZMin)) + pathGeom = self._planarMakePathGeom(obj, clrAreaShp) + pathGeom.translate(FreeCAD.Vector(0.0, 0.0, csHght - pathGeom.BoundBox.ZMin)) + # clrAreaShp.translate(FreeCAD.Vector(0.0, 0.0, csHght - clrAreaShp.BoundBox.ZMin)) + + if self.showDebugObjects is True: + OA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'pathGeom_{}'.format(round(csHght, 2))) + OA.Shape = pathGeom + OA.purgeTouched() + self.tempGroup.addObject(OA) + + # Convert pathGeom to gcode more efficiently + if True: + if obj.CutPattern == 'Offset': + commands.extend(self._makeOffsetLayerPaths(JOB, obj, clrAreaShp, csHght)) + else: + clrAreaShp.translate(FreeCAD.Vector(0.0, 0.0, csHght - clrAreaShp.BoundBox.ZMin)) + if obj.CutPattern == 'Line': + pntSet = self._pathGeomToLinesPointSet(obj, pathGeom) + elif obj.CutPattern == 'ZigZag': + pntSet = self._pathGeomToZigzagPointSet(obj, pathGeom) + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + pntSet = self._pathGeomToArcPointSet(obj, pathGeom) + stpOVRS = self._getExperimentalWaterlinePaths(obj, pntSet, csHght) + # PathLog.debug('stpOVRS:\n{}'.format(stpOVRS)) + safePDC = False + cmds = self._clearGeomToPaths(JOB, obj, safePDC, stpOVRS, csHght) + commands.extend(cmds) + else: + # Use Path.fromShape() to convert edges to paths + for w in range(0, len(pathGeom.Edges)): + wire = pathGeom.Edges[w] + V = wire.Vertexes + if obj.CutMode == 'Climb': + lv = len(V) - 1 + startVect = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z) + else: + startVect = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z) + + commands.append(Path.Command('N (Wire {}.)'.format(w))) + (cmds, endVect) = self._wireToPath(obj, wire, startVect) + commands.extend(cmds) + commands.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + + return commands + + def _makeOffsetLayerPaths(self, JOB, obj, clrAreaShp, csHght): + PathLog.debug('_makeOffsetLayerPaths()') + PathLog.warning('Using `Offset` for clearing bottom layer.') + cmds = list() + # ofst = obj.BoundaryAdjustment.Value + ofst = 0.0 - self.cutOut # - self.cutter.getDiameter() # (self.radius + (tolrnc / 10.0)) + shape = clrAreaShp + cont = True + cnt = 0 + while cont: + ofstArea = self._extractFaceOffset(obj, shape, ofst, makeComp=True) + if not ofstArea: + PathLog.warning('No offset clearing area returned.') + break + for F in ofstArea.Faces: + cmds.extend(self._wiresToWaterlinePath(obj, F, csHght)) + shape = ofstArea + if cnt == 0: + ofst = 0.0 - self.cutOut # self.cutter.Diameter() + cnt += 1 + return cmds + + def _clearGeomToPaths(self, JOB, obj, safePDC, SCANDATA, csHght): + PathLog.debug('_clearGeomToPaths()') + + GCODE = [Path.Command('N (Beginning of Single-pass layer.)', {})] + tolrnc = JOB.GeometryTolerance.Value + prevDepth = obj.SafeHeight.Value + lenSCANDATA = len(SCANDATA) + gDIR = ['G3', 'G2'] + + if self.CutClimb is True: + gDIR = ['G2', 'G3'] + + # Send cutter to x,y position of first point on first line + first = SCANDATA[0][0][0] # [step][item][point] + GCODE.append(Path.Command('G0', {'X': first.x, 'Y': first.y, 'F': self.horizRapid})) + + # Cycle through step-over sections (line segments or arcs) + odd = True + lstStpEnd = None + prevDepth = obj.SafeHeight.Value # Not used for Single-pass + for so in range(0, lenSCANDATA): + cmds = list() + PRTS = SCANDATA[so] + lenPRTS = len(PRTS) + first = PRTS[0][0] # first point of arc/line stepover group + start = PRTS[0][0] # will change with each line/arc segment + last = None + cmds.append(Path.Command('N (Begin step {}.)'.format(so), {})) + + if so > 0: + if obj.CutPattern == 'CircularZigZag': + if odd is True: + odd = False + else: + odd = True + # minTrnsHght = self._getMinSafeTravelHeight(safePDC, lstStpEnd, first) # Check safe travel height against fullSTL + minTrnsHght = obj.SafeHeight.Value + # cmds.append(Path.Command('N (Transition: last, first: {}, {}: minSTH: {})'.format(lstStpEnd, first, minTrnsHght), {})) + cmds.extend(self._stepTransitionCmds(obj, lstStpEnd, first, minTrnsHght, tolrnc)) + + # Cycle through current step-over parts + for i in range(0, lenPRTS): + prt = PRTS[i] + lenPrt = len(prt) + # PathLog.debug('prt: {}'.format(prt)) + if prt == 'BRK': + nxtStart = PRTS[i + 1][0] + # minSTH = self._getMinSafeTravelHeight(safePDC, last, nxtStart) # Check safe travel height against fullSTL + minSTH = obj.SafeHeight.Value + cmds.append(Path.Command('N (Break)', {})) + cmds.extend(self._breakCmds(obj, last, nxtStart, minSTH, tolrnc)) + else: + cmds.append(Path.Command('N (part {}.)'.format(i + 1), {})) + if obj.CutPattern in ['Line', 'ZigZag']: + start, last = prt + cmds.append(Path.Command('G1', {'X': start.x, 'Y': start.y, 'Z': start.z, 'F': self.horizFeed})) + cmds.append(Path.Command('G1', {'X': last.x, 'Y': last.y, 'F': self.horizFeed})) + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + start, last, centPnt, cMode = prt + gcode = self._makeGcodeArc(start, last, odd, gDIR, tolrnc) + cmds.extend(gcode) + cmds.append(Path.Command('N (End of step {}.)'.format(so), {})) + GCODE.extend(cmds) # save line commands + lstStpEnd = last + # Efor + + return GCODE + + def _getSolidAreasFromPlanarFaces(self, csFaces): + PathLog.debug('_getSolidAreasFromPlanarFaces()') + holds = list() + cutFaces = list() + useFaces = list() + lenCsF = len(csFaces) + PathLog.debug('lenCsF: {}'.format(lenCsF)) + + if lenCsF == 1: + useFaces = csFaces + else: + fIds = list() + aIds = list() + pIds = list() + cIds = list() + + for af in range(0, lenCsF): + fIds.append(af) # face ids + aIds.append(af) # face ids + pIds.append(-1) # parent ids + cIds.append(False) # cut ids + holds.append(False) + + while len(fIds) > 0: + li = fIds.pop() + low = csFaces[li] # senior face + pIds = self._idInternalFeature(csFaces, fIds, pIds, li, low) + # Ewhile + ##PathLog.info('fIds: {}'.format(fIds)) + ##PathLog.info('pIds: {}'.format(pIds)) + + for af in range(lenCsF - 1, -1, -1): # cycle from last item toward first + ##PathLog.info('af: {}'.format(af)) + prnt = pIds[af] + ##PathLog.info('prnt: {}'.format(prnt)) + if prnt == -1: + stack = -1 + else: + stack = [af] + # get_face_ids_to_parent + stack.insert(0, prnt) + nxtPrnt = pIds[prnt] + # find af value for nxtPrnt + while nxtPrnt != -1: + stack.insert(0, nxtPrnt) + nxtPrnt = pIds[nxtPrnt] + cIds[af] = stack + # PathLog.debug('cIds: {}\n'.format(cIds)) + + for af in range(0, lenCsF): + # PathLog.debug('af is {}'.format(af)) + pFc = cIds[af] + if pFc == -1: + # Simple, independent region + holds[af] = csFaces[af] # place face in hold + # PathLog.debug('pFc == -1') + else: + # Compound region + # PathLog.debug('pFc is not -1') + cnt = len(pFc) + if cnt % 2.0 == 0.0: + # even is donut cut + # PathLog.debug('cnt is even') + inr = pFc[cnt - 1] + otr = pFc[cnt - 2] + # PathLog.debug('inr / otr: {} / {}'.format(inr, otr)) + holds[otr] = holds[otr].cut(csFaces[inr]) + else: + # odd is floating solid + # PathLog.debug('cnt is ODD') + holds[af] = csFaces[af] + # Efor + + for af in range(0, lenCsF): + if holds[af]: + useFaces.append(holds[af]) # save independent solid + + # Eif + + if len(useFaces) > 0: + return useFaces + + return False + + def _getModelCrossSection(self, shape, csHght): + PathLog.debug('_getCrossSection()') + wires = list() + + def byArea(fc): + return fc.Area + + for i in shape.slice(FreeCAD.Vector(0, 0, 1), csHght): + wires.append(i) + + if len(wires) > 0: + for w in wires: + if w.isClosed() is False: + return False + FCS = list() + for w in wires: + w.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - w.BoundBox.ZMin)) + FCS.append(Part.Face(w)) + FCS.sort(key=byArea, reverse=True) + return FCS + else: + PathLog.debug(' -No wires from .slice() method') + + return False + + def _isInBoundBox(self, outShp, inShp): + obb = outShp.BoundBox + ibb = inShp.BoundBox + + if obb.XMin < ibb.XMin: + if obb.XMax > ibb.XMax: + if obb.YMin < ibb.YMin: + if obb.YMax > ibb.YMax: + return True + return False + + def _idInternalFeature(self, csFaces, fIds, pIds, li, low): + Ids = list() + for i in fIds: + Ids.append(i) + while len(Ids) > 0: + hi = Ids.pop() + high = csFaces[hi] + if self._isInBoundBox(high, low): + cmn = high.common(low) + if cmn.Area > 0.0: + pIds[li] = hi + break + # Ewhile + return pIds + + def _wireToPath(self, obj, wire, startVect): + '''_wireToPath(obj, wire, startVect) ... wire to path.''' + PathLog.track() + + paths = [] + pathParams = {} # pylint: disable=assignment-from-no-return + V = wire.Vertexes + + pathParams['shapes'] = [wire] + pathParams['feedrate'] = self.horizFeed + pathParams['feedrate_v'] = self.vertFeed + pathParams['verbose'] = True + pathParams['resume_height'] = obj.SafeHeight.Value + pathParams['retraction'] = obj.ClearanceHeight.Value + pathParams['return_end'] = True + # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers + pathParams['preamble'] = False + pathParams['start'] = startVect + + (pp, end_vector) = Path.fromShapes(**pathParams) + paths.extend(pp.Commands) + # PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector)) + + self.endVector = end_vector # pylint: disable=attribute-defined-outside-init + + return (paths, end_vector) + + def _makeExtendedBoundBox(self, wBB, bbBfr, zDep): + pl = FreeCAD.Placement() + pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) + pl.Base = FreeCAD.Vector(0, 0, 0) + + p1 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMin - bbBfr, zDep) + p2 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMin - bbBfr, zDep) + p3 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMax + bbBfr, zDep) + p4 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMax + bbBfr, zDep) + bb = Part.makePolygon([p1, p2, p3, p4, p1]) + + return bb + + def _makeGcodeArc(self, strtPnt, endPnt, odd, gDIR, tolrnc): + cmds = list() + isCircle = False + inrPnt = None + gdi = 0 + if odd is True: + gdi = 1 + + # Test if pnt set is circle + if abs(strtPnt.x - endPnt.x) < tolrnc: + if abs(strtPnt.y - endPnt.y) < tolrnc: + isCircle = True + isCircle = False + + if isCircle is True: + # convert LN to G2/G3 arc, consolidating GCode + # https://wiki.shapeoko.com/index.php/G-Code#G2_-_clockwise_arc + # https://www.cnccookbook.com/cnc-g-code-arc-circle-g02-g03/ + # Dividing circle into two arcs allows for G2/G3 on inclined surfaces + + # ijk = self.tmpCOM - strtPnt # vector from start to center + ijk = self.tmpCOM - strtPnt # vector from start to center + xyz = self.tmpCOM.add(ijk) # end point + cmds.append(Path.Command('G1', {'X': strtPnt.x, 'Y': strtPnt.y, 'Z': strtPnt.z, 'F': self.horizFeed})) + cmds.append(Path.Command(gDIR[gdi], {'X': xyz.x, 'Y': xyz.y, 'Z': xyz.z, + 'I': ijk.x, 'J': ijk.y, 'K': ijk.z, # leave same xyz.z height + 'F': self.horizFeed})) + cmds.append(Path.Command('G1', {'X': xyz.x, 'Y': xyz.y, 'Z': xyz.z, 'F': self.horizFeed})) + ijk = self.tmpCOM - xyz # vector from start to center + rst = strtPnt # end point + cmds.append(Path.Command(gDIR[gdi], {'X': rst.x, 'Y': rst.y, 'Z': rst.z, + 'I': ijk.x, 'J': ijk.y, 'K': ijk.z, # leave same xyz.z height + 'F': self.horizFeed})) + cmds.append(Path.Command('G1', {'X': strtPnt.x, 'Y': strtPnt.y, 'Z': strtPnt.z, 'F': self.horizFeed})) + else: + # ijk = self.tmpCOM - strtPnt + ijk = self.tmpCOM.sub(strtPnt) # vector from start to center + xyz = endPnt + cmds.append(Path.Command('G1', {'X': strtPnt.x, 'Y': strtPnt.y, 'Z': strtPnt.z, 'F': self.horizFeed})) + cmds.append(Path.Command(gDIR[gdi], {'X': xyz.x, 'Y': xyz.y, 'Z': xyz.z, + 'I': ijk.x, 'J': ijk.y, 'K': ijk.z, # leave same xyz.z height + 'F': self.horizFeed})) + cmds.append(Path.Command('G1', {'X': endPnt.x, 'Y': endPnt.y, 'Z': endPnt.z, 'F': self.horizFeed})) + + return cmds + + def _clearLayer(self, obj, ca, lastCA, clearLastLayer): + PathLog.debug('_clearLayer()') + usePat = False + useOfst = False + + if obj.ClearLastLayer == 'Off': + if obj.CutPattern != 'None': + usePat = True + else: + if ca == lastCA: + PathLog.debug('... Clearing bottom layer.') + if obj.ClearLastLayer == 'Offset': + obj.CutPattern = 'None' + useOfst = True + else: + obj.CutPattern = obj.ClearLastLayer + usePat = True + clearLastLayer = False + + return (useOfst, usePat, clearLastLayer) + + def resetOpVariables(self, all=True): + '''resetOpVariables() ... Reset class variables used for instance of operation.''' + self.holdPoint = None + self.layerEndPnt = None + self.onHold = False + self.SafeHeightOffset = 2.0 + self.ClearHeightOffset = 4.0 + self.layerEndzMax = 0.0 + self.resetTolerance = 0.0 + self.holdPntCnt = 0 + self.bbRadius = 0.0 + self.axialFeed = 0.0 + self.axialRapid = 0.0 + self.FinalDepth = 0.0 + self.clearHeight = 0.0 + self.safeHeight = 0.0 + self.faceZMax = -999999999999.0 + if all is True: + self.cutter = None + self.stl = None + self.fullSTL = None + self.cutOut = 0.0 + self.radius = 0.0 + self.useTiltCutter = False + return True + + def deleteOpVariables(self, all=True): + '''deleteOpVariables() ... Reset class variables used for instance of operation.''' + del self.holdPoint + del self.layerEndPnt + del self.onHold + del self.SafeHeightOffset + del self.ClearHeightOffset + del self.layerEndzMax + del self.resetTolerance + del self.holdPntCnt + del self.bbRadius + del self.axialFeed + del self.axialRapid + del self.FinalDepth + del self.clearHeight + del self.safeHeight + del self.faceZMax + if all is True: + del self.cutter + del self.stl + del self.fullSTL + del self.cutOut + del self.radius + del self.useTiltCutter + return True + + def setOclCutter(self, obj, safe=False): + ''' setOclCutter(obj) ... Translation function to convert FreeCAD tool definition to OCL formatted tool. ''' + # Set cutter details + # https://www.freecadweb.org/api/dd/dfe/classPath_1_1Tool.html#details + diam_1 = float(obj.ToolController.Tool.Diameter) + lenOfst = obj.ToolController.Tool.LengthOffset if hasattr(obj.ToolController.Tool, 'LengthOffset') else 0 + FR = obj.ToolController.Tool.FlatRadius if hasattr(obj.ToolController.Tool, 'FlatRadius') else 0 + CEH = obj.ToolController.Tool.CuttingEdgeHeight if hasattr(obj.ToolController.Tool, 'CuttingEdgeHeight') else 0 + CEA = obj.ToolController.Tool.CuttingEdgeAngle if hasattr(obj.ToolController.Tool, 'CuttingEdgeAngle') else 0 + + # Make safeCutter with 2 mm buffer around physical cutter + if safe is True: + diam_1 += 4.0 + if FR != 0.0: + FR += 2.0 + + PathLog.debug('ToolType: {}'.format(obj.ToolController.Tool.ToolType)) + if obj.ToolController.Tool.ToolType == 'EndMill': + # Standard End Mill + return ocl.CylCutter(diam_1, (CEH + lenOfst)) + + elif obj.ToolController.Tool.ToolType == 'BallEndMill' and FR == 0.0: + # Standard Ball End Mill + # OCL -> BallCutter::BallCutter(diameter, length) + self.useTiltCutter = True + return ocl.BallCutter(diam_1, (diam_1 / 2 + lenOfst)) + + elif obj.ToolController.Tool.ToolType == 'BallEndMill' and FR > 0.0: + # Bull Nose or Corner Radius cutter + # Reference: https://www.fine-tools.com/halbstabfraeser.html + # OCL -> BallCutter::BallCutter(diameter, length) + return ocl.BullCutter(diam_1, FR, (CEH + lenOfst)) + + elif obj.ToolController.Tool.ToolType == 'Engraver' and FR > 0.0: + # Bull Nose or Corner Radius cutter + # Reference: https://www.fine-tools.com/halbstabfraeser.html + # OCL -> ConeCutter::ConeCutter(diameter, angle, lengthOffset) + return ocl.ConeCutter(diam_1, (CEA / 2), lenOfst) + + elif obj.ToolController.Tool.ToolType == 'ChamferMill': + # Bull Nose or Corner Radius cutter + # Reference: https://www.fine-tools.com/halbstabfraeser.html + # OCL -> ConeCutter::ConeCutter(diameter, angle, lengthOffset) + return ocl.ConeCutter(diam_1, (CEA / 2), lenOfst) + else: + # Default to standard end mill + PathLog.warning("Defaulting cutter to standard end mill.") + return ocl.CylCutter(diam_1, (CEH + lenOfst)) + + # http://www.carbidecutter.net/products/carbide-burr-cone-shape-sm.html + ''' + # Available FreeCAD cutter types - some still need translation to available OCL cutter classes. + Drill, CenterDrill, CounterSink, CounterBore, FlyCutter, Reamer, Tap, + EndMill, SlotCutter, BallEndMill, ChamferMill, CornerRound, Engraver + ''' + # Adittional problem is with new ToolBit user-defined cutter shapes. + # Some sort of translation/conversion will have to be defined to make compatible with OCL. + PathLog.error('Unable to set OCL cutter.') + return False + + +def SetupProperties(): + ''' SetupProperties() ... Return list of properties required for operation.''' + setup = [] + setup.append('Algorithm') + setup.append('AngularDeflection') + setup.append('AvoidLastX_Faces') + setup.append('AvoidLastX_InternalFeatures') + setup.append('BoundBox') + setup.append('BoundaryAdjustment') + setup.append('CircularCenterAt') + setup.append('CircularCenterCustom') + setup.append('ClearLastLayer') + setup.append('CutMode') + setup.append('CutPattern') + setup.append('CutPatternAngle') + setup.append('CutPatternReversed') + setup.append('DepthOffset') + setup.append('GapSizes') + setup.append('GapThreshold') + setup.append('HandleMultipleFeatures') + setup.append('InternalFeaturesCut') + setup.append('InternalFeaturesAdjustment') + setup.append('LayerMode') + setup.append('LinearDeflection') + setup.append('OptimizeStepOverTransitions') + setup.append('ProfileEdges') + setup.append('BoundaryEnforcement') + setup.append('SampleInterval') + setup.append('StartPoint') + setup.append('StepOver') + setup.append('UseStartPoint') + # For debugging + setup.append('ShowTempObjects') + return setup + + +def Create(name, obj=None): + '''Create(name) ... Creates and returns a Waterline operation.''' + if obj is None: + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) + obj.Proxy = ObjectWaterline(obj, name) + return obj diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py new file mode 100644 index 0000000000..eed15fc3d3 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2020 sliptonic * +# * Copyright (c) 2020 russ4262 * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import FreeCADGui +import PathScripts.PathWaterline as PathWaterline +import PathScripts.PathGui as PathGui +import PathScripts.PathOpGui as PathOpGui + +from PySide import QtCore + +__title__ = "Path Waterline Operation UI" +__author__ = "sliptonic (Brad Collette), russ4262 (Russell Johnson)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Waterline operation page controller and command implementation." + + +class TaskPanelOpPage(PathOpGui.TaskPanelPage): + '''Page controller class for the Waterline operation.''' + + def initPage(self, obj): + # self.setTitle("Waterline") + self.updateVisibility() + + def getForm(self): + '''getForm() ... returns UI''' + return FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui") + + def getFields(self, obj): + '''getFields(obj) ... transfers values from UI to obj's proprties''' + self.updateToolController(obj, self.form.toolController) + + if obj.Algorithm != str(self.form.algorithmSelect.currentText()): + obj.Algorithm = str(self.form.algorithmSelect.currentText()) + + if obj.BoundBox != str(self.form.boundBoxSelect.currentText()): + obj.BoundBox = str(self.form.boundBoxSelect.currentText()) + + if obj.LayerMode != str(self.form.layerMode.currentText()): + obj.LayerMode = str(self.form.layerMode.currentText()) + + if obj.CutPattern != str(self.form.cutPattern.currentText()): + obj.CutPattern = str(self.form.cutPattern.currentText()) + + PathGui.updateInputField(obj, 'BoundaryAdjustment', self.form.boundaryAdjustment) + + if obj.StepOver != self.form.stepOver.value(): + obj.StepOver = self.form.stepOver.value() + + PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + + if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): + obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() + + def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' + self.setupToolController(obj, self.form.toolController) + self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect) + self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) + self.selectInComboBox(obj.LayerMode, self.form.layerMode) + self.selectInComboBox(obj.CutPattern, self.form.cutPattern) + self.form.boundaryAdjustment.setText(FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString) + self.form.stepOver.setValue(obj.StepOver) + self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString) + + if obj.OptimizeLinearPaths: + self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked) + else: + self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked) + + def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + signals = [] + signals.append(self.form.toolController.currentIndexChanged) + signals.append(self.form.algorithmSelect.currentIndexChanged) + signals.append(self.form.boundBoxSelect.currentIndexChanged) + signals.append(self.form.layerMode.currentIndexChanged) + signals.append(self.form.cutPattern.currentIndexChanged) + signals.append(self.form.boundaryAdjustment.editingFinished) + signals.append(self.form.stepOver.editingFinished) + signals.append(self.form.sampleInterval.editingFinished) + signals.append(self.form.optimizeEnabled.stateChanged) + + return signals + + def updateVisibility(self): + if self.form.algorithmSelect.currentText() == 'OCL Dropcutter': + self.form.cutPattern.setEnabled(False) + self.form.boundaryAdjustment.setEnabled(False) + self.form.stepOver.setEnabled(False) + self.form.sampleInterval.setEnabled(True) + self.form.optimizeEnabled.setEnabled(True) + else: + self.form.cutPattern.setEnabled(True) + self.form.boundaryAdjustment.setEnabled(True) + if self.form.cutPattern.currentText() == 'None': + self.form.stepOver.setEnabled(False) + else: + self.form.stepOver.setEnabled(True) + self.form.sampleInterval.setEnabled(False) + self.form.optimizeEnabled.setEnabled(False) + + def registerSignalHandlers(self, obj): + self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility) + self.form.cutPattern.currentIndexChanged.connect(self.updateVisibility) + + +Command = PathOpGui.SetupOperation('Waterline', + PathWaterline.Create, + TaskPanelOpPage, + 'Path-Waterline', + QtCore.QT_TRANSLATE_NOOP("Waterline", "Waterline"), + QtCore.QT_TRANSLATE_NOOP("Waterline", "Create a Waterline Operation from a model"), + PathWaterline.SetupProperties) + +FreeCAD.Console.PrintLog("Loading PathWaterlineGui... done\n") diff --git a/src/Mod/Path/libarea/kurve/geometry.h b/src/Mod/Path/libarea/kurve/geometry.h index 9b3b7366a7..2da0f23577 100644 --- a/src/Mod/Path/libarea/kurve/geometry.h +++ b/src/Mod/Path/libarea/kurve/geometry.h @@ -604,7 +604,7 @@ inline bool FNEZ(double a, double tolerance = TIGHT_TOLERANCE) {return fabs(a) > double atn360(double dx, double dy); // angle 0 to 2pi // distance functions - //double Dist(double px, double py, double p1x, double p1y); // diatance between 2 points (2d) + //double Dist(double px, double py, double p1x, double p1y); // distance between 2 points (2d) //double Dist(Point& p0, Point& p1); // distance between 2 points (3d) //double Dist(CLine& s, Point& p1); // distance between cline & point diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp index b8235c5ac2..1a43878cab 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp @@ -29,6 +29,8 @@ # include # include # include +# include +# include # include #endif @@ -663,34 +665,34 @@ void TaskSketcherElements::leaveEvent (QEvent * event) void TaskSketcherElements::slotElementsChanged(void) { - QIcon Sketcher_Element_Arc_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_Edge") ); - QIcon Sketcher_Element_Arc_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_EndPoint") ); - QIcon Sketcher_Element_Arc_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_MidPoint") ); - QIcon Sketcher_Element_Arc_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_StartingPoint") ); - QIcon Sketcher_Element_Circle_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Circle_Edge") ); - QIcon Sketcher_Element_Circle_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Circle_MidPoint") ); - QIcon Sketcher_Element_Line_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Line_Edge") ); - QIcon Sketcher_Element_Line_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Line_EndPoint") ); - QIcon Sketcher_Element_Line_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Line_StartingPoint") ); - QIcon Sketcher_Element_Point_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Point_StartingPoint") ); - QIcon Sketcher_Element_Ellipse_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Ellipse_Edge_2") ); - QIcon Sketcher_Element_Ellipse_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Ellipse_CentrePoint") ); - QIcon Sketcher_Element_ArcOfEllipse_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_Edge") ); - QIcon Sketcher_Element_ArcOfEllipse_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_Centre_Point") ); - QIcon Sketcher_Element_ArcOfEllipse_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfEllipse_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_End_Point") ); - QIcon Sketcher_Element_ArcOfHyperbola_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_Edge") ); - QIcon Sketcher_Element_ArcOfHyperbola_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_Centre_Point") ); - QIcon Sketcher_Element_ArcOfHyperbola_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfHyperbola_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_End_Point") ); - QIcon Sketcher_Element_ArcOfParabola_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_Edge") ); - QIcon Sketcher_Element_ArcOfParabola_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_Centre_Point") ); - QIcon Sketcher_Element_ArcOfParabola_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfParabola_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_End_Point") ); - QIcon Sketcher_Element_BSpline_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_BSpline_Edge") ); - QIcon Sketcher_Element_BSpline_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_BSpline_StartPoint") ); - QIcon Sketcher_Element_BSpline_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_BSpline_EndPoint") ); - QIcon none( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_SelectionTypeInvalid") ); + MultIcon Sketcher_Element_Arc_Edge("Sketcher_Element_Arc_Edge"); + MultIcon Sketcher_Element_Arc_EndPoint("Sketcher_Element_Arc_EndPoint"); + MultIcon Sketcher_Element_Arc_MidPoint("Sketcher_Element_Arc_MidPoint"); + MultIcon Sketcher_Element_Arc_StartingPoint("Sketcher_Element_Arc_StartingPoint"); + MultIcon Sketcher_Element_Circle_Edge("Sketcher_Element_Circle_Edge"); + MultIcon Sketcher_Element_Circle_MidPoint("Sketcher_Element_Circle_MidPoint"); + MultIcon Sketcher_Element_Line_Edge("Sketcher_Element_Line_Edge"); + MultIcon Sketcher_Element_Line_EndPoint("Sketcher_Element_Line_EndPoint"); + MultIcon Sketcher_Element_Line_StartingPoint("Sketcher_Element_Line_StartingPoint"); + MultIcon Sketcher_Element_Point_StartingPoint("Sketcher_Element_Point_StartingPoint"); + MultIcon Sketcher_Element_Ellipse_Edge("Sketcher_Element_Ellipse_Edge_2"); + MultIcon Sketcher_Element_Ellipse_MidPoint("Sketcher_Element_Ellipse_CentrePoint"); + MultIcon Sketcher_Element_ArcOfEllipse_Edge("Sketcher_Element_Elliptical_Arc_Edge"); + MultIcon Sketcher_Element_ArcOfEllipse_MidPoint("Sketcher_Element_Elliptical_Arc_Centre_Point"); + MultIcon Sketcher_Element_ArcOfEllipse_StartingPoint("Sketcher_Element_Elliptical_Arc_Start_Point"); + MultIcon Sketcher_Element_ArcOfEllipse_EndPoint("Sketcher_Element_Elliptical_Arc_End_Point"); + MultIcon Sketcher_Element_ArcOfHyperbola_Edge("Sketcher_Element_Hyperbolic_Arc_Edge"); + MultIcon Sketcher_Element_ArcOfHyperbola_MidPoint("Sketcher_Element_Hyperbolic_Arc_Centre_Point"); + MultIcon Sketcher_Element_ArcOfHyperbola_StartingPoint("Sketcher_Element_Hyperbolic_Arc_Start_Point"); + MultIcon Sketcher_Element_ArcOfHyperbola_EndPoint("Sketcher_Element_Hyperbolic_Arc_End_Point"); + MultIcon Sketcher_Element_ArcOfParabola_Edge("Sketcher_Element_Parabolic_Arc_Edge"); + MultIcon Sketcher_Element_ArcOfParabola_MidPoint("Sketcher_Element_Parabolic_Arc_Centre_Point"); + MultIcon Sketcher_Element_ArcOfParabola_StartingPoint("Sketcher_Element_Parabolic_Arc_Start_Point"); + MultIcon Sketcher_Element_ArcOfParabola_EndPoint("Sketcher_Element_Parabolic_Arc_End_Point"); + MultIcon Sketcher_Element_BSpline_Edge("Sketcher_Element_BSpline_Edge"); + MultIcon Sketcher_Element_BSpline_StartingPoint("Sketcher_Element_BSpline_StartPoint"); + MultIcon Sketcher_Element_BSpline_EndPoint("Sketcher_Element_BSpline_EndPoint"); + MultIcon none("Sketcher_Element_SelectionTypeInvalid"); assert(sketchView); // Build up ListView with the elements @@ -707,34 +709,34 @@ void TaskSketcherElements::slotElementsChanged(void) bool construction = (*it)->Construction; ui->listWidgetElements->addItem(new ElementItem( - (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint : - (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge : - (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint : - (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint : - (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge : - (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint : - (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge : - (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint : - none, + (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint.getIcon(construction, false) : + (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge.getIcon(construction, false) : + (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint.getIcon(construction, false) : + (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint.getIcon(construction, false) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge.getIcon(construction, false) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint.getIcon(construction, false) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint.getIcon(construction, false) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint.getIcon(construction, false) : + (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge.getIcon(construction, false) : + (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint.getIcon(construction, false) : + (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge.getIcon(construction, false) : + (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint.getIcon(construction, false) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge.getIcon(construction, false) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint.getIcon(construction, false) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint.getIcon(construction, false) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint.getIcon(construction, false) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge.getIcon(construction, false) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint.getIcon(construction, false) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint.getIcon(construction, false) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint.getIcon(construction, false) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge.getIcon(construction, false) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint.getIcon(construction, false) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint.getIcon(construction, false) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint.getIcon(construction, false) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge.getIcon(construction, false) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint.getIcon(construction, false) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint.getIcon(construction, false) : + none.getIcon(construction, false), type == Part::GeomPoint::getClassTypeId() ? ( isNamingBoxChecked ? (tr("Point") + QString::fromLatin1("(Edge%1)").arg(i)): (QString::fromLatin1("%1-").arg(i)+tr("Point"))) : @@ -813,34 +815,34 @@ void TaskSketcherElements::slotElementsChanged(void) ui->listWidgetElements->addItem(new ElementItem( - (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint : - (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge : - (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint : - (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint : - (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge : - (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint : - (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge : - (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint : - none, + (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint.External : + (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge.External : + (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint.External : + (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint.External : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge.External : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint.External : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint.External : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint.External : + (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge.External : + (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint.External : + (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge.External : + (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint.External : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge.External : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint.External : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint.External : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint.External : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge.External : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint.External : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint.External : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint.External : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge.External : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint.External : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint.External : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint.External : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge.External : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint.External : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint.External : + none.External, type == Part::GeomPoint::getClassTypeId() ? ( isNamingBoxChecked ? (tr("Point") + linkname): (QString::fromLatin1("%1-").arg(i-2)+tr("Point"))) : @@ -1033,67 +1035,70 @@ void TaskSketcherElements::updateVisibility(int filterindex) void TaskSketcherElements::updateIcons(int element) { - QIcon Sketcher_Element_Arc_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_Edge") ); - QIcon Sketcher_Element_Arc_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_EndPoint") ); - QIcon Sketcher_Element_Arc_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_MidPoint") ); - QIcon Sketcher_Element_Arc_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_StartingPoint") ); - QIcon Sketcher_Element_Circle_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Circle_Edge") ); - QIcon Sketcher_Element_Circle_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Circle_MidPoint") ); - QIcon Sketcher_Element_Line_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Line_Edge") ); - QIcon Sketcher_Element_Line_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Line_EndPoint") ); - QIcon Sketcher_Element_Line_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Line_StartingPoint") ); - QIcon Sketcher_Element_Point_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Point_StartingPoint") ); - QIcon Sketcher_Element_Ellipse_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Ellipse_Edge_2") ); - QIcon Sketcher_Element_Ellipse_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Ellipse_CentrePoint") ); - QIcon Sketcher_Element_ArcOfEllipse_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_Edge") ); - QIcon Sketcher_Element_ArcOfEllipse_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_Centre_Point") ); - QIcon Sketcher_Element_ArcOfEllipse_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfEllipse_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_End_Point") ); - QIcon Sketcher_Element_ArcOfHyperbola_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_Edge") ); - QIcon Sketcher_Element_ArcOfHyperbola_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_Centre_Point") ); - QIcon Sketcher_Element_ArcOfHyperbola_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfHyperbola_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_End_Point") ); - QIcon Sketcher_Element_ArcOfParabola_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_Edge") ); - QIcon Sketcher_Element_ArcOfParabola_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_Centre_Point") ); - QIcon Sketcher_Element_ArcOfParabola_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfParabola_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_End_Point") ); - QIcon Sketcher_Element_BSpline_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_BSpline_Edge") ); - QIcon Sketcher_Element_BSpline_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_BSpline_StartPoint") ); - QIcon Sketcher_Element_BSpline_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_BSpline_EndPoint") ); - QIcon none( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_SelectionTypeInvalid") ); + MultIcon Sketcher_Element_Arc_Edge("Sketcher_Element_Arc_Edge"); + MultIcon Sketcher_Element_Arc_EndPoint("Sketcher_Element_Arc_EndPoint"); + MultIcon Sketcher_Element_Arc_MidPoint("Sketcher_Element_Arc_MidPoint"); + MultIcon Sketcher_Element_Arc_StartingPoint("Sketcher_Element_Arc_StartingPoint"); + MultIcon Sketcher_Element_Circle_Edge("Sketcher_Element_Circle_Edge"); + MultIcon Sketcher_Element_Circle_MidPoint("Sketcher_Element_Circle_MidPoint"); + MultIcon Sketcher_Element_Line_Edge("Sketcher_Element_Line_Edge"); + MultIcon Sketcher_Element_Line_EndPoint("Sketcher_Element_Line_EndPoint"); + MultIcon Sketcher_Element_Line_StartingPoint("Sketcher_Element_Line_StartingPoint"); + MultIcon Sketcher_Element_Point_StartingPoint("Sketcher_Element_Point_StartingPoint"); + MultIcon Sketcher_Element_Ellipse_Edge("Sketcher_Element_Ellipse_Edge_2"); + MultIcon Sketcher_Element_Ellipse_MidPoint("Sketcher_Element_Ellipse_CentrePoint"); + MultIcon Sketcher_Element_ArcOfEllipse_Edge("Sketcher_Element_Elliptical_Arc_Edge"); + MultIcon Sketcher_Element_ArcOfEllipse_MidPoint("Sketcher_Element_Elliptical_Arc_Centre_Point"); + MultIcon Sketcher_Element_ArcOfEllipse_StartingPoint("Sketcher_Element_Elliptical_Arc_Start_Point"); + MultIcon Sketcher_Element_ArcOfEllipse_EndPoint("Sketcher_Element_Elliptical_Arc_End_Point"); + MultIcon Sketcher_Element_ArcOfHyperbola_Edge("Sketcher_Element_Hyperbolic_Arc_Edge"); + MultIcon Sketcher_Element_ArcOfHyperbola_MidPoint("Sketcher_Element_Hyperbolic_Arc_Centre_Point"); + MultIcon Sketcher_Element_ArcOfHyperbola_StartingPoint("Sketcher_Element_Hyperbolic_Arc_Start_Point"); + MultIcon Sketcher_Element_ArcOfHyperbola_EndPoint("Sketcher_Element_Hyperbolic_Arc_End_Point"); + MultIcon Sketcher_Element_ArcOfParabola_Edge("Sketcher_Element_Parabolic_Arc_Edge"); + MultIcon Sketcher_Element_ArcOfParabola_MidPoint("Sketcher_Element_Parabolic_Arc_Centre_Point"); + MultIcon Sketcher_Element_ArcOfParabola_StartingPoint("Sketcher_Element_Parabolic_Arc_Start_Point"); + MultIcon Sketcher_Element_ArcOfParabola_EndPoint("Sketcher_Element_Parabolic_Arc_End_Point"); + MultIcon Sketcher_Element_BSpline_Edge("Sketcher_Element_BSpline_Edge"); + MultIcon Sketcher_Element_BSpline_StartingPoint("Sketcher_Element_BSpline_StartPoint"); + MultIcon Sketcher_Element_BSpline_EndPoint("Sketcher_Element_BSpline_EndPoint"); + MultIcon none("Sketcher_Element_SelectionTypeInvalid"); + for (int i=0;ilistWidgetElements->count(); i++) { Base::Type type = static_cast(ui->listWidgetElements->item(i))->GeometryType; - + bool construction = static_cast(ui->listWidgetElements->item(i))->isConstruction; + bool external = static_cast(ui->listWidgetElements->item(i))->isExternal; + ui->listWidgetElements->item(i)->setIcon( - (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint : - (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge : - (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint : - (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint : - (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge : - (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint : - (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge : - (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint : - none); + (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint.getIcon(construction, external) : + (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge.getIcon(construction, external) : + (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint.getIcon(construction, external) : + (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint.getIcon(construction, external) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge.getIcon(construction, external) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint.getIcon(construction, external) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint.getIcon(construction, external) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint.getIcon(construction, external) : + (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge.getIcon(construction, external) : + (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint.getIcon(construction, external) : + (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge.getIcon(construction, external) : + (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint.getIcon(construction, external) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge.getIcon(construction, external) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint.getIcon(construction, external) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint.getIcon(construction, external) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint.getIcon(construction, external) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge.getIcon(construction, external) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint.getIcon(construction, external) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint.getIcon(construction, external) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint.getIcon(construction, external) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge.getIcon(construction, external) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint.getIcon(construction, external) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint.getIcon(construction, external) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint.getIcon(construction, external) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge.getIcon(construction, external) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint.getIcon(construction, external) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint.getIcon(construction, external) : + none.getIcon(construction, external)); } } @@ -1105,6 +1110,45 @@ void TaskSketcherElements::changeEvent(QEvent *e) } } +TaskSketcherElements::MultIcon::MultIcon(const char* name) +{ + int hue, sat, val, alp; + Normal = Gui::BitmapFactory().iconFromTheme(name); + QImage imgConstr(Normal.pixmap(Normal.availableSizes()[0]).toImage()); + QImage imgExt(imgConstr); + + for(int ix=0 ; ix 127 && hue >= 0) { + if (sat > 127 && (hue > 330 || hue < 30)) { + clr.setHsv((hue + 240) % 360, sat, val, alp); + imgConstr.setPixel(ix, iy, clr.rgba()); + clr.setHsv((hue + 300) % 360, sat, val, alp); + imgExt.setPixel(ix, iy, clr.rgba()); + } + else if (sat < 64 && val > 192) + { + clr.setHsv(240, (255-sat), val, alp); + imgConstr.setPixel(ix, iy, clr.rgba()); + clr.setHsv(300, (255-sat), val, alp); + imgExt.setPixel(ix, iy, clr.rgba()); + } + } + } + } + Construction = QIcon(QPixmap::fromImage(imgConstr)); + External = QIcon(QPixmap::fromImage(imgExt)); +} + +QIcon TaskSketcherElements::MultIcon::getIcon(bool construction, bool external) const +{ + if (construction && external) return QIcon(); + if (construction) return Construction; + if (external) return External; + return Normal; +} #include "moc_TaskSketcherElements.cpp" diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.h b/src/Mod/Sketcher/Gui/TaskSketcherElements.h index 64c0619a5e..493e81ac8b 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.h @@ -28,6 +28,7 @@ #include #include #include +#include namespace App { class Property; @@ -91,6 +92,18 @@ class TaskSketcherElements : public Gui::TaskView::TaskBox, public Gui::Selectio { Q_OBJECT + class MultIcon { + + public: + MultIcon(const char*); + + QIcon Normal; + QIcon Construction; + QIcon External; + + QIcon getIcon(bool construction, bool external) const; + }; + public: TaskSketcherElements(ViewProviderSketch *sketchView); ~TaskSketcherElements(); diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index 28012a70f5..a9a7afdd2d 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -1067,7 +1067,7 @@ void PropertySheet::removeDependencies(CellAddress key) void PropertySheet::recomputeDependants(const App::DocumentObject *owner, const char *propName) { // First, search without actual property name for sub-object/link - // references, i.e indirect references. The depenedecies of these + // references, i.e indirect references. The dependencies of these // references are too complex to track exactly, so we only track the // top parent object instead, and mark the involved expression // whenever the top parent changes. diff --git a/src/Mod/TechDraw/App/DrawUtil.cpp b/src/Mod/TechDraw/App/DrawUtil.cpp index 786daaf79b..27908dbcca 100644 --- a/src/Mod/TechDraw/App/DrawUtil.cpp +++ b/src/Mod/TechDraw/App/DrawUtil.cpp @@ -570,6 +570,13 @@ Base::Vector3d DrawUtil::invertY(Base::Vector3d v) return result; } +QPointF DrawUtil::invertY(QPointF v) +{ + QPointF result(v.x(), -v.y()); + return result; +} + + //obs? was used in CSV prototype of Cosmetics std::vector DrawUtil::split(std::string csvLine) { diff --git a/src/Mod/TechDraw/App/DrawUtil.h b/src/Mod/TechDraw/App/DrawUtil.h index 8b9f66c92a..aa70753220 100644 --- a/src/Mod/TechDraw/App/DrawUtil.h +++ b/src/Mod/TechDraw/App/DrawUtil.h @@ -110,6 +110,7 @@ class TechDrawExport DrawUtil { static std::string shapeToString(TopoDS_Shape s); static TopoDS_Shape shapeFromString(std::string s); static Base::Vector3d invertY(Base::Vector3d v); + static QPointF invertY(QPointF p); static std::vector split(std::string csvLine); static std::vector tokenize(std::string csvLine, std::string delimiter = ",$$$,"); static App::Color pyTupleToColor(PyObject* pColor); diff --git a/src/Mod/TechDraw/App/DrawViewDetail.cpp b/src/Mod/TechDraw/App/DrawViewDetail.cpp index 4e2e3f21ed..53c02f5894 100644 --- a/src/Mod/TechDraw/App/DrawViewDetail.cpp +++ b/src/Mod/TechDraw/App/DrawViewDetail.cpp @@ -100,8 +100,6 @@ using namespace std; PROPERTY_SOURCE(TechDraw::DrawViewDetail, TechDraw::DrawViewPart) DrawViewDetail::DrawViewDetail() -// : -// m_mattingStyle(0) { static const char *dgroup = "Detail"; diff --git a/src/Mod/TechDraw/App/DrawViewSymbol.cpp b/src/Mod/TechDraw/App/DrawViewSymbol.cpp index 26319064a5..2000508a94 100644 --- a/src/Mod/TechDraw/App/DrawViewSymbol.cpp +++ b/src/Mod/TechDraw/App/DrawViewSymbol.cpp @@ -63,7 +63,7 @@ DrawViewSymbol::DrawViewSymbol(void) { static const char *vgroup = "Drawing view"; - ADD_PROPERTY_TYPE(Symbol,(""),vgroup,App::Prop_Hidden,"The SVG code defining this symbol"); + ADD_PROPERTY_TYPE(Symbol,(""),vgroup,App::Prop_None,"The SVG code defining this symbol"); ADD_PROPERTY_TYPE(EditableTexts,(""),vgroup,App::Prop_None,"Substitution values for the editable strings in this symbol"); ScaleType.setValue("Custom"); } @@ -83,7 +83,14 @@ void DrawViewSymbol::onChanged(const App::Property* prop) std::vector editables; QDomDocument symbolDocument; - if (symbolDocument.setContent(QString::fromUtf8(Symbol.getValue()))) { + const char* symbol = Symbol.getValue(); + QByteArray qba(symbol); + QString errorMsg; + int errorLine; + int errorCol; + bool nsProcess = false; + bool rc = symbolDocument.setContent(qba, nsProcess, &errorMsg, &errorLine, &errorCol); + if (rc) { QDomElement symbolDocElem = symbolDocument.documentElement(); QXmlQuery query(QXmlQuery::XQuery10); @@ -107,7 +114,13 @@ void DrawViewSymbol::onChanged(const App::Property* prop) } } else { - Base::Console().Warning("DrawViewSymbol:onChanged - SVG for Symbol is not a valid document\n"); + Base::Console().Warning("DVS::onChanged - %s - SVG for Symbol is not valid. See log.\n"); + Base::Console().Log( + "Warning: DVS::onChanged(Symbol) for %s - len: %d rc: %d error: %s line: %d col: %d\n", + getNameInDocument(), strlen(symbol), rc, + qPrintable(errorMsg), errorLine, errorCol); + + } EditableTexts.setValues(editables); @@ -127,12 +140,22 @@ App::DocumentObjectExecReturn *DrawViewSymbol::execute(void) // } std::string svg = Symbol.getValue(); + if (svg.empty()) { + return App::DocumentObject::StdReturn; + } + const std::vector& editText = EditableTexts.getValues(); if (!editText.empty()) { QDomDocument symbolDocument; - - if (symbolDocument.setContent(QString::fromUtf8(Symbol.getValue()))) { + const char* symbol = Symbol.getValue(); + QByteArray qba(symbol); + QString errorMsg; + int errorLine; + int errorCol; + bool nsProcess = false; + bool rc = symbolDocument.setContent(qba, nsProcess, &errorMsg, &errorLine, &errorCol); + if (rc) { QDomElement symbolDocElem = symbolDocument.documentElement(); QXmlQuery query(QXmlQuery::XQuery10); @@ -171,8 +194,12 @@ App::DocumentObjectExecReturn *DrawViewSymbol::execute(void) Symbol.setValue(symbolDocument.toString(1).toStdString()); } else { - Base::Console().Warning("DrawViewSymbol:execute - SVG for Symbol is not a valid document\n"); - } + Base::Console().Warning("DVS::execute - %s - SVG for Symbol is not valid. See log.\n"); + Base::Console().Log( + "Warning: DVS::execute() - %s - len: %d rc: %d error: %s line: %d col: %d\n", + getNameInDocument(), strlen(symbol), rc, + qPrintable(errorMsg), errorLine, errorCol); + } } // requestPaint(); diff --git a/src/Mod/TechDraw/App/DrawWeldSymbol.cpp b/src/Mod/TechDraw/App/DrawWeldSymbol.cpp index 155c7dea99..0279868ea6 100644 --- a/src/Mod/TechDraw/App/DrawWeldSymbol.cpp +++ b/src/Mod/TechDraw/App/DrawWeldSymbol.cpp @@ -62,6 +62,7 @@ DrawWeldSymbol::DrawWeldSymbol(void) Caption.setStatus(App::Property::Hidden,true); Scale.setStatus(App::Property::Hidden,true); ScaleType.setStatus(App::Property::Hidden,true); + Rotation.setStatus(App::Property::Hidden, true); } DrawWeldSymbol::~DrawWeldSymbol() diff --git a/src/Mod/TechDraw/Gui/CMakeLists.txt b/src/Mod/TechDraw/Gui/CMakeLists.txt index fb03ba059a..43ab8e5eeb 100644 --- a/src/Mod/TechDraw/Gui/CMakeLists.txt +++ b/src/Mod/TechDraw/Gui/CMakeLists.txt @@ -70,6 +70,8 @@ set(TechDrawGui_MOC_HDRS QGIWeldSymbol.h SymbolChooser.h TaskActiveView.h + TaskDetail.h + QGIGhostHighlight.h ) fc_wrap_cpp(TechDrawGui_MOC_SRCS ${TechDrawGui_MOC_HDRS}) @@ -103,6 +105,7 @@ set(TechDrawGui_UIC_SRCS TaskWeldingSymbol.ui SymbolChooser.ui TaskActiveView.ui + TaskDetail.ui ) if(BUILD_QT5) @@ -203,6 +206,9 @@ SET(TechDrawGui_SRCS TaskActiveView.h Grabber3d.cpp Grabber3d.h + TaskDetail.ui + TaskDetail.cpp + TaskDetail.h ) SET(TechDrawGuiView_SRCS @@ -299,6 +305,8 @@ SET(TechDrawGuiView_SRCS TemplateTextField.cpp TemplateTextField.h ZVALUE.h + QGIGhostHighlight.cpp + QGIGhostHighlight.h ) SET(TechDrawGuiViewProvider_SRCS ViewProviderPage.cpp @@ -366,6 +374,7 @@ SET(TechDrawGuiTaskDlgs_SRCS TaskWeldingSymbol.ui SymbolChooser.ui TaskActiveView.ui + TaskDetail.ui ) SOURCE_GROUP("TaskDialogs" FILES ${TechDrawGuiTaskDlgs_SRCS}) diff --git a/src/Mod/TechDraw/Gui/Command.cpp b/src/Mod/TechDraw/Gui/Command.cpp index e28271917e..89b680d198 100644 --- a/src/Mod/TechDraw/Gui/Command.cpp +++ b/src/Mod/TechDraw/Gui/Command.cpp @@ -1,1392 +1,1378 @@ -/*************************************************************************** - * * - * This program 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. * - * for detail see the LICENCE text file. * - * Jürgen Riegel 2002 * - * Copyright (c) 2014 Luke Parry * - * * - ***************************************************************************/ - -#include "PreCompiled.h" -#ifndef _PreComp_ -# include -# include -# include -# include -# include -# include -# include -#endif - -#include - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "DrawGuiUtil.h" -#include "MDIViewPage.h" -#include "TaskProjGroup.h" -#include "TaskSectionView.h" -#include "TaskActiveView.h" -#include "ViewProviderPage.h" - -using namespace TechDrawGui; -using namespace std; - - -//=========================================================================== -// TechDraw_PageDefault -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawPageDefault) - -CmdTechDrawPageDefault::CmdTechDrawPageDefault() - : Command("TechDraw_PageDefault") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Default Page"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_PageDefault"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-PageDefault"; -} - -void CmdTechDrawPageDefault::activated(int iMsg) -{ - Q_UNUSED(iMsg); - Base::Reference hGrp = App::GetApplication().GetUserParameter() - .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Files"); - - std::string defaultDir = App::Application::getResourceDir() + "Mod/TechDraw/Templates/"; - std::string defaultFileName = defaultDir + "A4_LandscapeTD.svg"; - QString templateFileName = QString::fromStdString(hGrp->GetASCII("TemplateFile",defaultFileName.c_str())); - if (templateFileName.isEmpty()) { - templateFileName = QString::fromStdString(defaultFileName); - } - - std::string PageName = getUniqueObjectName("Page"); - std::string TemplateName = getUniqueObjectName("Template"); - - QFileInfo tfi(templateFileName); - if (tfi.isReadable()) { - Gui::WaitCursor wc; - openCommand("Drawing create page"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawPage','%s')",PageName.c_str()); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawSVGTemplate','%s')",TemplateName.c_str()); - - doCommand(Doc,"App.activeDocument().%s.Template = '%s'",TemplateName.c_str(), templateFileName.toStdString().c_str()); - doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str()); - - commitCommand(); - TechDraw::DrawPage* fp = dynamic_cast(getDocument()->getObject(PageName.c_str())); - if (!fp) { - throw Base::TypeError("CmdTechDrawPageDefault fp not found\n"); - } - - Gui::ViewProvider* vp = Gui::Application::Instance->getDocument(getDocument())->getViewProvider(fp); - TechDrawGui::ViewProviderPage* dvp = dynamic_cast(vp); - if (dvp) { - dvp->show(); - } - else { - Base::Console().Log("INFO - Template: %s for Page: %s NOT Found\n", PageName.c_str(),TemplateName.c_str()); - } - } else { - QMessageBox::critical(Gui::getMainWindow(), - QLatin1String("No template"), - QLatin1String("No default template found")); - } -} - -bool CmdTechDrawPageDefault::isActive(void) -{ - return hasActiveDocument(); -} - -//=========================================================================== -// TechDraw_PageTemplate -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawPageTemplate) - -CmdTechDrawPageTemplate::CmdTechDrawPageTemplate() - : Command("TechDraw_PageTemplate") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Page using Template"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_PageTemplate"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-PageTemplate"; -} - -void CmdTechDrawPageTemplate::activated(int iMsg) -{ - Q_UNUSED(iMsg); - Base::Reference hGrp = App::GetApplication().GetUserParameter() - .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Files"); - - std::string defaultDir = App::Application::getResourceDir() + "Mod/TechDraw/Templates"; - QString templateDir = QString::fromStdString(hGrp->GetASCII("TemplateDir", defaultDir.c_str())); - QString templateFileName = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), - QString::fromUtf8(QT_TR_NOOP("Select a Template File")), - templateDir, - QString::fromUtf8(QT_TR_NOOP("Template (*.svg *.dxf)"))); - - if (templateFileName.isEmpty()) { - return; - } - - std::string PageName = getUniqueObjectName("Page"); - std::string TemplateName = getUniqueObjectName("Template"); - - QFileInfo tfi(templateFileName); - if (tfi.isReadable()) { - Gui::WaitCursor wc; - openCommand("Drawing create page"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawPage','%s')",PageName.c_str()); - - // Create the Template Object to attach to the page - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawSVGTemplate','%s')",TemplateName.c_str()); - - //why is "Template" property set twice? -wf - // once to set DrawSVGTemplate.Template to OS template file name - templateFileName = Base::Tools::escapeEncodeFilename(templateFileName); - doCommand(Doc,"App.activeDocument().%s.Template = \"%s\"",TemplateName.c_str(), templateFileName.toUtf8().constData()); - // once to set Page.Template to DrawSVGTemplate.Name - doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str()); - // consider renaming DrawSVGTemplate.Template property? - - commitCommand(); - TechDraw::DrawPage* fp = dynamic_cast(getDocument()->getObject(PageName.c_str())); - if (!fp) { - throw Base::TypeError("CmdTechDrawNewPagePick fp not found\n"); - } - Gui::ViewProvider* vp = Gui::Application::Instance->getDocument(getDocument())->getViewProvider(fp); - TechDrawGui::ViewProviderPage* dvp = dynamic_cast(vp); - if (dvp) { - dvp->show(); - } - else { - Base::Console().Log("INFO - Template: %s for Page: %s NOT Found\n", PageName.c_str(),TemplateName.c_str()); - } - } - else { - QMessageBox::critical(Gui::getMainWindow(), - QLatin1String("No template"), - QLatin1String("Template file is invalid")); - } -} - -bool CmdTechDrawPageTemplate::isActive(void) -{ - return hasActiveDocument(); -} - -//=========================================================================== -// TechDraw_RedrawPage -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawRedrawPage) - -CmdTechDrawRedrawPage::CmdTechDrawRedrawPage() - : Command("TechDraw_RedrawPage") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Redraw Page"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_RedrawPage"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-RedrawPage"; -} - -void CmdTechDrawRedrawPage::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - Gui::WaitCursor wc; - - page->redrawCommand(); -} - -bool CmdTechDrawRedrawPage::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool haveView = DrawGuiUtil::needView(this,false); - return (havePage && haveView); -} - -//=========================================================================== -// TechDraw_View -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawView) - -CmdTechDrawView::CmdTechDrawView() - : Command("TechDraw_View") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert View"); - sToolTipText = QT_TR_NOOP("Insert a View"); - sWhatsThis = "TechDraw_View"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-View"; -} - -void CmdTechDrawView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - //set projection direction from selected Face - //use first object with a face selected - std::vector shapes; - App::DocumentObject* partObj = nullptr; - std::string faceName; - int resolve = 1; //mystery - bool single = false; //mystery - auto selection = getSelection().getSelectionEx(0, - App::DocumentObject::getClassTypeId(), - resolve, - single); - for (auto& sel: selection) { - auto obj = sel.getObject(); - if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId()) ) { - continue; - } - if (obj != nullptr) { - shapes.push_back(obj); - } - if(partObj != nullptr) { - continue; - } - for(auto& sub : sel.getSubNames()) { - if (TechDraw::DrawUtil::getGeomTypeFromName(sub) == "Face") { - faceName = sub; - partObj = obj; - break; - } - } - } - - if ((shapes.empty())) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("No Shapes, Groups or Links in this selection")); - return; - } - - Base::Vector3d projDir; - - Gui::WaitCursor wc; - openCommand("Create view"); - std::string FeatName = getUniqueObjectName("View"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewPart','%s')",FeatName.c_str()); - App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); - TechDraw::DrawViewPart* dvp = dynamic_cast(docObj); - if (!dvp) { - throw Base::TypeError("CmdTechDrawView DVP not found\n"); - } - dvp->Source.setValues(shapes); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); - if (faceName.size()) { - std::pair dirs = DrawGuiUtil::getProjDirFromFace(partObj,faceName); - projDir = dirs.first; - getDocument()->setStatus(App::Document::Status::SkipRecompute, true); - doCommand(Doc,"App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", - FeatName.c_str(), projDir.x,projDir.y,projDir.z); - //do something clever with dirs.second; -// dvp->setXDir(dirs.second); - doCommand(Doc,"App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", - FeatName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - doCommand(Doc,"App.activeDocument().%s.recompute()", FeatName.c_str()); - getDocument()->setStatus(App::Document::Status::SkipRecompute, false); - } else { - std::pair dirs = DrawGuiUtil::get3DDirAndRot(); - projDir = dirs.first; - getDocument()->setStatus(App::Document::Status::SkipRecompute, true); - doCommand(Doc,"App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", - FeatName.c_str(), projDir.x,projDir.y,projDir.z); - doCommand(Doc,"App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", - FeatName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); -// dvp->setXDir(dirs.second); - getDocument()->setStatus(App::Document::Status::SkipRecompute, false); - doCommand(Doc,"App.activeDocument().%s.recompute()", FeatName.c_str()); - } - commitCommand(); -} - -bool CmdTechDrawView::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_ActiveView -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawActiveView) - -CmdTechDrawActiveView::CmdTechDrawActiveView() - : Command("TechDraw_ActiveView") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Active View (3D View)"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_ActiveView"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ActiveView"; -} - -void CmdTechDrawActiveView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - Gui::Control().showDialog(new TaskDlgActiveView(page)); -} - -bool CmdTechDrawActiveView::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_SectionView -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawSectionView) - -CmdTechDrawSectionView::CmdTechDrawSectionView() - : Command("TechDraw_SectionView") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Section View"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_SectionView"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-SectionView"; -} - -void CmdTechDrawSectionView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - - std::vector baseObj = getSelection().getObjectsOfType(TechDraw::DrawViewPart::getClassTypeId()); - if (baseObj.empty()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select at least 1 DrawViewPart object as Base.")); - return; - } - TechDraw::DrawViewPart* dvp = static_cast(*baseObj.begin()); -// std::string BaseName = dvp->getNameInDocument(); -// std::string PageName = page->getNameInDocument(); -// double baseScale = dvp->getScale(); - -// Gui::WaitCursor wc; -// openCommand("Create view"); -// std::string FeatName = getUniqueObjectName("Section"); - -// doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSection','%s')",FeatName.c_str()); - -// App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); -// TechDraw::DrawViewSection* dsv = dynamic_cast(docObj); -// if (!dsv) { -// throw Base::TypeError("CmdTechDrawSectionView DVS not found\n"); -// } -// dsv->Source.setValues(dvp->Source.getValues()); -// doCommand(Doc,"App.activeDocument().%s.BaseView = App.activeDocument().%s",FeatName.c_str(),BaseName.c_str()); -// doCommand(Doc,"App.activeDocument().%s.ScaleType = App.activeDocument().%s.ScaleType",FeatName.c_str(),BaseName.c_str()); -// doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); -// doCommand(Doc,"App.activeDocument().%s.Scale = %0.6f",FeatName.c_str(),baseScale); - Gui::Control().showDialog(new TaskDlgSectionView(dvp)); - - updateActive(); //ok here since dialog doesn't call doc.recompute() - commitCommand(); -} - -bool CmdTechDrawSectionView::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool haveView = DrawGuiUtil::needView(this); - bool taskInProgress = false; - if (havePage) { - taskInProgress = Gui::Control().activeDialog(); - } - return (havePage && haveView && !taskInProgress); -} - -//=========================================================================== -// TechDraw_DetailView -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawDetailView) - -CmdTechDrawDetailView::CmdTechDrawDetailView() - : Command("TechDraw_DetailView") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Detail View"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_DetailView"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-DetailView"; -} - -void CmdTechDrawDetailView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - - std::vector baseObj = getSelection().getObjectsOfType(TechDraw::DrawViewPart::getClassTypeId()); - if (baseObj.empty()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select at least 1 DrawViewPart object as Base.")); - return; - } - TechDraw::DrawViewPart* dvp = static_cast(*(baseObj.begin())); - - std::string PageName = page->getNameInDocument(); - - Gui::WaitCursor wc; - openCommand("Create view"); - - std::string FeatName = getUniqueObjectName("Detail"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewDetail','%s')",FeatName.c_str()); - App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); - TechDraw::DrawViewDetail* dvd = dynamic_cast(docObj); - if (!dvd) { - throw Base::TypeError("CmdTechDrawDetailView DVD not found\n"); - } - dvd->Source.setValues(dvp->Source.getValues()); - - doCommand(Doc,"App.activeDocument().%s.BaseView = App.activeDocument().%s",FeatName.c_str(),dvp->getNameInDocument()); - doCommand(Doc,"App.activeDocument().%s.Direction = App.activeDocument().%s.Direction",FeatName.c_str(),dvp->getNameInDocument()); - doCommand(Doc,"App.activeDocument().%s.XDirection = App.activeDocument().%s.XDirection",FeatName.c_str(),dvp->getNameInDocument()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); - - updateActive(); //ok here, no preceding recompute - commitCommand(); -} - -bool CmdTechDrawDetailView::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool haveView = DrawGuiUtil::needView(this); - bool taskInProgress = false; - if (havePage) { - taskInProgress = Gui::Control().activeDialog(); - } - return (havePage && haveView && !taskInProgress); -} - -//=========================================================================== -// TechDraw_ProjectionGroup -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawProjectionGroup) - -CmdTechDrawProjectionGroup::CmdTechDrawProjectionGroup() - : Command("TechDraw_ProjectionGroup") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Projection Group"); - sToolTipText = QT_TR_NOOP("Insert multiple linked views of drawable object(s)"); - sWhatsThis = "TechDraw_ProjectionGroup"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ProjectionGroup"; -} - -void CmdTechDrawProjectionGroup::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); -// auto inlist = page->getInListEx(true); -// inlist.insert(page); - - //set projection direction from selected Face - //use first object with a face selected - std::vector shapes; - App::DocumentObject* partObj = nullptr; - std::string faceName; - int resolve = 1; //mystery - bool single = false; //mystery - auto selection = getSelection().getSelectionEx(0, - App::DocumentObject::getClassTypeId(), - resolve, - single); - for (auto& sel: selection) { -// for(auto &sel : getSelection().getSelectionEx(0,App::DocumentObject::getClassTypeId(),false)) { - auto obj = sel.getObject(); - if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId()) ) { - continue; - } -// if(!obj || inlist.count(obj)) //?????? -// continue; - if (obj != nullptr) { //can this happen? - shapes.push_back(obj); - } - if(partObj != nullptr) { - continue; - } - for(auto& sub : sel.getSubNames()) { - if (TechDraw::DrawUtil::getGeomTypeFromName(sub) == "Face") { - faceName = sub; - partObj = obj; - break; - } - } - } - if (shapes.empty()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("No Shapes or Groups in this selection")); - return; - } - - Base::Vector3d projDir; - Gui::WaitCursor wc; - - openCommand("Create Projection Group"); - - std::string multiViewName = getUniqueObjectName("ProjGroup"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawProjGroup','%s')", - multiViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", - PageName.c_str(),multiViewName.c_str()); - - App::DocumentObject *docObj = getDocument()->getObject(multiViewName.c_str()); - auto multiView( static_cast(docObj) ); - multiView->Source.setValues(shapes); - doCommand(Doc,"App.activeDocument().%s.addProjection('Front')",multiViewName.c_str()); - - if (faceName.size()) { - std::pair dirs = DrawGuiUtil::getProjDirFromFace(partObj,faceName); - getDocument()->setStatus(App::Document::Status::SkipRecompute, true); - doCommand(Doc,"App.activeDocument().%s.Anchor.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.first.x,dirs.first.y,dirs.first.z); - doCommand(Doc,"App.activeDocument().%s.Anchor.RotationVector = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - doCommand(Doc,"App.activeDocument().%s.Anchor.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - getDocument()->setStatus(App::Document::Status::SkipRecompute, false); - } else { - std::pair dirs = DrawGuiUtil::get3DDirAndRot(); - getDocument()->setStatus(App::Document::Status::SkipRecompute, true); - doCommand(Doc,"App.activeDocument().%s.Anchor.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.first.x,dirs.first.y,dirs.first.z); - doCommand(Doc,"App.activeDocument().%s.Anchor.RotationVector = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - doCommand(Doc,"App.activeDocument().%s.Anchor.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - getDocument()->setStatus(App::Document::Status::SkipRecompute, false); - } - - doCommand(Doc,"App.activeDocument().%s.Anchor.recompute()", multiViewName.c_str()); - commitCommand(); - updateActive(); - - // create the rest of the desired views - Gui::Control().showDialog(new TaskDlgProjGroup(multiView,true)); -} - -bool CmdTechDrawProjectionGroup::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool taskInProgress = false; - if (havePage) { - taskInProgress = Gui::Control().activeDialog(); - } - return (havePage && !taskInProgress); -} - -//=========================================================================== -// TechDraw_NewMulti **deprecated** -//=========================================================================== - -//DEF_STD_CMD_A(CmdTechDrawNewMulti); - -//CmdTechDrawNewMulti::CmdTechDrawNewMulti() -// : Command("TechDraw_NewMulti") -//{ -// sAppModule = "TechDraw"; -// sGroup = QT_TR_NOOP("TechDraw"); -// sMenuText = QT_TR_NOOP("Insert multi-part view in drawing"); -// sToolTipText = QT_TR_NOOP("Insert a new View of a multiple Parts in the active drawing"); -// sWhatsThis = "TechDraw_NewMulti"; -// sStatusTip = sToolTipText; -// sPixmap = "actions/techdraw-multiview"; -//} - -//void CmdTechDrawNewMulti::activated(int iMsg) -//{ -// Q_UNUSED(iMsg); -// TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); -// if (!page) { -// return; -// } - -// std::vector shapes = getSelection().getObjectsOfType(App::DocumentObject::getClassTypeId()); -// if (shapes.empty()) { -// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), -// QObject::tr("Can not MultiView from this selection.")); -// return; -// } - -// std::string PageName = page->getNameInDocument(); - -// Gui::WaitCursor wc; - -// openCommand("Create view"); -// std::string FeatName = getUniqueObjectName("MultiView"); -// doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewMulti','%s')",FeatName.c_str()); -// App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); -// auto multiView( static_cast(docObj) ); -// multiView->Sources.setValues(shapes); -// doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); -// updateActive(); -// commitCommand(); -//} - -//bool CmdTechDrawNewMulti::isActive(void) -//{ -// return DrawGuiUtil::needPage(this); -//} - -//=========================================================================== -// TechDraw_Balloon -//=========================================================================== - -//! common checks of Selection for Dimension commands -//non-empty selection, no more than maxObjs selected and at least 1 DrawingPage exists -bool _checkSelectionBalloon(Gui::Command* cmd, unsigned maxObjs) { - std::vector selection = cmd->getSelection().getSelectionEx(); - if (selection.size() == 0) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), - QObject::tr("Select an object first")); - return false; - } - - const std::vector SubNames = selection[0].getSubNames(); - if (SubNames.size() > maxObjs){ - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), - QObject::tr("Too many objects selected")); - return false; - } - - std::vector pages = cmd->getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId()); - if (pages.empty()){ - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), - QObject::tr("Create a page first.")); - return false; - } - return true; -} - -bool _checkDrawViewPartBalloon(Gui::Command* cmd) { - std::vector selection = cmd->getSelection().getSelectionEx(); - auto objFeat( dynamic_cast(selection[0].getObject()) ); - if( !objFeat ) { - QMessageBox::warning( Gui::getMainWindow(), - QObject::tr("Incorrect selection"), - QObject::tr("No View of a Part in selection.") ); - return false; - } - return true; -} - -DEF_STD_CMD_A(CmdTechDrawBalloon) - -CmdTechDrawBalloon::CmdTechDrawBalloon() - : Command("TechDraw_Balloon") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Balloon Annotation"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_Balloon"; - sStatusTip = sToolTipText; - sPixmap = "TechDraw_Balloon"; -} - -void CmdTechDrawBalloon::activated(int iMsg) -{ - Q_UNUSED(iMsg); - bool result = _checkSelectionBalloon(this,1); - if (!result) - return; - result = _checkDrawViewPartBalloon(this); - if (!result) - return; - - std::vector selection = getSelection().getSelectionEx(); - auto objFeat( dynamic_cast(selection[0].getObject()) ); - if( objFeat == nullptr ) { - return; - } - - TechDraw::DrawPage* page = objFeat->findParentPage(); - std::string PageName = page->getNameInDocument(); - - page->balloonParent = objFeat; - page->balloonPlacing = true; - -} - -bool CmdTechDrawBalloon::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool haveView = DrawGuiUtil::needView(this); - return (havePage && haveView); -} - -//=========================================================================== -// TechDraw_ClipGroup -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawClipGroup) - -CmdTechDrawClipGroup::CmdTechDrawClipGroup() - : Command("TechDraw_ClipGroup") -{ - // setting the - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Clip Group"); - sToolTipText = sToolTipText; - sWhatsThis = "TechDraw_ClipGroup"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ClipGroup"; -} - -void CmdTechDrawClipGroup::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - std::string FeatName = getUniqueObjectName("Clip"); - openCommand("Create Clip"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewClip','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); - updateActive(); - commitCommand(); -} - -bool CmdTechDrawClipGroup::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_ClipGroupAdd -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawClipGroupAdd) - -CmdTechDrawClipGroupAdd::CmdTechDrawClipGroupAdd() - : Command("TechDraw_ClipGroupAdd") -{ - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Add View to Clip Group"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_ClipGroupAdd"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ClipGroupAdd"; -} - -void CmdTechDrawClipGroupAdd::activated(int iMsg) -{ - Q_UNUSED(iMsg); - std::vector selection = getSelection().getSelectionEx(); - if (selection.size() != 2) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select one Clip group and one View.")); - return; - } - - TechDraw::DrawViewClip* clip = 0; - TechDraw::DrawView* view = 0; - std::vector::iterator itSel = selection.begin(); - for (; itSel != selection.end(); itSel++) { - if ((*itSel).getObject()->isDerivedFrom(TechDraw::DrawViewClip::getClassTypeId())) { - clip = static_cast((*itSel).getObject()); - } else if ((*itSel).getObject()->isDerivedFrom(TechDraw::DrawView::getClassTypeId())) { - view = static_cast((*itSel).getObject()); - } - } - if (!view) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select exactly one View to add to group.")); - return; - } - if (!clip) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select exactly one Clip group.")); - return; - } - - TechDraw::DrawPage* pageClip = clip->findParentPage(); - TechDraw::DrawPage* pageView = view->findParentPage(); - - if (pageClip != pageView) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Clip and View must be from same Page.")); - return; - } - - std::string PageName = pageClip->getNameInDocument(); - std::string ClipName = clip->getNameInDocument(); - std::string ViewName = view->getNameInDocument(); - - openCommand("ClipGroupAdd"); - doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = False",ViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",ClipName.c_str(),ViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = True",ViewName.c_str()); - updateActive(); - commitCommand(); -} - -bool CmdTechDrawClipGroupAdd::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool haveClip = false; - if (havePage) { - auto drawClipType( TechDraw::DrawViewClip::getClassTypeId() ); - auto selClips = getDocument()->getObjectsOfType(drawClipType); - if (!selClips.empty()) { - haveClip = true; - } - } - return (havePage && haveClip); -} - -//=========================================================================== -// TechDraw_ClipGroupRemove -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawClipGroupRemove) - -CmdTechDrawClipGroupRemove::CmdTechDrawClipGroupRemove() - : Command("TechDraw_ClipGroupRemove") -{ - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Remove View from Clip Group"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_ClipGroupRemove"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ClipGroupRemove"; -} - -void CmdTechDrawClipGroupRemove::activated(int iMsg) -{ - Q_UNUSED(iMsg); - auto dObj( getSelection().getObjectsOfType(TechDraw::DrawView::getClassTypeId()) ); - if (dObj.empty()) { - QMessageBox::warning( Gui::getMainWindow(), - QObject::tr("Wrong selection"), - QObject::tr("Select exactly one View to remove from Group.") ); - return; - } - - auto view( static_cast(dObj.front()) ); - - TechDraw::DrawPage* page = view->findParentPage(); - const std::vector pViews = page->Views.getValues(); - TechDraw::DrawViewClip *clip(nullptr); - for (auto &v : pViews) { - clip = dynamic_cast(v); - if (clip && clip->isViewInClip(view)) { - break; - } - clip = nullptr; - } - - if (!clip) { - QMessageBox::warning( Gui::getMainWindow(), - QObject::tr("Wrong selection"), - QObject::tr("View does not belong to a Clip") ); - return; - } - - std::string ClipName = clip->getNameInDocument(); - std::string ViewName = view->getNameInDocument(); - - openCommand("ClipGroupRemove"); - doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = False",ViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.removeView(App.activeDocument().%s)",ClipName.c_str(),ViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = True",ViewName.c_str()); - updateActive(); - commitCommand(); -} - -bool CmdTechDrawClipGroupRemove::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool haveClip = false; - if (havePage) { - auto drawClipType( TechDraw::DrawViewClip::getClassTypeId() ); - auto selClips = getDocument()->getObjectsOfType(drawClipType); - if (!selClips.empty()) { - haveClip = true; - } - } - return (havePage && haveClip); -} - - -//=========================================================================== -// TechDraw_Symbol -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawSymbol) - -CmdTechDrawSymbol::CmdTechDrawSymbol() - : Command("TechDraw_Symbol") -{ - // setting the Gui eye-candy - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert SVG Symbol"); - sToolTipText = QT_TR_NOOP("Insert symbol from a SVG file"); - sWhatsThis = "TechDraw_Symbol"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-symbol"; -} - -void CmdTechDrawSymbol::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - // Reading an image - QString filename = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), QObject::tr("Choose an SVG file to open"), QString::null, - QString::fromLatin1("%1 (*.svg *.svgz)").arg(QObject::tr("Scalable Vector Graphic"))); - if (!filename.isEmpty()) - { - std::string FeatName = getUniqueObjectName("Symbol"); - filename = Base::Tools::escapeEncodeFilename(filename); - openCommand("Create Symbol"); -#if PY_MAJOR_VERSION < 3 - doCommand(Doc,"f = open(unicode(\"%s\",'utf-8'),'r')",(const char*)filename.toUtf8()); -#else - doCommand(Doc,"f = open(\"%s\",'r')",(const char*)filename.toUtf8()); -#endif - doCommand(Doc,"svg = f.read()"); - doCommand(Doc,"f.close()"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSymbol','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Symbol = svg",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); - updateActive(); - commitCommand(); - } -} - -bool CmdTechDrawSymbol::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_DraftView -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawDraftView) - -CmdTechDrawDraftView::CmdTechDrawDraftView() - : Command("TechDraw_DraftView") -{ - // setting the Gui eye-candy - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Draft Workbench Object"); - sToolTipText = QT_TR_NOOP("Insert a View of a Draft Workbench object"); - sWhatsThis = "TechDraw_NewDraft"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-DraftView"; -} - -void CmdTechDrawDraftView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - std::vector objects = getSelection(). - getObjectsOfType(App::DocumentObject::getClassTypeId()); - - if (objects.empty()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select at least one object.")); - return; - } - - int draftItemsFound = 0; - for (std::vector::iterator it = objects.begin(); it != objects.end(); ++it) { - if (DrawGuiUtil::isDraftObject((*it))) { - draftItemsFound++; - std::string FeatName = getUniqueObjectName("DraftView"); - std::string SourceName = (*it)->getNameInDocument(); - openCommand("Create DraftView"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewDraft','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s", - FeatName.c_str(),SourceName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", - PageName.c_str(),FeatName.c_str()); - updateActive(); - commitCommand(); - } - } - if (draftItemsFound == 0) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("There were no DraftWB objects in the selection.")); - } -} - -bool CmdTechDrawDraftView::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_ArchView -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawArchView) - -CmdTechDrawArchView::CmdTechDrawArchView() - : Command("TechDraw_ArchView") -{ - // setting the Gui eye-candy - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Arch Workbench Object"); - sToolTipText = QT_TR_NOOP("Insert a View of a Section Plane from Arch Workbench"); - sWhatsThis = "TechDraw_NewArch"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ArchView"; -} - -void CmdTechDrawArchView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - - const std::vector objects = getSelection(). - getObjectsOfType(App::DocumentObject::getClassTypeId()); - App::DocumentObject* archObject = nullptr; - int archCount = 0; - for (auto& obj : objects) { - if (DrawGuiUtil::isArchSection(obj) ) { - archCount++; - archObject = obj; - } - } - if ( archCount > 1 ) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Please select only 1 Arch Section.")); - return; - } - - if (archObject == nullptr) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("No Arch Sections in selection.")); - return; - } - - std::string FeatName = getUniqueObjectName("ArchView"); - std::string SourceName = archObject->getNameInDocument(); - openCommand("Create ArchView"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewArch','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s",FeatName.c_str(),SourceName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); - updateActive(); - commitCommand(); -} - -bool CmdTechDrawArchView::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_SpreadsheetView -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawSpreadsheetView) - -CmdTechDrawSpreadsheetView::CmdTechDrawSpreadsheetView() - : Command("TechDraw_SpreadsheetView") -{ - // setting the - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Spreadsheet View"); - sToolTipText = QT_TR_NOOP("Insert View to a spreadsheet"); - sWhatsThis = "TechDraw_SpreadsheetView"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-SpreadsheetView"; -} - -void CmdTechDrawSpreadsheetView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - const std::vector spreads = getSelection().getObjectsOfType(Spreadsheet::Sheet::getClassTypeId()); - if (spreads.size() != 1) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select exactly one Spreadsheet object.")); - return; - } - std::string SpreadName = spreads.front()->getNameInDocument(); - - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - openCommand("Create spreadsheet view"); - std::string FeatName = getUniqueObjectName("Sheet"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSpreadsheet','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s",FeatName.c_str(),SpreadName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); - updateActive(); - commitCommand(); -} - -bool CmdTechDrawSpreadsheetView::isActive(void) -{ - //need a Page and a SpreadSheet::Sheet - bool havePage = DrawGuiUtil::needPage(this); - bool haveSheet = false; - if (havePage) { - auto spreadSheetType( Spreadsheet::Sheet::getClassTypeId() ); - auto selSheets = getDocument()->getObjectsOfType(spreadSheetType); - if (!selSheets.empty()) { - haveSheet = true; - } - } - return (havePage && haveSheet); -} - - -//=========================================================================== -// TechDraw_ExportPageSVG -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawExportPageSVG) - -CmdTechDrawExportPageSVG::CmdTechDrawExportPageSVG() - : Command("TechDraw_ExportPageSVG") -{ - sGroup = QT_TR_NOOP("File"); - sMenuText = QT_TR_NOOP("Export Page as SVG"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_ExportPageSVG"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ExportPageSVG"; -} - -void CmdTechDrawExportPageSVG::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - Gui::Document* activeGui = Gui::Application::Instance->getDocument(page->getDocument()); - Gui::ViewProvider* vp = activeGui->getViewProvider(page); - ViewProviderPage* dvp = dynamic_cast(vp); - - if (dvp && dvp->getMDIViewPage()) { - dvp->getMDIViewPage()->saveSVG(); - } else { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No Drawing View"), - QObject::tr("Open Drawing View before attempting export to SVG.")); - return; - } -} - -bool CmdTechDrawExportPageSVG::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_ExportPageDXF -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawExportPageDXF) - -CmdTechDrawExportPageDXF::CmdTechDrawExportPageDXF() - : Command("TechDraw_ExportPageDXF") -{ - sGroup = QT_TR_NOOP("File"); - sMenuText = QT_TR_NOOP("Export Page as DXF"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_ExportPageDXF"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ExportPageDXF"; -} - -void CmdTechDrawExportPageDXF::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - - std::vector views = page->Views.getValues(); - for (auto& v: views) { - if (v->isDerivedFrom(TechDraw::DrawViewArch::getClassTypeId())) { - QMessageBox::StandardButton rc = - QMessageBox::question(Gui::getMainWindow(), QObject::tr("Can not export selection"), - QObject::tr("Page contains DrawViewArch which will not be exported. Continue?"), - QMessageBox::StandardButtons(QMessageBox::Yes| QMessageBox::No)); - if (rc == QMessageBox::No) { - return; - } else { - break; - } - } - } - -//WF? allow more than one TD Page per Dxf file?? 1 TD page = 1 DXF file = 1 drawing? - QString defaultDir; - QString fileName = Gui::FileDialog::getSaveFileName(Gui::getMainWindow(), - QString::fromUtf8(QT_TR_NOOP("Save Dxf File ")), - defaultDir, - QString::fromUtf8(QT_TR_NOOP("Dxf (*.dxf)"))); - - if (fileName.isEmpty()) { - return; - } - - std::string PageName = page->getNameInDocument(); - openCommand("Save page to dxf"); - doCommand(Doc,"import TechDraw"); - fileName = Base::Tools::escapeEncodeFilename(fileName); - doCommand(Doc,"TechDraw.writeDXFPage(App.activeDocument().%s,u\"%s\")",PageName.c_str(),(const char*)fileName.toUtf8()); - commitCommand(); -} - - -bool CmdTechDrawExportPageDXF::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -void CreateTechDrawCommands(void) -{ - Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); - - rcCmdMgr.addCommand(new CmdTechDrawPageDefault()); - rcCmdMgr.addCommand(new CmdTechDrawPageTemplate()); - rcCmdMgr.addCommand(new CmdTechDrawRedrawPage()); - rcCmdMgr.addCommand(new CmdTechDrawView()); - rcCmdMgr.addCommand(new CmdTechDrawActiveView()); - rcCmdMgr.addCommand(new CmdTechDrawSectionView()); - rcCmdMgr.addCommand(new CmdTechDrawDetailView()); - rcCmdMgr.addCommand(new CmdTechDrawProjectionGroup()); - rcCmdMgr.addCommand(new CmdTechDrawClipGroup()); - rcCmdMgr.addCommand(new CmdTechDrawClipGroupAdd()); - rcCmdMgr.addCommand(new CmdTechDrawClipGroupRemove()); - rcCmdMgr.addCommand(new CmdTechDrawSymbol()); - rcCmdMgr.addCommand(new CmdTechDrawExportPageSVG()); - rcCmdMgr.addCommand(new CmdTechDrawExportPageDXF()); - rcCmdMgr.addCommand(new CmdTechDrawDraftView()); - rcCmdMgr.addCommand(new CmdTechDrawArchView()); - rcCmdMgr.addCommand(new CmdTechDrawSpreadsheetView()); - rcCmdMgr.addCommand(new CmdTechDrawBalloon()); -} +/*************************************************************************** + * * + * This program 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. * + * for detail see the LICENCE text file. * + * Jürgen Riegel 2002 * + * Copyright (c) 2014 Luke Parry * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +#endif + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DrawGuiUtil.h" +#include "MDIViewPage.h" +#include "TaskProjGroup.h" +#include "TaskSectionView.h" +#include "TaskActiveView.h" +#include "TaskDetail.h" +#include "ViewProviderPage.h" + +using namespace TechDrawGui; +using namespace std; + + +//=========================================================================== +// TechDraw_PageDefault +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawPageDefault) + +CmdTechDrawPageDefault::CmdTechDrawPageDefault() + : Command("TechDraw_PageDefault") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Default Page"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_PageDefault"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-PageDefault"; +} + +void CmdTechDrawPageDefault::activated(int iMsg) +{ + Q_UNUSED(iMsg); + Base::Reference hGrp = App::GetApplication().GetUserParameter() + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Files"); + + std::string defaultDir = App::Application::getResourceDir() + "Mod/TechDraw/Templates/"; + std::string defaultFileName = defaultDir + "A4_LandscapeTD.svg"; + QString templateFileName = QString::fromStdString(hGrp->GetASCII("TemplateFile",defaultFileName.c_str())); + if (templateFileName.isEmpty()) { + templateFileName = QString::fromStdString(defaultFileName); + } + + std::string PageName = getUniqueObjectName("Page"); + std::string TemplateName = getUniqueObjectName("Template"); + + QFileInfo tfi(templateFileName); + if (tfi.isReadable()) { + Gui::WaitCursor wc; + openCommand("Drawing create page"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawPage','%s')",PageName.c_str()); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawSVGTemplate','%s')",TemplateName.c_str()); + + doCommand(Doc,"App.activeDocument().%s.Template = '%s'",TemplateName.c_str(), templateFileName.toStdString().c_str()); + doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str()); + + commitCommand(); + TechDraw::DrawPage* fp = dynamic_cast(getDocument()->getObject(PageName.c_str())); + if (!fp) { + throw Base::TypeError("CmdTechDrawPageDefault fp not found\n"); + } + + Gui::ViewProvider* vp = Gui::Application::Instance->getDocument(getDocument())->getViewProvider(fp); + TechDrawGui::ViewProviderPage* dvp = dynamic_cast(vp); + if (dvp) { + dvp->show(); + } + else { + Base::Console().Log("INFO - Template: %s for Page: %s NOT Found\n", PageName.c_str(),TemplateName.c_str()); + } + } else { + QMessageBox::critical(Gui::getMainWindow(), + QLatin1String("No template"), + QLatin1String("No default template found")); + } +} + +bool CmdTechDrawPageDefault::isActive(void) +{ + return hasActiveDocument(); +} + +//=========================================================================== +// TechDraw_PageTemplate +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawPageTemplate) + +CmdTechDrawPageTemplate::CmdTechDrawPageTemplate() + : Command("TechDraw_PageTemplate") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Page using Template"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_PageTemplate"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-PageTemplate"; +} + +void CmdTechDrawPageTemplate::activated(int iMsg) +{ + Q_UNUSED(iMsg); + Base::Reference hGrp = App::GetApplication().GetUserParameter() + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Files"); + + std::string defaultDir = App::Application::getResourceDir() + "Mod/TechDraw/Templates"; + QString templateDir = QString::fromStdString(hGrp->GetASCII("TemplateDir", defaultDir.c_str())); + QString templateFileName = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), + QString::fromUtf8(QT_TR_NOOP("Select a Template File")), + templateDir, + QString::fromUtf8(QT_TR_NOOP("Template (*.svg *.dxf)"))); + + if (templateFileName.isEmpty()) { + return; + } + + std::string PageName = getUniqueObjectName("Page"); + std::string TemplateName = getUniqueObjectName("Template"); + + QFileInfo tfi(templateFileName); + if (tfi.isReadable()) { + Gui::WaitCursor wc; + openCommand("Drawing create page"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawPage','%s')",PageName.c_str()); + + // Create the Template Object to attach to the page + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawSVGTemplate','%s')",TemplateName.c_str()); + + //why is "Template" property set twice? -wf + // once to set DrawSVGTemplate.Template to OS template file name + templateFileName = Base::Tools::escapeEncodeFilename(templateFileName); + doCommand(Doc,"App.activeDocument().%s.Template = \"%s\"",TemplateName.c_str(), templateFileName.toUtf8().constData()); + // once to set Page.Template to DrawSVGTemplate.Name + doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str()); + // consider renaming DrawSVGTemplate.Template property? + + commitCommand(); + TechDraw::DrawPage* fp = dynamic_cast(getDocument()->getObject(PageName.c_str())); + if (!fp) { + throw Base::TypeError("CmdTechDrawNewPagePick fp not found\n"); + } + Gui::ViewProvider* vp = Gui::Application::Instance->getDocument(getDocument())->getViewProvider(fp); + TechDrawGui::ViewProviderPage* dvp = dynamic_cast(vp); + if (dvp) { + dvp->show(); + } + else { + Base::Console().Log("INFO - Template: %s for Page: %s NOT Found\n", PageName.c_str(),TemplateName.c_str()); + } + } + else { + QMessageBox::critical(Gui::getMainWindow(), + QLatin1String("No template"), + QLatin1String("Template file is invalid")); + } +} + +bool CmdTechDrawPageTemplate::isActive(void) +{ + return hasActiveDocument(); +} + +//=========================================================================== +// TechDraw_RedrawPage +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawRedrawPage) + +CmdTechDrawRedrawPage::CmdTechDrawRedrawPage() + : Command("TechDraw_RedrawPage") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Redraw Page"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_RedrawPage"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-RedrawPage"; +} + +void CmdTechDrawRedrawPage::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + Gui::WaitCursor wc; + + page->redrawCommand(); +} + +bool CmdTechDrawRedrawPage::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveView = DrawGuiUtil::needView(this,false); + return (havePage && haveView); +} + +//=========================================================================== +// TechDraw_View +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawView) + +CmdTechDrawView::CmdTechDrawView() + : Command("TechDraw_View") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert View"); + sToolTipText = QT_TR_NOOP("Insert a View"); + sWhatsThis = "TechDraw_View"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-View"; +} + +void CmdTechDrawView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + //set projection direction from selected Face + //use first object with a face selected + std::vector shapes; + App::DocumentObject* partObj = nullptr; + std::string faceName; + int resolve = 1; //mystery + bool single = false; //mystery + auto selection = getSelection().getSelectionEx(0, + App::DocumentObject::getClassTypeId(), + resolve, + single); + for (auto& sel: selection) { + auto obj = sel.getObject(); + if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId()) ) { + continue; + } + if (obj != nullptr) { + shapes.push_back(obj); + } + if(partObj != nullptr) { + continue; + } + for(auto& sub : sel.getSubNames()) { + if (TechDraw::DrawUtil::getGeomTypeFromName(sub) == "Face") { + faceName = sub; + partObj = obj; + break; + } + } + } + + if ((shapes.empty())) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("No Shapes, Groups or Links in this selection")); + return; + } + + Base::Vector3d projDir; + + Gui::WaitCursor wc; + openCommand("Create view"); + std::string FeatName = getUniqueObjectName("View"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewPart','%s')",FeatName.c_str()); + App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); + TechDraw::DrawViewPart* dvp = dynamic_cast(docObj); + if (!dvp) { + throw Base::TypeError("CmdTechDrawView DVP not found\n"); + } + dvp->Source.setValues(shapes); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + if (faceName.size()) { + std::pair dirs = DrawGuiUtil::getProjDirFromFace(partObj,faceName); + projDir = dirs.first; + getDocument()->setStatus(App::Document::Status::SkipRecompute, true); + doCommand(Doc,"App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), projDir.x,projDir.y,projDir.z); + //do something clever with dirs.second; +// dvp->setXDir(dirs.second); + doCommand(Doc,"App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); + doCommand(Doc,"App.activeDocument().%s.recompute()", FeatName.c_str()); + getDocument()->setStatus(App::Document::Status::SkipRecompute, false); + } else { + std::pair dirs = DrawGuiUtil::get3DDirAndRot(); + projDir = dirs.first; + getDocument()->setStatus(App::Document::Status::SkipRecompute, true); + doCommand(Doc,"App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), projDir.x,projDir.y,projDir.z); + doCommand(Doc,"App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); +// dvp->setXDir(dirs.second); + getDocument()->setStatus(App::Document::Status::SkipRecompute, false); + doCommand(Doc,"App.activeDocument().%s.recompute()", FeatName.c_str()); + } + commitCommand(); +} + +bool CmdTechDrawView::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_ActiveView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawActiveView) + +CmdTechDrawActiveView::CmdTechDrawActiveView() + : Command("TechDraw_ActiveView") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Active View (3D View)"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_ActiveView"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ActiveView"; +} + +void CmdTechDrawActiveView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + Gui::Control().showDialog(new TaskDlgActiveView(page)); +} + +bool CmdTechDrawActiveView::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_SectionView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawSectionView) + +CmdTechDrawSectionView::CmdTechDrawSectionView() + : Command("TechDraw_SectionView") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Section View"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_SectionView"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-SectionView"; +} + +void CmdTechDrawSectionView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + + std::vector baseObj = getSelection().getObjectsOfType(TechDraw::DrawViewPart::getClassTypeId()); + if (baseObj.empty()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select at least 1 DrawViewPart object as Base.")); + return; + } + TechDraw::DrawViewPart* dvp = static_cast(*baseObj.begin()); +// std::string BaseName = dvp->getNameInDocument(); +// std::string PageName = page->getNameInDocument(); +// double baseScale = dvp->getScale(); + +// Gui::WaitCursor wc; +// openCommand("Create view"); +// std::string FeatName = getUniqueObjectName("Section"); + +// doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSection','%s')",FeatName.c_str()); + +// App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); +// TechDraw::DrawViewSection* dsv = dynamic_cast(docObj); +// if (!dsv) { +// throw Base::TypeError("CmdTechDrawSectionView DVS not found\n"); +// } +// dsv->Source.setValues(dvp->Source.getValues()); +// doCommand(Doc,"App.activeDocument().%s.BaseView = App.activeDocument().%s",FeatName.c_str(),BaseName.c_str()); +// doCommand(Doc,"App.activeDocument().%s.ScaleType = App.activeDocument().%s.ScaleType",FeatName.c_str(),BaseName.c_str()); +// doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); +// doCommand(Doc,"App.activeDocument().%s.Scale = %0.6f",FeatName.c_str(),baseScale); + Gui::Control().showDialog(new TaskDlgSectionView(dvp)); + + updateActive(); //ok here since dialog doesn't call doc.recompute() + commitCommand(); +} + +bool CmdTechDrawSectionView::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveView = DrawGuiUtil::needView(this); + bool taskInProgress = false; + if (havePage) { + taskInProgress = Gui::Control().activeDialog(); + } + return (havePage && haveView && !taskInProgress); +} + +//=========================================================================== +// TechDraw_DetailView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawDetailView) + +CmdTechDrawDetailView::CmdTechDrawDetailView() + : Command("TechDraw_DetailView") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Detail View"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_DetailView"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-DetailView"; +} + +void CmdTechDrawDetailView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + + std::vector baseObj = getSelection(). + getObjectsOfType(TechDraw::DrawViewPart::getClassTypeId()); + if (baseObj.empty()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select at least 1 DrawViewPart object as Base.")); + return; + } + TechDraw::DrawViewPart* dvp = static_cast(*(baseObj.begin())); + + Gui::Control().showDialog(new TaskDlgDetail(dvp)); +} + +bool CmdTechDrawDetailView::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveView = DrawGuiUtil::needView(this); + bool taskInProgress = false; + if (havePage) { + taskInProgress = Gui::Control().activeDialog(); + } + return (havePage && haveView && !taskInProgress); +} + +//=========================================================================== +// TechDraw_ProjectionGroup +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawProjectionGroup) + +CmdTechDrawProjectionGroup::CmdTechDrawProjectionGroup() + : Command("TechDraw_ProjectionGroup") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Projection Group"); + sToolTipText = QT_TR_NOOP("Insert multiple linked views of drawable object(s)"); + sWhatsThis = "TechDraw_ProjectionGroup"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ProjectionGroup"; +} + +void CmdTechDrawProjectionGroup::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); +// auto inlist = page->getInListEx(true); +// inlist.insert(page); + + //set projection direction from selected Face + //use first object with a face selected + std::vector shapes; + App::DocumentObject* partObj = nullptr; + std::string faceName; + int resolve = 1; //mystery + bool single = false; //mystery + auto selection = getSelection().getSelectionEx(0, + App::DocumentObject::getClassTypeId(), + resolve, + single); + for (auto& sel: selection) { +// for(auto &sel : getSelection().getSelectionEx(0,App::DocumentObject::getClassTypeId(),false)) { + auto obj = sel.getObject(); + if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId()) ) { + continue; + } +// if(!obj || inlist.count(obj)) //?????? +// continue; + if (obj != nullptr) { //can this happen? + shapes.push_back(obj); + } + if(partObj != nullptr) { + continue; + } + for(auto& sub : sel.getSubNames()) { + if (TechDraw::DrawUtil::getGeomTypeFromName(sub) == "Face") { + faceName = sub; + partObj = obj; + break; + } + } + } + if (shapes.empty()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("No Shapes or Groups in this selection")); + return; + } + + Base::Vector3d projDir; + Gui::WaitCursor wc; + + openCommand("Create Projection Group"); + + std::string multiViewName = getUniqueObjectName("ProjGroup"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawProjGroup','%s')", + multiViewName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", + PageName.c_str(),multiViewName.c_str()); + + App::DocumentObject *docObj = getDocument()->getObject(multiViewName.c_str()); + auto multiView( static_cast(docObj) ); + multiView->Source.setValues(shapes); + doCommand(Doc,"App.activeDocument().%s.addProjection('Front')",multiViewName.c_str()); + + if (faceName.size()) { + std::pair dirs = DrawGuiUtil::getProjDirFromFace(partObj,faceName); + getDocument()->setStatus(App::Document::Status::SkipRecompute, true); + doCommand(Doc,"App.activeDocument().%s.Anchor.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.first.x,dirs.first.y,dirs.first.z); + doCommand(Doc,"App.activeDocument().%s.Anchor.RotationVector = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); + doCommand(Doc,"App.activeDocument().%s.Anchor.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); + getDocument()->setStatus(App::Document::Status::SkipRecompute, false); + } else { + std::pair dirs = DrawGuiUtil::get3DDirAndRot(); + getDocument()->setStatus(App::Document::Status::SkipRecompute, true); + doCommand(Doc,"App.activeDocument().%s.Anchor.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.first.x,dirs.first.y,dirs.first.z); + doCommand(Doc,"App.activeDocument().%s.Anchor.RotationVector = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); + doCommand(Doc,"App.activeDocument().%s.Anchor.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); + getDocument()->setStatus(App::Document::Status::SkipRecompute, false); + } + + doCommand(Doc,"App.activeDocument().%s.Anchor.recompute()", multiViewName.c_str()); + commitCommand(); + updateActive(); + + // create the rest of the desired views + Gui::Control().showDialog(new TaskDlgProjGroup(multiView,true)); +} + +bool CmdTechDrawProjectionGroup::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool taskInProgress = false; + if (havePage) { + taskInProgress = Gui::Control().activeDialog(); + } + return (havePage && !taskInProgress); +} + +//=========================================================================== +// TechDraw_NewMulti **deprecated** +//=========================================================================== + +//DEF_STD_CMD_A(CmdTechDrawNewMulti); + +//CmdTechDrawNewMulti::CmdTechDrawNewMulti() +// : Command("TechDraw_NewMulti") +//{ +// sAppModule = "TechDraw"; +// sGroup = QT_TR_NOOP("TechDraw"); +// sMenuText = QT_TR_NOOP("Insert multi-part view in drawing"); +// sToolTipText = QT_TR_NOOP("Insert a new View of a multiple Parts in the active drawing"); +// sWhatsThis = "TechDraw_NewMulti"; +// sStatusTip = sToolTipText; +// sPixmap = "actions/techdraw-multiview"; +//} + +//void CmdTechDrawNewMulti::activated(int iMsg) +//{ +// Q_UNUSED(iMsg); +// TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); +// if (!page) { +// return; +// } + +// std::vector shapes = getSelection().getObjectsOfType(App::DocumentObject::getClassTypeId()); +// if (shapes.empty()) { +// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), +// QObject::tr("Can not MultiView from this selection.")); +// return; +// } + +// std::string PageName = page->getNameInDocument(); + +// Gui::WaitCursor wc; + +// openCommand("Create view"); +// std::string FeatName = getUniqueObjectName("MultiView"); +// doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewMulti','%s')",FeatName.c_str()); +// App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); +// auto multiView( static_cast(docObj) ); +// multiView->Sources.setValues(shapes); +// doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); +// updateActive(); +// commitCommand(); +//} + +//bool CmdTechDrawNewMulti::isActive(void) +//{ +// return DrawGuiUtil::needPage(this); +//} + +//=========================================================================== +// TechDraw_Balloon +//=========================================================================== + +//! common checks of Selection for Dimension commands +//non-empty selection, no more than maxObjs selected and at least 1 DrawingPage exists +bool _checkSelectionBalloon(Gui::Command* cmd, unsigned maxObjs) { + std::vector selection = cmd->getSelection().getSelectionEx(); + if (selection.size() == 0) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), + QObject::tr("Select an object first")); + return false; + } + + const std::vector SubNames = selection[0].getSubNames(); + if (SubNames.size() > maxObjs){ + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), + QObject::tr("Too many objects selected")); + return false; + } + + std::vector pages = cmd->getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId()); + if (pages.empty()){ + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), + QObject::tr("Create a page first.")); + return false; + } + return true; +} + +bool _checkDrawViewPartBalloon(Gui::Command* cmd) { + std::vector selection = cmd->getSelection().getSelectionEx(); + auto objFeat( dynamic_cast(selection[0].getObject()) ); + if( !objFeat ) { + QMessageBox::warning( Gui::getMainWindow(), + QObject::tr("Incorrect selection"), + QObject::tr("No View of a Part in selection.") ); + return false; + } + return true; +} + +DEF_STD_CMD_A(CmdTechDrawBalloon) + +CmdTechDrawBalloon::CmdTechDrawBalloon() + : Command("TechDraw_Balloon") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Balloon Annotation"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_Balloon"; + sStatusTip = sToolTipText; + sPixmap = "TechDraw_Balloon"; +} + +void CmdTechDrawBalloon::activated(int iMsg) +{ + Q_UNUSED(iMsg); + bool result = _checkSelectionBalloon(this,1); + if (!result) + return; + result = _checkDrawViewPartBalloon(this); + if (!result) + return; + + std::vector selection = getSelection().getSelectionEx(); + auto objFeat( dynamic_cast(selection[0].getObject()) ); + if( objFeat == nullptr ) { + return; + } + + TechDraw::DrawPage* page = objFeat->findParentPage(); + std::string PageName = page->getNameInDocument(); + + page->balloonParent = objFeat; + page->balloonPlacing = true; + +} + +bool CmdTechDrawBalloon::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveView = DrawGuiUtil::needView(this); + return (havePage && haveView); +} + +//=========================================================================== +// TechDraw_ClipGroup +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawClipGroup) + +CmdTechDrawClipGroup::CmdTechDrawClipGroup() + : Command("TechDraw_ClipGroup") +{ + // setting the + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Clip Group"); + sToolTipText = sToolTipText; + sWhatsThis = "TechDraw_ClipGroup"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ClipGroup"; +} + +void CmdTechDrawClipGroup::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + std::string FeatName = getUniqueObjectName("Clip"); + openCommand("Create Clip"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewClip','%s')",FeatName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + updateActive(); + commitCommand(); +} + +bool CmdTechDrawClipGroup::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_ClipGroupAdd +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawClipGroupAdd) + +CmdTechDrawClipGroupAdd::CmdTechDrawClipGroupAdd() + : Command("TechDraw_ClipGroupAdd") +{ + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Add View to Clip Group"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_ClipGroupAdd"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ClipGroupAdd"; +} + +void CmdTechDrawClipGroupAdd::activated(int iMsg) +{ + Q_UNUSED(iMsg); + std::vector selection = getSelection().getSelectionEx(); + if (selection.size() != 2) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select one Clip group and one View.")); + return; + } + + TechDraw::DrawViewClip* clip = 0; + TechDraw::DrawView* view = 0; + std::vector::iterator itSel = selection.begin(); + for (; itSel != selection.end(); itSel++) { + if ((*itSel).getObject()->isDerivedFrom(TechDraw::DrawViewClip::getClassTypeId())) { + clip = static_cast((*itSel).getObject()); + } else if ((*itSel).getObject()->isDerivedFrom(TechDraw::DrawView::getClassTypeId())) { + view = static_cast((*itSel).getObject()); + } + } + if (!view) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select exactly one View to add to group.")); + return; + } + if (!clip) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select exactly one Clip group.")); + return; + } + + TechDraw::DrawPage* pageClip = clip->findParentPage(); + TechDraw::DrawPage* pageView = view->findParentPage(); + + if (pageClip != pageView) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Clip and View must be from same Page.")); + return; + } + + std::string PageName = pageClip->getNameInDocument(); + std::string ClipName = clip->getNameInDocument(); + std::string ViewName = view->getNameInDocument(); + + openCommand("ClipGroupAdd"); + doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = False",ViewName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",ClipName.c_str(),ViewName.c_str()); + doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = True",ViewName.c_str()); + updateActive(); + commitCommand(); +} + +bool CmdTechDrawClipGroupAdd::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveClip = false; + if (havePage) { + auto drawClipType( TechDraw::DrawViewClip::getClassTypeId() ); + auto selClips = getDocument()->getObjectsOfType(drawClipType); + if (!selClips.empty()) { + haveClip = true; + } + } + return (havePage && haveClip); +} + +//=========================================================================== +// TechDraw_ClipGroupRemove +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawClipGroupRemove) + +CmdTechDrawClipGroupRemove::CmdTechDrawClipGroupRemove() + : Command("TechDraw_ClipGroupRemove") +{ + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Remove View from Clip Group"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_ClipGroupRemove"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ClipGroupRemove"; +} + +void CmdTechDrawClipGroupRemove::activated(int iMsg) +{ + Q_UNUSED(iMsg); + auto dObj( getSelection().getObjectsOfType(TechDraw::DrawView::getClassTypeId()) ); + if (dObj.empty()) { + QMessageBox::warning( Gui::getMainWindow(), + QObject::tr("Wrong selection"), + QObject::tr("Select exactly one View to remove from Group.") ); + return; + } + + auto view( static_cast(dObj.front()) ); + + TechDraw::DrawPage* page = view->findParentPage(); + const std::vector pViews = page->Views.getValues(); + TechDraw::DrawViewClip *clip(nullptr); + for (auto &v : pViews) { + clip = dynamic_cast(v); + if (clip && clip->isViewInClip(view)) { + break; + } + clip = nullptr; + } + + if (!clip) { + QMessageBox::warning( Gui::getMainWindow(), + QObject::tr("Wrong selection"), + QObject::tr("View does not belong to a Clip") ); + return; + } + + std::string ClipName = clip->getNameInDocument(); + std::string ViewName = view->getNameInDocument(); + + openCommand("ClipGroupRemove"); + doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = False",ViewName.c_str()); + doCommand(Doc,"App.activeDocument().%s.removeView(App.activeDocument().%s)",ClipName.c_str(),ViewName.c_str()); + doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = True",ViewName.c_str()); + updateActive(); + commitCommand(); +} + +bool CmdTechDrawClipGroupRemove::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveClip = false; + if (havePage) { + auto drawClipType( TechDraw::DrawViewClip::getClassTypeId() ); + auto selClips = getDocument()->getObjectsOfType(drawClipType); + if (!selClips.empty()) { + haveClip = true; + } + } + return (havePage && haveClip); +} + + +//=========================================================================== +// TechDraw_Symbol +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawSymbol) + +CmdTechDrawSymbol::CmdTechDrawSymbol() + : Command("TechDraw_Symbol") +{ + // setting the Gui eye-candy + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert SVG Symbol"); + sToolTipText = QT_TR_NOOP("Insert symbol from a SVG file"); + sWhatsThis = "TechDraw_Symbol"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-symbol"; +} + +void CmdTechDrawSymbol::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + // Reading an image + QString filename = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), + QObject::tr("Choose an SVG file to open"), QString::null, + QString::fromLatin1("%1 (*.svg *.svgz);;%2 (*.*)"). + arg(QObject::tr("Scalable Vector Graphic")). + arg(QObject::tr("All Files"))); + + if (!filename.isEmpty()) + { + std::string FeatName = getUniqueObjectName("Symbol"); + filename = Base::Tools::escapeEncodeFilename(filename); + openCommand("Create Symbol"); +#if PY_MAJOR_VERSION < 3 + doCommand(Doc,"f = open(unicode(\"%s\",'utf-8'),'r')",(const char*)filename.toUtf8()); +#else + doCommand(Doc,"f = open(\"%s\",'r')",(const char*)filename.toUtf8()); +#endif + doCommand(Doc,"svg = f.read()"); + doCommand(Doc,"f.close()"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSymbol','%s')",FeatName.c_str()); + doCommand(Doc,"App.activeDocument().%s.Symbol = svg",FeatName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + updateActive(); + commitCommand(); + } +} + +bool CmdTechDrawSymbol::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_DraftView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawDraftView) + +CmdTechDrawDraftView::CmdTechDrawDraftView() + : Command("TechDraw_DraftView") +{ + // setting the Gui eye-candy + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Draft Workbench Object"); + sToolTipText = QT_TR_NOOP("Insert a View of a Draft Workbench object"); + sWhatsThis = "TechDraw_NewDraft"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-DraftView"; +} + +void CmdTechDrawDraftView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + std::vector objects = getSelection(). + getObjectsOfType(App::DocumentObject::getClassTypeId()); + + if (objects.empty()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select at least one object.")); + return; + } + + int draftItemsFound = 0; + for (std::vector::iterator it = objects.begin(); it != objects.end(); ++it) { + if (DrawGuiUtil::isDraftObject((*it))) { + draftItemsFound++; + std::string FeatName = getUniqueObjectName("DraftView"); + std::string SourceName = (*it)->getNameInDocument(); + openCommand("Create DraftView"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewDraft','%s')",FeatName.c_str()); + doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s", + FeatName.c_str(),SourceName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", + PageName.c_str(),FeatName.c_str()); + updateActive(); + commitCommand(); + } + } + if (draftItemsFound == 0) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("There were no DraftWB objects in the selection.")); + } +} + +bool CmdTechDrawDraftView::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_ArchView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawArchView) + +CmdTechDrawArchView::CmdTechDrawArchView() + : Command("TechDraw_ArchView") +{ + // setting the Gui eye-candy + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Arch Workbench Object"); + sToolTipText = QT_TR_NOOP("Insert a View of a Section Plane from Arch Workbench"); + sWhatsThis = "TechDraw_NewArch"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ArchView"; +} + +void CmdTechDrawArchView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + + const std::vector objects = getSelection(). + getObjectsOfType(App::DocumentObject::getClassTypeId()); + App::DocumentObject* archObject = nullptr; + int archCount = 0; + for (auto& obj : objects) { + if (DrawGuiUtil::isArchSection(obj) ) { + archCount++; + archObject = obj; + } + } + if ( archCount > 1 ) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Please select only 1 Arch Section.")); + return; + } + + if (archObject == nullptr) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("No Arch Sections in selection.")); + return; + } + + std::string FeatName = getUniqueObjectName("ArchView"); + std::string SourceName = archObject->getNameInDocument(); + openCommand("Create ArchView"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewArch','%s')",FeatName.c_str()); + doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s",FeatName.c_str(),SourceName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + updateActive(); + commitCommand(); +} + +bool CmdTechDrawArchView::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_SpreadsheetView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawSpreadsheetView) + +CmdTechDrawSpreadsheetView::CmdTechDrawSpreadsheetView() + : Command("TechDraw_SpreadsheetView") +{ + // setting the + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Spreadsheet View"); + sToolTipText = QT_TR_NOOP("Insert View to a spreadsheet"); + sWhatsThis = "TechDraw_SpreadsheetView"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-SpreadsheetView"; +} + +void CmdTechDrawSpreadsheetView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + const std::vector spreads = getSelection().getObjectsOfType(Spreadsheet::Sheet::getClassTypeId()); + if (spreads.size() != 1) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select exactly one Spreadsheet object.")); + return; + } + std::string SpreadName = spreads.front()->getNameInDocument(); + + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + openCommand("Create spreadsheet view"); + std::string FeatName = getUniqueObjectName("Sheet"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSpreadsheet','%s')",FeatName.c_str()); + doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s",FeatName.c_str(),SpreadName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + updateActive(); + commitCommand(); +} + +bool CmdTechDrawSpreadsheetView::isActive(void) +{ + //need a Page and a SpreadSheet::Sheet + bool havePage = DrawGuiUtil::needPage(this); + bool haveSheet = false; + if (havePage) { + auto spreadSheetType( Spreadsheet::Sheet::getClassTypeId() ); + auto selSheets = getDocument()->getObjectsOfType(spreadSheetType); + if (!selSheets.empty()) { + haveSheet = true; + } + } + return (havePage && haveSheet); +} + + +//=========================================================================== +// TechDraw_ExportPageSVG +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawExportPageSVG) + +CmdTechDrawExportPageSVG::CmdTechDrawExportPageSVG() + : Command("TechDraw_ExportPageSVG") +{ + sGroup = QT_TR_NOOP("File"); + sMenuText = QT_TR_NOOP("Export Page as SVG"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_ExportPageSVG"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ExportPageSVG"; +} + +void CmdTechDrawExportPageSVG::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + Gui::Document* activeGui = Gui::Application::Instance->getDocument(page->getDocument()); + Gui::ViewProvider* vp = activeGui->getViewProvider(page); + ViewProviderPage* dvp = dynamic_cast(vp); + + if (dvp && dvp->getMDIViewPage()) { + dvp->getMDIViewPage()->saveSVG(); + } else { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No Drawing View"), + QObject::tr("Open Drawing View before attempting export to SVG.")); + return; + } +} + +bool CmdTechDrawExportPageSVG::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_ExportPageDXF +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawExportPageDXF) + +CmdTechDrawExportPageDXF::CmdTechDrawExportPageDXF() + : Command("TechDraw_ExportPageDXF") +{ + sGroup = QT_TR_NOOP("File"); + sMenuText = QT_TR_NOOP("Export Page as DXF"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_ExportPageDXF"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ExportPageDXF"; +} + +void CmdTechDrawExportPageDXF::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + + std::vector views = page->Views.getValues(); + for (auto& v: views) { + if (v->isDerivedFrom(TechDraw::DrawViewArch::getClassTypeId())) { + QMessageBox::StandardButton rc = + QMessageBox::question(Gui::getMainWindow(), QObject::tr("Can not export selection"), + QObject::tr("Page contains DrawViewArch which will not be exported. Continue?"), + QMessageBox::StandardButtons(QMessageBox::Yes| QMessageBox::No)); + if (rc == QMessageBox::No) { + return; + } else { + break; + } + } + } + +//WF? allow more than one TD Page per Dxf file?? 1 TD page = 1 DXF file = 1 drawing? + QString defaultDir; + QString fileName = Gui::FileDialog::getSaveFileName(Gui::getMainWindow(), + QString::fromUtf8(QT_TR_NOOP("Save Dxf File ")), + defaultDir, + QString::fromUtf8(QT_TR_NOOP("Dxf (*.dxf)"))); + + if (fileName.isEmpty()) { + return; + } + + std::string PageName = page->getNameInDocument(); + openCommand("Save page to dxf"); + doCommand(Doc,"import TechDraw"); + fileName = Base::Tools::escapeEncodeFilename(fileName); + doCommand(Doc,"TechDraw.writeDXFPage(App.activeDocument().%s,u\"%s\")",PageName.c_str(),(const char*)fileName.toUtf8()); + commitCommand(); +} + + +bool CmdTechDrawExportPageDXF::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +void CreateTechDrawCommands(void) +{ + Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); + + rcCmdMgr.addCommand(new CmdTechDrawPageDefault()); + rcCmdMgr.addCommand(new CmdTechDrawPageTemplate()); + rcCmdMgr.addCommand(new CmdTechDrawRedrawPage()); + rcCmdMgr.addCommand(new CmdTechDrawView()); + rcCmdMgr.addCommand(new CmdTechDrawActiveView()); + rcCmdMgr.addCommand(new CmdTechDrawSectionView()); + rcCmdMgr.addCommand(new CmdTechDrawDetailView()); + rcCmdMgr.addCommand(new CmdTechDrawProjectionGroup()); + rcCmdMgr.addCommand(new CmdTechDrawClipGroup()); + rcCmdMgr.addCommand(new CmdTechDrawClipGroupAdd()); + rcCmdMgr.addCommand(new CmdTechDrawClipGroupRemove()); + rcCmdMgr.addCommand(new CmdTechDrawSymbol()); + rcCmdMgr.addCommand(new CmdTechDrawExportPageSVG()); + rcCmdMgr.addCommand(new CmdTechDrawExportPageDXF()); + rcCmdMgr.addCommand(new CmdTechDrawDraftView()); + rcCmdMgr.addCommand(new CmdTechDrawArchView()); + rcCmdMgr.addCommand(new CmdTechDrawSpreadsheetView()); + rcCmdMgr.addCommand(new CmdTechDrawBalloon()); +} diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui index 5829ebe238..49206852bd 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui @@ -7,7 +7,7 @@ 0 0 440 - 450 + 532
@@ -404,15 +404,48 @@ Each unit is approx. 0.1 mm wide
- - - - Vertex Scale + + + + + 0 + 0 + + + + + 174 + 0 + + + + + 0 + 0 + + + + Tolerance font size adjustment. Multiplier of dimension font size. + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.500000000000000 + + + TolSizeAdjust + + + Mod/TechDraw/Dimensions - - + + 0 @@ -426,34 +459,26 @@ Each unit is approx. 0.1 mm wide
- Scale of vertex dots. Multiplier of line width. - - - + Size of template field click handles Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - 5.000000000000000 + 3.000000000000000 - VertexScale + TemplateDotSize Mod/TechDraw/General
- - - - - true - - + + - Center Mark Scale + Vertex Scale @@ -504,6 +529,52 @@ Each unit is approx. 0.1 mm wide
+ + + + + 0 + 0 + + + + + 174 + 0 + + + + Scale of vertex dots. Multiplier of line width. + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 5.000000000000000 + + + VertexScale + + + Mod/TechDraw/General + + + + + + + + true + + + + Center Mark Scale + + + @@ -528,46 +599,6 @@ Each unit is approx. 0.1 mm wide - - - - - 0 - 0 - - - - - 174 - 0 - - - - - 0 - 0 - - - - Tolerance font size adjustment. Multiplier of dimension font size. - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 0.500000000000000 - - - TolSizeAdjust - - - Mod/TechDraw/Dimensions - - - @@ -575,34 +606,29 @@ Each unit is approx. 0.1 mm wide - - - - - 0 - 0 - - - - - 174 - 0 - + + + + Welding Symbol Scale + + + + - Size of template field click handles + Multiplier for size of welding symbols Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - 3.000000000000000 + 1.250000000000000 - TemplateDotSize + SymbolFactor - Mod/TechDraw/General + Mod/TechDraw/Decorations @@ -646,6 +672,11 @@ Each unit is approx. 0.1 mm wide + + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
Gui::PrefComboBox QComboBox diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp index 2a24864609..a726891675 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp @@ -69,6 +69,7 @@ void DlgPrefsTechDraw2Imp::saveSettings() pdsbEdgeFuzz->onSave(); pdsbMarkFuzz->onSave(); pdsbTemplateMark->onSave(); + pdsbSymbolScale->onSave(); } void DlgPrefsTechDraw2Imp::loadSettings() @@ -85,6 +86,7 @@ void DlgPrefsTechDraw2Imp::loadSettings() pdsbEdgeFuzz->onRestore(); pdsbMarkFuzz->onRestore(); pdsbTemplateMark->onRestore(); + pdsbSymbolScale->onRestore(); } /** diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui index 8c9b8c1cea..60ab9bc0c4 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui @@ -251,10 +251,10 @@ Then you need to increase the tile limit. Show Section Edges
- ShowUnits + ShowSectionEdges - /Mod/TechDraw/Dimensions + /Mod/TechDraw/General
diff --git a/src/Mod/TechDraw/Gui/QGIGhostHighlight.cpp b/src/Mod/TechDraw/Gui/QGIGhostHighlight.cpp new file mode 100644 index 0000000000..c0ac483a4c --- /dev/null +++ b/src/Mod/TechDraw/Gui/QGIGhostHighlight.cpp @@ -0,0 +1,115 @@ +/*************************************************************************** + * Copyright (c) 2020 WandererFan * + * * + * 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 +#endif + +#include +#include +#include +#include + +#include + +#include +#include "Rez.h" +#include "DrawGuiUtil.h" +#include "QGIView.h" +#include "QGIGhostHighlight.h" + +using namespace TechDrawGui; +using namespace TechDraw; + +QGIGhostHighlight::QGIGhostHighlight() +{ + setInteractive(true); + m_dragging = false; + + //make the ghost very visible + QFont f(QGIView::getPrefFont()); + double fontSize = QGIView::getPrefFontSize(); + setFont(f, fontSize); + setReference("drag"); + setStyle(Qt::SolidLine); + setColor(prefSelectColor()); + setWidth(Rez::guiX(1.0)); + setRadius(10.0); //placeholder +} + +QGIGhostHighlight::~QGIGhostHighlight() +{ + +} + +QVariant QGIGhostHighlight::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemPositionHasChanged && scene()) { + // nothing to do here? + } + return QGIHighlight::itemChange(change, value); +} + +void QGIGhostHighlight::mousePressEvent(QGraphicsSceneMouseEvent * event) +{ +// Base::Console().Message("QGIGhostHighlight::mousePress() - %X\n", this); + if ( (event->button() == Qt::LeftButton) && + (flags() && QGraphicsItem::ItemIsMovable) ) { + m_dragging = true; + event->accept(); + } + QGIHighlight::mousePressEvent(event); +} + +void QGIGhostHighlight::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) +{ +// Base::Console().Message("QGIGhostHighlight::mouseRelease() - pos: %s scenePos: %s\n", +// DrawUtil::formatVector(pos()).c_str(), +// DrawUtil::formatVector(mapToScene(pos())).c_str()); + if (m_dragging) { + m_dragging = false; + Q_EMIT positionChange(scenePos()); + event->accept(); + } + QGIHighlight::mouseReleaseEvent(event); +} + +void QGIGhostHighlight::setInteractive(bool state) +{ + setFlag(QGraphicsItem::ItemIsSelectable, state); + setFlag(QGraphicsItem::ItemIsMovable, state); + setFlag(QGraphicsItem::ItemSendsScenePositionChanges, state); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, state); +} + +//radius should scaled, but not Rez::guix() +void QGIGhostHighlight::setRadius(double r) +{ + setBounds(-r, r, r, -r); +} + +#include diff --git a/src/Mod/TechDraw/Gui/QGIGhostHighlight.h b/src/Mod/TechDraw/Gui/QGIGhostHighlight.h new file mode 100644 index 0000000000..0f86095a79 --- /dev/null +++ b/src/Mod/TechDraw/Gui/QGIGhostHighlight.h @@ -0,0 +1,66 @@ +/*************************************************************************** + * Copyright (c) 2020 WandererFan * + * * + * 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 * + * * + ***************************************************************************/ + +#ifndef TECHDRAWGUI_QGIGHOSTHIGHLIGHT_H +#define TECHDRAWGUI_QGIGHOSTHIGHLIGHT_H + +#include +#include +#include +#include + +#include "QGIHighlight.h" + +//a movable, selectable surrogate for detail highlights in QGIVPart + +namespace TechDrawGui +{ + +class TechDrawGuiExport QGIGhostHighlight : public QObject, public QGIHighlight +{ + Q_OBJECT +public: + explicit QGIGhostHighlight(); + ~QGIGhostHighlight(); + + enum {Type = QGraphicsItem::UserType + 177}; + int type() const { return Type;} + + void setInteractive(bool state); + void setRadius(double r); + +Q_SIGNALS: + void positionChange(QPointF p); + +protected: + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + + bool m_dragging; + +private: +}; + +} + +#endif // TECHDRAWGUI_QGIGHOSTHIGHLIGHT_H diff --git a/src/Mod/TechDraw/Gui/QGIHighlight.cpp b/src/Mod/TechDraw/Gui/QGIHighlight.cpp index f67212861e..8501a8106e 100644 --- a/src/Mod/TechDraw/Gui/QGIHighlight.cpp +++ b/src/Mod/TechDraw/Gui/QGIHighlight.cpp @@ -46,19 +46,65 @@ QGIHighlight::QGIHighlight() { m_refText = ""; m_refSize = 0.0; + setInteractive(false); + m_circle = new QGraphicsEllipseItem(); addToGroup(m_circle); + m_circle->setFlag(QGraphicsItem::ItemIsSelectable, false); + m_rect = new QGCustomRect(); addToGroup(m_rect); + m_rect->setFlag(QGraphicsItem::ItemIsSelectable, false); + m_reference = new QGCustomText(); addToGroup(m_reference); + m_reference->setFlag(QGraphicsItem::ItemIsSelectable, false); setWidth(Rez::guiX(0.75)); setStyle(getHighlightStyle()); setColor(getHighlightColor()); +} + +QGIHighlight::~QGIHighlight() +{ } +//really only want to emit signal at end of movement +//QVariant QGIHighlight::itemChange(GraphicsItemChange change, const QVariant &value) +//{ +// if (change == ItemPositionHasChanged && scene()) { +// // nothing to do here +// } +// return QGraphicsItem::itemChange(change, value); +//} + +//void QGIHighlight::mousePressEvent(QGraphicsSceneMouseEvent * event) +//{ +// Base::Console().Message("QGIHighlight::mousePress() - %X\n", this); +//// if(scene() && m_reference == scene()->mouseGrabberItem()) { +// if ( (event->button() == Qt::LeftButton) && +// (flags() && QGraphicsItem::ItemIsMovable) ) { +// m_dragging = true; +// } +//// } +// QGIDecoration::mousePressEvent(event); +//} + +//void QGIHighlight::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) +//{ +// Base::Console().Message("QGIHighlight::mouseRelease() - %X grabber: %X\n", this, scene()->mouseGrabberItem()); +//// if(scene() && this == scene()->mouseGrabberItem()) { +// if (m_dragging) { +// m_dragging = false; +//// QString itemName = data(0).toString(); +// Q_EMIT positionChange(pos()); +// return; +// } +//// } +// QGIDecoration::mouseReleaseEvent(event); +//} + void QGIHighlight::draw() { prepareGeometryChange(); @@ -100,6 +146,15 @@ void QGIHighlight::makeReference() } } +void QGIHighlight::setInteractive(bool state) +{ +// setAcceptHoverEvents(state); + setFlag(QGraphicsItem::ItemIsSelectable, state); + setFlag(QGraphicsItem::ItemIsMovable, state); + setFlag(QGraphicsItem::ItemSendsScenePositionChanges, state); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, state); +} + void QGIHighlight::setBounds(double x1,double y1,double x2,double y2) { m_start = QPointF(Rez::guiX(x1),Rez::guiX(-y1)); @@ -142,10 +197,9 @@ int QGIHighlight::getHoleStyle() return style; } - void QGIHighlight::paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) { QStyleOptionGraphicsItem myOption(*option); - myOption.state &= ~QStyle::State_Selected; +// myOption.state &= ~QStyle::State_Selected; setTools(); // painter->drawRect(boundingRect()); //good for debugging @@ -165,3 +219,4 @@ void QGIHighlight::setTools() m_reference->setDefaultTextColor(m_colCurrent); } + diff --git a/src/Mod/TechDraw/Gui/QGIHighlight.h b/src/Mod/TechDraw/Gui/QGIHighlight.h index 809f9b3fe3..4b3a9f02e5 100644 --- a/src/Mod/TechDraw/Gui/QGIHighlight.h +++ b/src/Mod/TechDraw/Gui/QGIHighlight.h @@ -25,9 +25,12 @@ #include #include +#include #include #include #include +#include +#include #include #include @@ -45,19 +48,25 @@ class TechDrawGuiExport QGIHighlight : public QGIDecoration { public: explicit QGIHighlight(); - ~QGIHighlight() {} + ~QGIHighlight(); - enum {Type = QGraphicsItem::UserType + 172}; + enum {Type = QGraphicsItem::UserType + 176}; int type() const { return Type;} - virtual void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0 ); + virtual void paint(QPainter * painter, + const QStyleOptionGraphicsItem * option, + QWidget * widget = 0 ) override; void setBounds(double x1,double y1,double x2,double y2); void setReference(char* sym); void setFont(QFont f, double fsize); virtual void draw(); + void setInteractive(bool state); protected: +/* virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;*/ +/* virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;*/ +/* virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;*/ QColor getHighlightColor(); Qt::PenStyle getHighlightStyle(); void makeHighlight(); @@ -65,6 +74,7 @@ protected: void setTools(); int getHoleStyle(void); +/* bool m_dragging;*/ private: char* m_refText; diff --git a/src/Mod/TechDraw/Gui/QGIUserTypes.h b/src/Mod/TechDraw/Gui/QGIUserTypes.h index 7995cfa785..df54412148 100644 --- a/src/Mod/TechDraw/Gui/QGIUserTypes.h +++ b/src/Mod/TechDraw/Gui/QGIUserTypes.h @@ -1,26 +1,26 @@ /* -Derived QGraphicsItem Classes type() Values +Derived QGI Classes type() Values Qt First UserType>> QGraphicsItem::UserType = 65536 -QGraphicsItemView : 101 -QGraphicsItemViewPart : 102 -QGraphicsItemEdge: 103 -QGraphicsItemFace: 104 -QGraphicsItemVertex: 105 -QGraphicsItemViewDimension : 106 -QGraphicsItemViewBalloon : 140 -QGraphicsItemBalloonLabel : 141 -QGraphicsItemDatumLabel : 107 -QGraphicsItemViewSection : 108 -QGraphicsItemArrow: 109 -QGraphicsItemViewCollection : 110 -QGraphicsItemViewOrthographic : 113 -QGraphicsItemViewAnnotation : 120 -QGraphicsItemViewSymbol : 121 -QGraphicsItemHatch : 122 //obsolete -QGraphicsItemClip : 123 -QGraphicsItemSpreadsheet : 124 +QGIView : 101 +QGIViewPart : 102 +QGIEdge: 103 +QGIFace: 104 +QGIVertex: 105 +QGIViewDimension : 106 +QGIViewBalloon : 140 +QGIBalloonLabel : 141 +QGIDatumLabel : 107 +QGIViewSection : 108 +QGIArrow: 109 +QGIViewCollection : 110 +QGIProjGroup : 113 +QGIViewAnnotation : 120 +QGIViewSymbol : 121 +QGIHatch : 122 //obsolete +QGIClip : 123 +QGISpreadsheet : 124 QGCustomText: 130 QGCustomSvg: 131 QGCustomClip: 132 @@ -28,9 +28,9 @@ QGCustomRect: 133 QGCustomLabel:135 QGCustomBorder: 136 QGDisplayArea: 137 -QGraphicsItemTemplate: 150 -QGraphicsItemDrawingTemplate: 151 -QGraphicsItemSVGTemplate: 153 +QGITemplate: 150 +QGIDrawingTemplate: 151 +QGISVGTemplate: 153 TemplateTextField: 160 QGIPrimPath: 170 QGICMark: 171 @@ -38,6 +38,8 @@ QGISectionLine: 172 QGIDecoration: 173 QGICenterLine: 174 QGIDimLines: 175 +QGIHighlight: 176 +QGIGhostHighlight: 177 QGICaption: 180 QGIViewImage: 200 QGCustomImage: 201 diff --git a/src/Mod/TechDraw/Gui/QGIView.h b/src/Mod/TechDraw/Gui/QGIView.h index ac8faff4ef..7ba639ee6c 100644 --- a/src/Mod/TechDraw/Gui/QGIView.h +++ b/src/Mod/TechDraw/Gui/QGIView.h @@ -121,6 +121,11 @@ public: static int calculateFontPixelWidth(const QFont &font); static const double DefaultFontSizeInMM; + static QString getPrefFont(void); + static double getPrefFontSize(void); + static double getDimFontSize(void); + + MDIViewPage* getMDIViewPage(void) const; virtual void removeChild(QGIView* child); @@ -145,9 +150,9 @@ protected: virtual QRectF customChildrenBoundingRect(void) const; void dumpRect(const char* text, QRectF r); - QString getPrefFont(void); - double getPrefFontSize(void); - double getDimFontSize(void); +/* QString getPrefFont(void);*/ +/* double getPrefFontSize(void);*/ +/* double getDimFontSize(void);*/ Base::Reference getParmGroupCol(void); diff --git a/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp b/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp index 0e8572f305..02c25ce9a4 100644 --- a/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp @@ -37,6 +37,9 @@ #include #endif +#include +#include + #include #include #include @@ -155,7 +158,10 @@ void QGIViewAnnotation::drawAnnotation() if (it != annoText.begin()) { ss << "
"; } - ss << Base::Tools::escapedUnicodeToUtf8(*it); + std::string u8String = Base::Tools::escapedUnicodeToUtf8(*it); +// what madness turns \' into \\\\\'? + std::string apos = std::regex_replace((u8String), std::regex("\\\\\'"), "'"); + ss << apos; } ss << "

\n\n "; diff --git a/src/Mod/TechDraw/Gui/QGVPage.cpp b/src/Mod/TechDraw/Gui/QGVPage.cpp index a6b1e51696..1c30fc1fb5 100644 --- a/src/Mod/TechDraw/Gui/QGVPage.cpp +++ b/src/Mod/TechDraw/Gui/QGVPage.cpp @@ -976,7 +976,7 @@ void QGVPage::postProcessXml(QTemporaryFile& temporaryFile, QString fileName, QS } QTextStream stream( &outFile ); - stream.setGenerateByteOrderMark(true); + stream.setGenerateByteOrderMark(false); stream.setCodec("UTF-8"); stream << exportDoc.toByteArray(); diff --git a/src/Mod/TechDraw/Gui/Rez.cpp b/src/Mod/TechDraw/Gui/Rez.cpp index e75c0ca02e..21ab1eb63f 100644 --- a/src/Mod/TechDraw/Gui/Rez.cpp +++ b/src/Mod/TechDraw/Gui/Rez.cpp @@ -67,6 +67,11 @@ Base::Vector2d Rez::guiX(Base::Vector3d v, bool planar) return Base::Vector2d(guiX(v.x), guiX(v.y)); } +QPointF Rez::guiX(QPointF p) +{ + return Rez::guiPt(p); +} + //turn Gui side value to App side value double Rez::appX(double x) { @@ -85,6 +90,7 @@ QPointF Rez::appX(QPointF p) } + //Misc conversions QPointF Rez::guiPt(QPointF p) { diff --git a/src/Mod/TechDraw/Gui/Rez.h b/src/Mod/TechDraw/Gui/Rez.h index f35e48a76c..5a03e8551f 100644 --- a/src/Mod/TechDraw/Gui/Rez.h +++ b/src/Mod/TechDraw/Gui/Rez.h @@ -43,6 +43,8 @@ public: static double guiX(double x); static Base::Vector3d guiX(Base::Vector3d v); static Base::Vector2d guiX(Base::Vector3d v, bool planar); + static QPointF guiX(QPointF p); + //turn Gui side value to App side value static double appX(double x); static Base::Vector3d appX(Base::Vector3d v); diff --git a/src/Mod/TechDraw/Gui/TaskDetail.cpp b/src/Mod/TechDraw/Gui/TaskDetail.cpp new file mode 100644 index 0000000000..270b8fb612 --- /dev/null +++ b/src/Mod/TechDraw/Gui/TaskDetail.cpp @@ -0,0 +1,585 @@ +/*************************************************************************** + * Copyright (c) 2020 Wandererfan +#include +#endif // #ifndef _PreComp_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "DrawGuiStd.h" +#include "QGVPage.h" +#include "QGIView.h" +#include "QGIPrimPath.h" +#include "QGIGhostHighlight.h" +#include "MDIViewPage.h" +#include "ViewProviderPage.h" +#include "Rez.h" +#include "QGIViewPart.h" + +#include "TaskDetail.h" + +using namespace TechDrawGui; +using namespace TechDraw; +using namespace Gui; + +#define CREATEMODE 0 +#define EDITMODE 1 + +//creation ctor +TaskDetail::TaskDetail(TechDraw::DrawViewPart* baseFeat): + ui(new Ui_TaskDetail), + m_detailFeat(nullptr), + m_baseFeat(baseFeat), + m_basePage(nullptr), + m_inProgressLock(false), + m_saveAnchor(Base::Vector3d(0.0, 0.0, 0.0)), + m_saveRadius(0.0), + m_baseName(std::string()), + m_pageName(std::string()), + m_detailName(std::string()), + m_doc(nullptr), + m_mode(CREATEMODE), + m_created(false) +{ + if (m_baseFeat == nullptr) { + //should be caught in CMD caller + Base::Console().Error("TaskDetail - bad parameters - base feature. Can not proceed.\n"); + return; + } + m_basePage = m_baseFeat->findParentPage(); + if (m_basePage == nullptr) { + Base::Console().Error("TaskDetail - bad parameters - base page. Can not proceed.\n"); + } + + m_baseName = m_baseFeat->getNameInDocument(); + m_doc = m_baseFeat->getDocument(); + m_pageName = m_basePage->getNameInDocument(); + + ui->setupUi(this); + + Gui::Document* activeGui = Gui::Application::Instance->getDocument(m_doc); + Gui::ViewProvider* vp = activeGui->getViewProvider(m_basePage); + ViewProviderPage* vpp = static_cast(vp); + m_mdi = vpp->getMDIViewPage(); + m_scene = m_mdi->m_scene; + m_view = m_mdi->getQGVPage(); + + createDetail(); + setUiFromFeat(); + setWindowTitle(QObject::tr("New Detail")); + + connect(ui->pbDragger, SIGNAL(clicked(bool)), + this, SLOT(onDraggerClicked(bool))); + connect(ui->qsbX, SIGNAL(editingFinished()), + this, SLOT(onXEdit())); + connect(ui->qsbY, SIGNAL(editingFinished()), + this, SLOT(onYEdit())); + connect(ui->qsbRadius, SIGNAL(editingFinished()), + this, SLOT(onRadiusEdit())); + + m_ghost = new QGIGhostHighlight(); + m_scene->addItem(m_ghost); + m_ghost->hide(); + connect(m_ghost, SIGNAL(positionChange(QPointF)), + this, SLOT(onHighlightMoved(QPointF))); +} + +//edit ctor +TaskDetail::TaskDetail(TechDraw::DrawViewDetail* detailFeat): + ui(new Ui_TaskDetail), + m_detailFeat(detailFeat), + m_baseFeat(nullptr), + m_basePage(nullptr), + m_inProgressLock(false), + m_saveAnchor(Base::Vector3d(0.0, 0.0, 0.0)), + m_saveRadius(0.0), + m_baseName(std::string()), + m_pageName(std::string()), + m_detailName(std::string()), + m_doc(nullptr), + m_mode(EDITMODE), + m_created(false) +{ + if (m_detailFeat == nullptr) { + //should be caught in CMD caller + Base::Console().Error("TaskDetail - bad parameters. Can not proceed.\n"); + return; + } + + m_doc = m_detailFeat->getDocument(); + m_detailName = m_detailFeat->getNameInDocument(); + + m_basePage = m_detailFeat->findParentPage(); + if (m_basePage != nullptr) { + m_pageName = m_basePage->getNameInDocument(); + } + + App::DocumentObject* baseObj = m_detailFeat->BaseView.getValue(); + m_baseFeat = dynamic_cast(baseObj); + if (m_baseFeat != nullptr) { + m_baseName = m_baseFeat->getNameInDocument(); + } else { + Base::Console().Error("TaskDetail - no BaseView. Can not proceed.\n"); + return; + } + + ui->setupUi(this); + + Gui::Document* activeGui = Gui::Application::Instance->getDocument(m_basePage->getDocument()); + Gui::ViewProvider* vp = activeGui->getViewProvider(m_basePage); + ViewProviderPage* vpp = static_cast(vp); + m_mdi = vpp->getMDIViewPage(); + m_scene = m_mdi->m_scene; + m_view = m_mdi->getQGVPage(); + + saveDetailState(); + setUiFromFeat(); + setWindowTitle(QObject::tr("Edit Detail")); + + connect(ui->pbDragger, SIGNAL(clicked(bool)), + this, SLOT(onDraggerClicked(bool))); + connect(ui->qsbX, SIGNAL(editingFinished()), + this, SLOT(onXEdit())); + connect(ui->qsbY, SIGNAL(editingFinished()), + this, SLOT(onYEdit())); + connect(ui->qsbRadius, SIGNAL(editingFinished()), + this, SLOT(onRadiusEdit())); + connect(ui->aeReference, SIGNAL(editingFinished()), + this, SLOT(onReferenceEdit())); + + m_ghost = new QGIGhostHighlight(); + m_scene->addItem(m_ghost); + m_ghost->hide(); + connect(m_ghost, SIGNAL(positionChange(QPointF)), + this, SLOT(onHighlightMoved(QPointF))); +} + +TaskDetail::~TaskDetail() +{ + delete ui; +} + +void TaskDetail::updateTask() +{ +// blockUpdate = true; + +// blockUpdate = false; +} + +void TaskDetail::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + ui->retranslateUi(this); + } +} + +//save the start conditions +void TaskDetail::saveDetailState() +{ +// Base::Console().Message("TD::saveDetailState()\n"); + TechDraw::DrawViewDetail* dvd = getDetailFeat(); + m_saveAnchor = dvd->AnchorPoint.getValue(); + m_saveRadius = dvd->Radius.getValue(); + m_saved = true; +} + +void TaskDetail::restoreDetailState() +{ +// Base::Console().Message("TD::restoreDetailState()\n"); + TechDraw::DrawViewDetail* dvd = getDetailFeat(); + dvd->AnchorPoint.setValue(m_saveAnchor); + dvd->Radius.setValue(m_saveRadius); +} + +//***** ui stuff *************************************************************** + +void TaskDetail::setUiFromFeat() +{ +// Base::Console().Message("TD::setUIFromFeat()\n"); + if (m_baseFeat != nullptr) { + std::string baseName = getBaseFeat()->getNameInDocument(); + ui->leBaseView->setText(Base::Tools::fromStdString(baseName)); + } + + Base::Vector3d anchor; + double radius; + + TechDraw::DrawViewDetail* detailFeat = getDetailFeat(); + QString detailDisplay = QString::fromUtf8(detailFeat->getNameInDocument()) + + QString::fromUtf8(" / ") + + QString::fromUtf8(detailFeat->Label.getValue()); + ui->leDetailView->setText(detailDisplay); + anchor = detailFeat->AnchorPoint.getValue(); + radius = detailFeat->Radius.getValue(); + QString ref = QString::fromUtf8(detailFeat->Reference.getValue()); + + ui->pbDragger->setText(QString::fromUtf8("Drag Highlight")); + ui->pbDragger->setEnabled(true); + int decimals = Base::UnitsApi::getDecimals(); + ui->qsbX->setUnit(Base::Unit::Length); + ui->qsbX->setDecimals(decimals); + ui->qsbY->setUnit(Base::Unit::Length); + ui->qsbY->setDecimals(decimals); + ui->qsbRadius->setDecimals(decimals); + ui->qsbRadius->setUnit(Base::Unit::Length); + ui->qsbX->setValue(anchor.x); + ui->qsbY->setValue(anchor.y); + ui->qsbRadius->setValue(radius); + ui->aeReference->setText(ref); +} + +//update ui point fields after tracker finishes +void TaskDetail::updateUi(QPointF p) +{ + ui->qsbX->setValue(p.x()); + ui->qsbY->setValue(- p.y()); +} + +void TaskDetail::enableInputFields(bool b) +{ + ui->qsbX->setEnabled(b); + ui->qsbY->setEnabled(b); + ui->qsbRadius->setEnabled(b); + ui->aeReference->setEnabled(b); +} + +void TaskDetail::onXEdit() +{ + updateDetail(); +} + +void TaskDetail::onYEdit() +{ + updateDetail(); +} + +void TaskDetail::onRadiusEdit() +{ + updateDetail(); +} + +void TaskDetail::onReferenceEdit() +{ + updateDetail(); +} + +void TaskDetail::onDraggerClicked(bool b) +{ + Q_UNUSED(b); + ui->pbDragger->setEnabled(false); + enableInputFields(false); + editByHighlight(); + return; +} + +void TaskDetail::editByHighlight() +{ +// Base::Console().Message("TD::editByHighlight()\n"); + if (m_ghost == nullptr) { + Base::Console().Error("TaskDetail::editByHighlight - no ghost object\n"); + return; + } + + m_scene->clearSelection(); + m_ghost->setSelected(true); + m_ghost->setPos(getAnchorScene()); + m_ghost->draw(); + m_ghost->show(); +} + +//dragEnd is in scene coords. +void TaskDetail::onHighlightMoved(QPointF dragEnd) +{ +// Base::Console().Message("TD::onHighlightMoved(%s) - highlight: %X\n", +// DrawUtil::formatVector(dragEnd).c_str(), m_ghost); + ui->pbDragger->setEnabled(true); + + double scale = getBaseFeat()->getScale(); + double x = Rez::guiX(getBaseFeat()->X.getValue()) * scale; + double y = Rez::guiX(getBaseFeat()->Y.getValue()) * scale; + QPointF basePosScene(x, -y); //base position in scene coords + + QPointF anchorDisplace = dragEnd - basePosScene; + QPointF newAnchorPos = Rez::appX(anchorDisplace) / scale; + + updateUi(newAnchorPos); + updateDetail(); + enableInputFields(true); + m_ghost->setSelected(false); + m_ghost->hide(); +} + +void TaskDetail::saveButtons(QPushButton* btnOK, + QPushButton* btnCancel) +{ + m_btnOK = btnOK; + m_btnCancel = btnCancel; +} + +void TaskDetail::enableTaskButtons(bool b) +{ + m_btnOK->setEnabled(b); + m_btnCancel->setEnabled(b); +} + +//***** Feature create & edit stuff ******************************************* +void TaskDetail::createDetail() +{ +// Base::Console().Message("TD::createDetail()\n"); + Gui::Command::openCommand("Create Detail"); + + m_detailName = m_doc->getUniqueObjectName("Detail"); + + Gui::Command::doCommand(Command::Doc,"App.activeDocument().addObject('TechDraw::DrawViewDetail','%s')", + m_detailName.c_str()); + App::DocumentObject *docObj = m_doc->getObject(m_detailName.c_str()); + TechDraw::DrawViewDetail* dvd = dynamic_cast(docObj); + if (!dvd) { + throw Base::TypeError("TaskDetail - new detail not found\n"); + } + m_detailFeat = dvd; + + dvd->Source.setValues(getBaseFeat()->Source.getValues()); + + Gui::Command::doCommand(Command::Doc,"App.activeDocument().%s.BaseView = App.activeDocument().%s", + m_detailName.c_str(),m_baseName.c_str()); + Gui::Command::doCommand(Command::Doc,"App.activeDocument().%s.Direction = App.activeDocument().%s.Direction", + m_detailName.c_str(),m_baseName.c_str()); + Gui::Command::doCommand(Command::Doc,"App.activeDocument().%s.XDirection = App.activeDocument().%s.XDirection", + m_detailName.c_str(),m_baseName.c_str()); + Gui::Command::doCommand(Command::Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", + m_pageName.c_str(), m_detailName.c_str()); + + Gui::Command::updateActive(); + Gui::Command::commitCommand(); + + getBaseFeat()->requestPaint(); + m_created = true; +} + +void TaskDetail::updateDetail() +{ +// Base::Console().Message("TD::updateDetail()\n"); + Gui::Command::openCommand("Update Detail"); + double x = ui->qsbX->rawValue(); + double y = ui->qsbY->rawValue(); + Base::Vector3d temp(x, y, 0.0); + TechDraw::DrawViewDetail* detailFeat = getDetailFeat(); + detailFeat->AnchorPoint.setValue(temp); + + double radius = ui->qsbRadius->rawValue(); + detailFeat->Radius.setValue(radius); + QString qRef = ui->aeReference->text(); + std::string ref = Base::Tools::toStdString(qRef); + detailFeat->Reference.setValue(ref); + + detailFeat->recomputeFeature(); + getBaseFeat()->requestPaint(); + Gui::Command::updateActive(); + Gui::Command::commitCommand(); +} + +//***** Getters **************************************************************** + +//get the current Anchor highlight position in scene coords +QPointF TaskDetail::getAnchorScene() +{ + TechDraw::DrawViewPart* dvp = getBaseFeat(); + TechDraw::DrawViewDetail* dvd = getDetailFeat(); + + Base::Vector3d anchorPos = dvd->AnchorPoint.getValue(); + double x = dvp->X.getValue(); + double y = dvp->Y.getValue(); + Base::Vector3d basePos(x, y, 0.0); + Base::Vector3d netPos = basePos + anchorPos; + netPos = Rez::guiX(netPos * dvp->getScale()); + + QPointF qAnchor(netPos.x, - netPos.y); + return qAnchor; +} + +// protects against stale pointers +DrawViewPart* TaskDetail::getBaseFeat() +{ +// Base::Console().Message("TD::getBaseFeat()\n"); + DrawViewPart* result = nullptr; + + if (m_doc != nullptr) { + App::DocumentObject* baseObj = m_doc->getObject(m_baseName.c_str()); + if (baseObj != nullptr) { + result = static_cast(baseObj); + } + } + if (result == nullptr) { + std::string msg = "TaskDetail - base feature " + + m_baseName + + " not found \n"; + throw Base::TypeError(msg); + } + return result; +} + +// protects against stale pointers +DrawViewDetail* TaskDetail::getDetailFeat() +{ +// Base::Console().Message("TD::getDetailFeat()\n"); + DrawViewDetail* result = nullptr; + + if (m_doc != nullptr) { + App::DocumentObject* detailObj = m_doc->getObject(m_detailName.c_str()); + if (detailObj != nullptr) { + result = static_cast(detailObj); + } + } + if (result == nullptr) { + std::string msg = "TaskDetail - detail feature " + + m_detailName + + " not found \n"; +// throw Base::TypeError("TaskDetail - detail feature not found\n"); + throw Base::TypeError(msg); + } + return result; +} + +//****************************************************************************** + +bool TaskDetail::accept() +{ +// Base::Console().Message("TD::accept()\n"); + + Gui::Document* doc = Gui::Application::Instance->getDocument(m_basePage->getDocument()); + if (!doc) return false; + + getDetailFeat()->requestPaint(); + getBaseFeat()->requestPaint(); + Gui::Command::doCommand(Gui::Command::Gui,"Gui.ActiveDocument.resetEdit()"); + + return true; +} + +bool TaskDetail::reject() +{ +// Base::Console().Message("TD::reject()\n"); + Gui::Document* doc = Gui::Application::Instance->getDocument(m_basePage->getDocument()); + if (!doc) return false; + + if (m_mode == CREATEMODE) { + if (m_created) { + Gui::Command::doCommand(Gui::Command::Gui,"App.activeDocument().removeObject('%s')", + m_detailName.c_str()); + } + } else { + restoreDetailState(); + getDetailFeat()->recomputeFeature(); + getBaseFeat()->requestPaint(); + } + + Gui::Command::doCommand(Gui::Command::Gui,"App.activeDocument().recompute()"); + Gui::Command::doCommand(Gui::Command::Gui,"Gui.ActiveDocument.resetEdit()"); + + return false; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +TaskDlgDetail::TaskDlgDetail(TechDraw::DrawViewPart* baseFeat) + : TaskDialog() +{ + widget = new TaskDetail(baseFeat); + taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/techdraw-DetailView"), + widget->windowTitle(), true, 0); + taskbox->groupLayout()->addWidget(widget); + Content.push_back(taskbox); +} + +TaskDlgDetail::TaskDlgDetail(TechDraw::DrawViewDetail* detailFeat) + : TaskDialog() +{ + widget = new TaskDetail(detailFeat); + taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/techdraw-DetailView"), + widget->windowTitle(), true, 0); + taskbox->groupLayout()->addWidget(widget); + Content.push_back(taskbox); +} + +TaskDlgDetail::~TaskDlgDetail() +{ +} + +void TaskDlgDetail::update() +{ +// widget->updateTask(); +} + +void TaskDlgDetail::modifyStandardButtons(QDialogButtonBox* box) +{ + QPushButton* btnOK = box->button(QDialogButtonBox::Ok); + QPushButton* btnCancel = box->button(QDialogButtonBox::Cancel); + widget->saveButtons(btnOK, btnCancel); +} + +//==== calls from the TaskView =============================================================== +void TaskDlgDetail::open() +{ +} + +void TaskDlgDetail::clicked(int) +{ +} + +bool TaskDlgDetail::accept() +{ + widget->accept(); + return true; +} + +bool TaskDlgDetail::reject() +{ + widget->reject(); + return true; +} + +#include diff --git a/src/Mod/TechDraw/Gui/TaskDetail.h b/src/Mod/TechDraw/Gui/TaskDetail.h new file mode 100644 index 0000000000..588dcaea23 --- /dev/null +++ b/src/Mod/TechDraw/Gui/TaskDetail.h @@ -0,0 +1,179 @@ +/*************************************************************************** + * Copyright (c) 2020 WandererFan * + * * + * 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 * + * * + ***************************************************************************/ + +#ifndef TECHDRAWGUI_TASKCOSVERTEX_H +#define TECHDRAWGUI_TASKCOSVERTEX_H + +#include +#include +#include +#include + +#include + +//TODO: make this a proper enum +#define TRACKERPICK 0 +#define TRACKEREDIT 1 +#define TRACKERCANCEL 2 +#define TRACKERCANCELEDIT 3 + +class Ui_TaskDetail; + +namespace App { +class DocumentObject; +} + +namespace TechDraw +{ +class DrawPage; +class DrawView; +class DrawDetail; +class DrawViewPart; +} + +namespace TechDrawGui +{ +class QGVPage; +class QGIView; +class QGIPrimPath; +class MDIViewPage; +class QGEPath; +class QGIDetail; +class QGIGhostHighlight; +class ViewProviderLeader; + +class TaskDetail : public QWidget +{ + Q_OBJECT + +public: + TaskDetail(TechDraw::DrawViewPart* baseFeat); + TaskDetail(TechDraw::DrawViewDetail* detailFeat); + ~TaskDetail(); + +public Q_SLOTS: + void onDraggerClicked(bool b); + void onHighlightMoved(QPointF newPos); + void onXEdit(); + void onYEdit(); + void onRadiusEdit(); + void onReferenceEdit(); + +public: + virtual bool accept(); + virtual bool reject(); + void updateTask(); + void saveButtons(QPushButton* btnOK, + QPushButton* btnCancel); + void enableTaskButtons(bool b); + +protected: + void changeEvent(QEvent *e); + void startDragger(void); + + void createDetail(); + void updateDetail(); + + void editByHighlight(); + + void blockButtons(bool b); + void setUiFromFeat(void); + void updateUi(QPointF p); + void enableInputFields(bool b); + + void saveDetailState(); + void restoreDetailState(); + QPointF getAnchorScene(); + + TechDraw::DrawViewPart* getBaseFeat(); + TechDraw::DrawViewDetail* getDetailFeat(); + +private: + Ui_TaskDetail * ui; + bool blockUpdate; + + QGIGhostHighlight* m_ghost; + + MDIViewPage* m_mdi; + QGraphicsScene* m_scene; + QGVPage* m_view; + TechDraw::DrawViewDetail* m_detailFeat; + TechDraw::DrawViewPart* m_baseFeat; + TechDraw::DrawPage* m_basePage; + QGIView* m_qgParent; + std::string m_qgParentName; + + bool m_inProgressLock; + + QPushButton* m_btnOK; + QPushButton* m_btnCancel; + + Base::Vector3d m_saveAnchor; + double m_saveRadius; + bool m_saved; + QPointF m_dragStart; + + std::string m_baseName; + std::string m_pageName; + std::string m_detailName; + App::Document* m_doc; + + bool m_mode; + bool m_created; +}; + +class TaskDlgDetail : public Gui::TaskView::TaskDialog +{ + Q_OBJECT + +public: + TaskDlgDetail(TechDraw::DrawViewPart* baseFeat); + TaskDlgDetail(TechDraw::DrawViewDetail* detailFeat); + ~TaskDlgDetail(); + +public: + /// is called the TaskView when the dialog is opened + virtual void open(); + /// is called by the framework if an button is clicked which has no accept or reject role + virtual void clicked(int); + /// is called by the framework if the dialog is accepted (Ok) + virtual bool accept(); + /// is called by the framework if the dialog is rejected (Cancel) + virtual bool reject(); + /// is called by the framework if the user presses the help button + virtual void helpRequested() { return;} + virtual bool isAllowedAlterDocument(void) const + { return false; } + void update(); + + void modifyStandardButtons(QDialogButtonBox* box); + +protected: + +private: + TaskDetail * widget; + Gui::TaskView::TaskBox* taskbox; +}; + +} //namespace TechDrawGui + +#endif // #ifndef TECHDRAWGUI_TASKCOSVERTEX_H diff --git a/src/Mod/TechDraw/Gui/TaskDetail.ui b/src/Mod/TechDraw/Gui/TaskDetail.ui new file mode 100644 index 0000000000..bf7d9798f3 --- /dev/null +++ b/src/Mod/TechDraw/Gui/TaskDetail.ui @@ -0,0 +1,296 @@ + + + TechDrawGui::TaskDetail + + + + 0 + 0 + 381 + 405 + + + + + 0 + 0 + + + + + 250 + 0 + + + + Detail Anchor + + + + :/icons/actions/techdraw-DetailView.svg:/icons/actions/techdraw-DetailView.svg + + + + + + + 0 + 0 + + + + + 300 + 300 + + + + + 300 + 300 + + + + QFrame::Box + + + QFrame::Raised + + + + + + + + + + false + + + false + + + Qt::NoFocus + + + false + + + + + + + Base View + + + + + + + Detail View + + + + + + + false + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Click to drag detail highlight to new position + + + Drag Highlight + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + + + size of detail view + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10.000000000000000 + + + + + + + X + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Y + + + + + + + + + + + + + + x position of detail highlight within view + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0.000000000000000 + + + + + + + y position of detail highlight within view + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Radius + + + + + + + Reference + + + + + + + Detail identifier + + + 1 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Gui::AccelLineEdit + QLineEdit +
Gui/Widgets.h
+
+ + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+
+ + + + +
diff --git a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp index 215e2706bb..bfcc9223d2 100644 --- a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp +++ b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp @@ -376,7 +376,7 @@ void TaskWeldingSymbol::onFlipSidesClicked() ui->leOtherTextR->setText(ui->leArrowTextR->text()); ui->leArrowTextR->setText(tempText); - // one cannot get the path from the icon therfore read out + // one cannot get the path from the icon therefore read out // the path property auto tempPathArrow = m_arrowFeat->SymbolFile.getValue(); auto tempPathOther = m_otherFeat->SymbolFile.getValue(); diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp b/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp index a1c35d66cf..b8469fc1a2 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp @@ -37,19 +37,29 @@ #include #include #include +#include +#include +#include +#include #include +#include +#include +#include #include #include #include #include #include +#include #include #include #include #include #include +#include "QGIView.h" +#include "TaskDetail.h" #include "ViewProviderViewPart.h" using namespace TechDrawGui; @@ -166,8 +176,11 @@ void ViewProviderViewPart::onChanged(const App::Property* prop) void ViewProviderViewPart::attach(App::DocumentObject *pcFeat) { TechDraw::DrawViewMulti* dvm = dynamic_cast(pcFeat); + TechDraw::DrawViewDetail* dvd = dynamic_cast(pcFeat); if (dvm != nullptr) { sPixmap = "TechDraw_Tree_Multi"; + } else if (dvd != nullptr) { + sPixmap = "actions/techdraw-DetailView"; } ViewProviderDrawingView::attach(pcFeat); @@ -232,6 +245,57 @@ std::vector ViewProviderViewPart::claimChildren(void) cons return tmp; } } +bool ViewProviderViewPart::setEdit(int ModNum) +{ + if (ModNum == ViewProvider::Default ) { + if (Gui::Control().activeDialog()) { //TaskPanel already open! + return false; + } + TechDraw::DrawViewPart* dvp = getViewObject(); + TechDraw::DrawViewDetail* dvd = dynamic_cast(dvp); + if (dvd != nullptr) { + // clear the selection (convenience) + Gui::Selection().clearSelection(); + Gui::Control().showDialog(new TaskDlgDetail(dvd)); +// Gui::Selection().clearSelection(); +// flush any lingering gui objects + Gui::Selection().addSelection(dvd->getDocument()->getName(), + dvd->getNameInDocument()); + Gui::Selection().clearSelection(); + Gui::Selection().addSelection(dvd->getDocument()->getName(), + dvd->getNameInDocument()); + +//Gui.ActiveDocument.resetEdit() +//>>> # Gui.Selection.addSelection('aaStart121','Detail') +//>>> # Gui.Selection.clearSelection() +//>>> # Gui.Selection.addSelection('aaStart121','Detail') +//>>> # Gui.Selection.addSelection('aaStart121','Detail') +//>>> # Gui.Selection.clearSelection() +//>>> # Gui.Selection.addSelection('aaStart121','Detail') + return true; + } + } else { + return ViewProviderDrawingView::setEdit(ModNum); + } + return true; +} + +void ViewProviderViewPart::unsetEdit(int ModNum) +{ + Q_UNUSED(ModNum); + if (ModNum == ViewProvider::Default) { + Gui::Control().closeDialog(); + } + else { + ViewProviderDrawingView::unsetEdit(ModNum); + } +} + +bool ViewProviderViewPart::doubleClicked(void) +{ + setEdit(ViewProvider::Default); + return true; +} TechDraw::DrawViewPart* ViewProviderViewPart::getViewObject() const { diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewPart.h b/src/Mod/TechDraw/Gui/ViewProviderViewPart.h index 9ab6a4501e..13ff73242c 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewPart.h +++ b/src/Mod/TechDraw/Gui/ViewProviderViewPart.h @@ -68,6 +68,9 @@ public: virtual std::vector getDisplayModes(void) const; virtual bool onDelete(const std::vector &); virtual bool canDelete(App::DocumentObject* obj) const; + virtual bool setEdit(int ModNum); + virtual void unsetEdit(int ModNum); + virtual bool doubleClicked(void); public: virtual void onChanged(const App::Property *prop); diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/edge-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/edge-weld.svg index 19103afa7c..7fdde1fd98 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/edge-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/edge-weld.svg @@ -1,6 +1,6 @@ -Торцевой шов. Edge Weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Торцевой шов. Edge Weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/flanging.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/flanging.svg index c62ac345ef..2f9de53c76 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/flanging.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/flanging.svg @@ -1,6 +1,6 @@ -С отбортовкой кромок/сварной шов углового соединения. Flanging – tech drawing chars (FreeCAD) https://freecad-gost.ru +С отбортовкой кромок/сварной шов углового соединения. Flanging - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/flare-bevel-groove.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/flare-bevel-groove.svg index 3f6215e7c7..b78928cfde 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/flare-bevel-groove.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/flare-bevel-groove.svg @@ -1,6 +1,6 @@ -Сварной шов между закругленным и плоским элементами. Flare bevel groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Сварной шов между закругленным и плоским элементами. Flare bevel groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/flare-v-groove.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/flare-v-groove.svg index e5750ab0cb..ad2dffc40d 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/flare-v-groove.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/flare-v-groove.svg @@ -1,6 +1,6 @@ -Сварной шов с V-образной разделкой между закругленными элементами. Flare V groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Сварной шов с V-образной разделкой между закругленными элементами. Flare V groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/cathetus.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/cathetus.svg index 6ffe7c13f4..70f39d8ed8 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/cathetus.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/cathetus.svg @@ -1,6 +1,6 @@ -Cathetus – tech drawing chars (FreeCAD) https://freecad-gost.ru +Cathetus - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-intermittent.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-intermittent.svg index e4d89b043e..3532e54d9d 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-intermittent.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-intermittent.svg @@ -1,6 +1,6 @@ -Шов прерывистый или точечный с шахматным расположением. Intermittent – tech drawing chars (FreeCAD) https://freecad-gost.ru +Шов прерывистый или точечный с шахматным расположением. Intermittent - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-non-closed.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-non-closed.svg index bf69ff25aa..416e43f455 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-non-closed.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-non-closed.svg @@ -1,6 +1,6 @@ -Шов по незамкнутой линии. Non-closed line – tech drawing chars (FreeCAD) https://freecad-gost.ru +Шов по незамкнутой линии. Non-closed line - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-site-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-site-weld.svg index e724286fcb..04ded41402 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-site-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-site-weld.svg @@ -1,6 +1,6 @@ -Шов выполнить при монтаже изделия. Site weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Шов выполнить при монтаже изделия. Site weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-smooth-processing.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-smooth-processing.svg index 24cc817780..5efdda2244 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-smooth-processing.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-smooth-processing.svg @@ -1,7 +1,7 @@ -Наплывы и неровности шва обработать. Smooth processing – tech drawing chars (FreeCAD) https://freecad-gost.ru +Наплывы и неровности шва обработать. Smooth processing - tech drawing chars (FreeCAD) https://freecad-gost.ru diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-usilenie-remove.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-usilenie-remove.svg index 2870376bb4..efe9cb0f44 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-usilenie-remove.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-usilenie-remove.svg @@ -1,7 +1,7 @@ -Усиление шва снять. Reinforcement removal – tech drawing chars (FreeCAD) https://freecad-gost.ru +Усиление шва снять. Reinforcement removal - tech drawing chars (FreeCAD) https://freecad-gost.ru diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/seam-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/seam-weld.svg index 24b15503e0..fe67f2c079 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/seam-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/seam-weld.svg @@ -1,7 +1,7 @@ -Шов при сварке плавлением с проплавлением верхнего элемента соединения (роликовый шов). Seam Weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Шов при сварке плавлением с проплавлением верхнего элемента соединения (роликовый шов). Seam Weld - tech drawing chars (FreeCAD) https://freecad-gost.ru diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-cjp-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-cjp-groove-weld.svg index 6aadb02a38..eb45b373e6 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-cjp-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-cjp-groove-weld.svg @@ -1,5 +1,5 @@ -Стыковой шов односторонний с V-образным крутым скосом одной кромки. Single bevel CJP groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с V-образным крутым скосом одной кромки. Single bevel CJP groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld-with-broad-root-face.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld-with-broad-root-face.svg index 0230f16cd6..eb1bda95f6 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld-with-broad-root-face.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld-with-broad-root-face.svg @@ -1,6 +1,6 @@ -Стыковой шов односторонний с V-образным скосом одной кромки и с притуплением кромки. Single bevel groove weld with broad root face – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с V-образным скосом одной кромки и с притуплением кромки. Single bevel groove weld with broad root face - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld.svg index cf8134e97e..d522dcf3ff 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld.svg @@ -1,6 +1,6 @@ -Стыковой шов односторонний с V-образным скосом одной кромки. Single bevel groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с V-образным скосом одной кромки. Single bevel groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-j-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-j-groove-weld.svg index d73bc61d4d..860f3a9ceb 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-j-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-j-groove-weld.svg @@ -1,7 +1,7 @@ -Стыковой шов односторонний с J-образным криволинейным скосом одной кромки. Single J groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с J-образным криволинейным скосом одной кромки. Single J groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-u-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-u-groove-weld.svg index 3dc7e1c303..dff7f8db86 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-u-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-u-groove-weld.svg @@ -1,7 +1,7 @@ -Стыковой шов односторонний с U-образным криволинейным скосом двух кромок. Single U groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с U-образным криволинейным скосом двух кромок. Single U groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-cjp-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-cjp-groove-weld.svg index 0049f8abf2..6809b0f40e 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-cjp-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-cjp-groove-weld.svg @@ -1,6 +1,6 @@ -Стыковой шов односторонний с V-образным крутым скосом кромок. Single V CJP Groove Weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с V-образным крутым скосом кромок. Single V CJP Groove Weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld-with-broad-root-face.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld-with-broad-root-face.svg index 9e662c038e..362481a750 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld-with-broad-root-face.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld-with-broad-root-face.svg @@ -1,7 +1,7 @@ -Стыковой шов односторонний с V-образным скосом с притуплением кромок. Single V Groove Weld with Broad Root Face – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с V-образным скосом с притуплением кромок. Single V Groove Weld with Broad Root Face - tech drawing chars (FreeCAD) https://freecad-gost.ru diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld.svg index 207d34d0d9..b0b0f66adb 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld.svg @@ -1,6 +1,6 @@ -Стыковой шов односторонний с V-образным скосом двух кромок. Single V Groove Weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с V-образным скосом двух кромок. Single V Groove Weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/spile-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/spile-weld.svg index df378a69af..71a1d200d4 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/spile-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/spile-weld.svg @@ -1,6 +1,6 @@ -Столбчатый шов. Spile groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Столбчатый шов. Spile groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/square-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/square-groove-weld.svg index 8a6c21ac07..4097b7a660 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/square-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/square-groove-weld.svg @@ -1,6 +1,6 @@ -Стыковой шов без скоса кромок. Square groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов без скоса кромок. Square groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/surfacing.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/surfacing.svg index f456989793..0ce5050bde 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/surfacing.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/surfacing.svg @@ -1,6 +1,6 @@ -Наплавка. Surfacing – tech drawing chars (FreeCAD) https://freecad-gost.ru +Наплавка. Surfacing - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/ConcentricityFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/ConcentricityFrame.svg new file mode 100644 index 0000000000..30ed7a9181 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/ConcentricityFrame.svg @@ -0,0 +1,36 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/CylindricityFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/CylindricityFrame.svg new file mode 100644 index 0000000000..feb879875b --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/CylindricityFrame.svg @@ -0,0 +1,37 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/FlatnessFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/FlatnessFrame.svg new file mode 100644 index 0000000000..036857ae32 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/FlatnessFrame.svg @@ -0,0 +1,2 @@ + +image/svg+xml diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/ParallelismFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/ParallelismFrame.svg new file mode 100644 index 0000000000..8bdec00030 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/ParallelismFrame.svg @@ -0,0 +1,58 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/ReferenceSurface.svg b/src/Mod/TechDraw/Symbols/gd-and-t/ReferenceSurface.svg new file mode 100644 index 0000000000..6d0ff32172 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/ReferenceSurface.svg @@ -0,0 +1,24 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/RoundnessFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/RoundnessFrame.svg new file mode 100644 index 0000000000..8b1a9f1438 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/RoundnessFrame.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/SquarenessFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/SquarenessFrame.svg new file mode 100644 index 0000000000..d501eb59be --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/SquarenessFrame.svg @@ -0,0 +1,36 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/CutLine.svg b/src/Mod/TechDraw/Symbols/other/CutLine.svg new file mode 100644 index 0000000000..2ff4ed1153 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/CutLine.svg @@ -0,0 +1,169 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/Diameter.svg b/src/Mod/TechDraw/Symbols/other/Diameter.svg new file mode 100644 index 0000000000..e6d5212893 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/Diameter.svg @@ -0,0 +1,68 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/Envelope.svg b/src/Mod/TechDraw/Symbols/other/Envelope.svg new file mode 100644 index 0000000000..708b67ea68 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/Envelope.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/FilletInside.svg b/src/Mod/TechDraw/Symbols/other/FilletInside.svg new file mode 100644 index 0000000000..c45dcc53c9 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/FilletInside.svg @@ -0,0 +1,209 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/FilletOutside.svg b/src/Mod/TechDraw/Symbols/other/FilletOutside.svg new file mode 100644 index 0000000000..965fe87861 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/FilletOutside.svg @@ -0,0 +1,209 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/IndexCircle.svg b/src/Mod/TechDraw/Symbols/other/IndexCircle.svg new file mode 100644 index 0000000000..da16f04305 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/IndexCircle.svg @@ -0,0 +1,63 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/IndexTriangle.svg b/src/Mod/TechDraw/Symbols/other/IndexTriangle.svg new file mode 100644 index 0000000000..4c3ba15830 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/IndexTriangle.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/InsideEdgeBroken.svg b/src/Mod/TechDraw/Symbols/other/InsideEdgeBroken.svg new file mode 100644 index 0000000000..24e4edded0 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/InsideEdgeBroken.svg @@ -0,0 +1,197 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/OutsideEdgeBroken.svg b/src/Mod/TechDraw/Symbols/other/OutsideEdgeBroken.svg new file mode 100644 index 0000000000..0ffb09f1b0 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/OutsideEdgeBroken.svg @@ -0,0 +1,194 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/Rectangle.svg b/src/Mod/TechDraw/Symbols/other/Rectangle.svg new file mode 100644 index 0000000000..caa46e54f6 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/Rectangle.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/SectionLine.svg b/src/Mod/TechDraw/Symbols/other/SectionLine.svg new file mode 100644 index 0000000000..27ff955a97 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/SectionLine.svg @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/Square.svg b/src/Mod/TechDraw/Symbols/other/Square.svg new file mode 100644 index 0000000000..f09e487d17 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/Square.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/surface-roughness-symbols/Roughness.svg b/src/Mod/TechDraw/Symbols/surface-roughness-symbols/Roughness.svg new file mode 100644 index 0000000000..81c1c7fadb --- /dev/null +++ b/src/Mod/TechDraw/Symbols/surface-roughness-symbols/Roughness.svg @@ -0,0 +1,189 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/surface-roughness-symbols/RoughnessGeneral.svg b/src/Mod/TechDraw/Symbols/surface-roughness-symbols/RoughnessGeneral.svg new file mode 100644 index 0000000000..43a9653253 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/surface-roughness-symbols/RoughnessGeneral.svg @@ -0,0 +1,204 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Tools/plugins/widget/FreeCAD_widgets.sln b/src/Tools/plugins/widget/FreeCAD_widgets.sln new file mode 100644 index 0000000000..59eb9f9632 --- /dev/null +++ b/src/Tools/plugins/widget/FreeCAD_widgets.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1062 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FreeCAD_widgets", "FreeCAD_widgets.vcxproj", "{0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}.Debug|x64.ActiveCfg = Debug|x64 + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}.Debug|x64.Build.0 = Debug|x64 + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}.Release|x64.ActiveCfg = Release|x64 + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EC696D06-B20E-41F8-94A6-AFEB0D9F3D25} + EndGlobalSection +EndGlobal diff --git a/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj new file mode 100644 index 0000000000..ba4f3f9ba7 --- /dev/null +++ b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj @@ -0,0 +1,195 @@ + + + + + Release + x64 + + + Debug + x64 + + + + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1} + FreeCAD_widgets + Qt4VSv1.0 + 10.0.17763.0 + 10.0.17763.0 + + + + v141 + release\ + false + NotSet + DynamicLibrary + release\ + FreeCAD_widgets + + + v141 + debug\ + false + NotSet + DynamicLibrary + debug\ + FreeCAD_widgetsd + + + + + + + + + + + + release\ + release\ + FreeCAD_widgets + true + false + debug\ + debug\ + FreeCAD_widgetsd + true + + + + .;.;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtDesigner;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtUiPlugin;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtWidgets;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtGui;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtANGLE;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtXml;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtCore;release;/include;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\win32-msvc;%(AdditionalIncludeDirectories) + -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) + release\ + false + None + 4577;4467;%(DisableSpecificWarnings) + Sync + release\ + MaxSpeed + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_NO_DEBUG;QT_PLUGIN;QT_DESIGNER_LIB;QT_UIPLUGIN_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_CORE_LIB;QDESIGNER_EXPORT_WIDGETS;NDEBUG;%(PreprocessorDefinitions) + false + + MultiThreadedDLL + true + true + Level3 + + + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Designer.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Widgets.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Gui.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Xml.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Core.lib;%(AdditionalDependencies) + true + false + true + true + false + $(OutDir)\FreeCAD_widgets.dll + true + Windows + true + + + Unsigned + None + 0 + + + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_NO_DEBUG;QT_PLUGIN;QT_DESIGNER_LIB;QT_UIPLUGIN_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_CORE_LIB;QDESIGNER_EXPORT_WIDGETS;%(PreprocessorDefinitions) + + + + + .;.;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtDesigner;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtUiPlugin;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtWidgets;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtGui;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtANGLE;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtXml;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtCore;debug;/include;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\win32-msvc;%(AdditionalIncludeDirectories) + -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) + debug\ + false + ProgramDatabase + 4577;4467;%(DisableSpecificWarnings) + Sync + debug\ + Disabled + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_PLUGIN;QT_DESIGNER_LIB;QT_UIPLUGIN_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_CORE_LIB;QDESIGNER_EXPORT_WIDGETS;%(PreprocessorDefinitions) + false + MultiThreadedDebugDLL + true + true + Level3 + + + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Designerd.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Widgetsd.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Guid.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Xmld.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Cored.lib;%(AdditionalDependencies) + true + true + true + true + $(OutDir)\FreeCAD_widgetsd.dll + true + Windows + true + + + Unsigned + None + 0 + + + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_PLUGIN;QT_DESIGNER_LIB;QT_UIPLUGIN_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_CORE_LIB;QDESIGNER_EXPORT_WIDGETS;_DEBUG;%(PreprocessorDefinitions) + + + + + + + + + customwidgets.h;release\moc_predefs.h;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe;%(AdditionalInputs) + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN64 -DQT_NO_DEBUG -DQT_PLUGIN -DQT_DESIGNER_LIB -DQT_UIPLUGIN_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_XML_LIB -DQT_CORE_LIB -DQDESIGNER_EXPORT_WIDGETS --compiler-flavor=msvc --include D:/FreeCADGit/src/Tools/plugins/widget/release/moc_predefs.h -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/mkspecs/win32-msvc -ID:/FreeCADGit/src/Tools/plugins/widget -ID:/FreeCADGit/src/Tools/plugins/widget -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtDesigner -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtUiPlugin -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtWidgets -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtGui -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtANGLE -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtXml -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtCore -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\ATLMFC\include" -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" -I"C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" customwidgets.h -o release\moc_customwidgets.cpp + MOC customwidgets.h + release\moc_customwidgets.cpp;%(Outputs) + customwidgets.h;debug\moc_predefs.h;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe;%(AdditionalInputs) + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN64 -DQT_PLUGIN -DQT_DESIGNER_LIB -DQT_UIPLUGIN_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_XML_LIB -DQT_CORE_LIB -DQDESIGNER_EXPORT_WIDGETS --compiler-flavor=msvc --include D:/FreeCADGit/src/Tools/plugins/widget/debug/moc_predefs.h -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/mkspecs/win32-msvc -ID:/FreeCADGit/src/Tools/plugins/widget -ID:/FreeCADGit/src/Tools/plugins/widget -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtDesigner -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtUiPlugin -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtWidgets -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtGui -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtANGLE -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtXml -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtCore -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\ATLMFC\include" -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" -I"C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" customwidgets.h -o debug\moc_customwidgets.cpp + MOC customwidgets.h + debug\moc_customwidgets.cpp;%(Outputs) + + + plugin.h;release\moc_predefs.h;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe;%(AdditionalInputs) + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN64 -DQT_NO_DEBUG -DQT_PLUGIN -DQT_DESIGNER_LIB -DQT_UIPLUGIN_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_XML_LIB -DQT_CORE_LIB -DQDESIGNER_EXPORT_WIDGETS --compiler-flavor=msvc --include D:/FreeCADGit/src/Tools/plugins/widget/release/moc_predefs.h -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/mkspecs/win32-msvc -ID:/FreeCADGit/src/Tools/plugins/widget -ID:/FreeCADGit/src/Tools/plugins/widget -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtDesigner -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtUiPlugin -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtWidgets -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtGui -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtANGLE -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtXml -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtCore -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\ATLMFC\include" -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" -I"C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" plugin.h -o release\moc_plugin.cpp + MOC plugin.h + release\moc_plugin.cpp;%(Outputs) + plugin.h;debug\moc_predefs.h;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe;%(AdditionalInputs) + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN64 -DQT_PLUGIN -DQT_DESIGNER_LIB -DQT_UIPLUGIN_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_XML_LIB -DQT_CORE_LIB -DQDESIGNER_EXPORT_WIDGETS --compiler-flavor=msvc --include D:/FreeCADGit/src/Tools/plugins/widget/debug/moc_predefs.h -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/mkspecs/win32-msvc -ID:/FreeCADGit/src/Tools/plugins/widget -ID:/FreeCADGit/src/Tools/plugins/widget -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtDesigner -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtUiPlugin -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtWidgets -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtGui -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtANGLE -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtXml -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtCore -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\ATLMFC\include" -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" -I"C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" plugin.h -o debug\moc_plugin.cpp + MOC plugin.h + debug\moc_plugin.cpp;%(Outputs) + + + + + true + + + true + + + true + + + true + + + Document + true + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) + cl -BxC:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\qmake.exe -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -Zi -MDd -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\features\data\dummy.cpp 2>NUL >debug\moc_predefs.h + Generate moc_predefs.h + debug\moc_predefs.h;%(Outputs) + + + Document + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) + cl -BxC:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\qmake.exe -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -O2 -MD -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\features\data\dummy.cpp 2>NUL >release\moc_predefs.h + Generate moc_predefs.h + release\moc_predefs.h;%(Outputs) + true + + + + + \ No newline at end of file diff --git a/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.filters b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.filters new file mode 100644 index 0000000000..5316410ce8 --- /dev/null +++ b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.filters @@ -0,0 +1,65 @@ + + + + + {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} + cpp;c;cxx;moc;h;def;odl;idl;res; + + + {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} + cpp;c;cxx;moc;h;def;odl;idl;res; + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + Generated Files + + + Generated Files + + + Generated Files + + + Generated Files + + + Generated Files + + + Generated Files + + + \ No newline at end of file diff --git a/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.user b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.user new file mode 100644 index 0000000000..be25078707 --- /dev/null +++ b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Tools/thumbs/ThumbnailProvider/Main.cpp b/src/Tools/thumbs/ThumbnailProvider/Main.cpp index 8b61c2721a..7aacc17622 100644 --- a/src/Tools/thumbs/ThumbnailProvider/Main.cpp +++ b/src/Tools/thumbs/ThumbnailProvider/Main.cpp @@ -107,7 +107,8 @@ STDAPI DllRegisterServer() {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SampleThumbnailProvider L"\\InprocServer32", NULL, REG_SZ, (DWORD_PTR)szModule}, {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SampleThumbnailProvider L"\\InprocServer32", L"ThreadingModel", REG_SZ, (DWORD_PTR)L"Apartment"}, //{HKEY_CLASSES_ROOT, L".FCStd\\shellex", L"Trick only here to create shellex when not existing",REG_DWORD, 1}, - {HKEY_CLASSES_ROOT, L".FCStd\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", NULL, REG_SZ, (DWORD_PTR)szCLSID_SampleThumbnailProvider} + {HKEY_CLASSES_ROOT, L".FCStd\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", NULL, REG_SZ, (DWORD_PTR)szCLSID_SampleThumbnailProvider}, + {HKEY_CLASSES_ROOT, L".FCBak\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", NULL, REG_SZ, (DWORD_PTR)szCLSID_SampleThumbnailProvider} }; return CreateRegistryKeys(keys, ARRAYSIZE(keys)); diff --git a/src/Tools/thumbs/ThumbnailProvider/ThumbnailProvider.sln b/src/Tools/thumbs/ThumbnailProvider/ThumbnailProvider.sln deleted file mode 100644 index b6b85bd939..0000000000 --- a/src/Tools/thumbs/ThumbnailProvider/ThumbnailProvider.sln +++ /dev/null @@ -1,97 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ALL_BUILD", "ALL_BUILD.vcxproj", "{55E5AB2B-1257-3869-91CB-80B99160AF43}" - ProjectSection(ProjectDependencies) = postProject - {745470E2-1A58-3E79-A6CF-94239E5C37D7} = {745470E2-1A58-3E79-A6CF-94239E5C37D7} - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21} = {8AFB36EB-2C63-3A31-90CB-70C6389B2F21} - {F9CB5900-ED83-3271-A120-9443E5D49799} = {F9CB5900-ED83-3271-A120-9443E5D49799} - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959} = {C3B09E2C-25D6-38B5-9D5E-B024A85E0959} - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB} = {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CxImage", "CxImage.vcxproj", "{745470E2-1A58-3E79-A6CF-94239E5C37D7}" - ProjectSection(ProjectDependencies) = postProject - {F9CB5900-ED83-3271-A120-9443E5D49799} = {F9CB5900-ED83-3271-A120-9443E5D49799} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FCStdThumbnail", "FCStdThumbnail.vcxproj", "{8AFB36EB-2C63-3A31-90CB-70C6389B2F21}" - ProjectSection(ProjectDependencies) = postProject - {745470E2-1A58-3E79-A6CF-94239E5C37D7} = {745470E2-1A58-3E79-A6CF-94239E5C37D7} - {F9CB5900-ED83-3271-A120-9443E5D49799} = {F9CB5900-ED83-3271-A120-9443E5D49799} - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959} = {C3B09E2C-25D6-38B5-9D5E-B024A85E0959} - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB} = {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZERO_CHECK", "ZERO_CHECK.vcxproj", "{F9CB5900-ED83-3271-A120-9443E5D49799}" - ProjectSection(ProjectDependencies) = postProject - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "png", "png.vcxproj", "{C3B09E2C-25D6-38B5-9D5E-B024A85E0959}" - ProjectSection(ProjectDependencies) = postProject - {F9CB5900-ED83-3271-A120-9443E5D49799} = {F9CB5900-ED83-3271-A120-9443E5D49799} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "zlib.vcxproj", "{B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}" - ProjectSection(ProjectDependencies) = postProject - {F9CB5900-ED83-3271-A120-9443E5D49799} = {F9CB5900-ED83-3271-A120-9443E5D49799} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Release|x64 = Release|x64 - MinSizeRel|x64 = MinSizeRel|x64 - RelWithDebInfo|x64 = RelWithDebInfo|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {55E5AB2B-1257-3869-91CB-80B99160AF43}.Debug|x64.ActiveCfg = Debug|x64 - {55E5AB2B-1257-3869-91CB-80B99160AF43}.Release|x64.ActiveCfg = Release|x64 - {55E5AB2B-1257-3869-91CB-80B99160AF43}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {55E5AB2B-1257-3869-91CB-80B99160AF43}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.Debug|x64.ActiveCfg = Debug|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.Debug|x64.Build.0 = Debug|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.Release|x64.ActiveCfg = Release|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.Release|x64.Build.0 = Release|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.Debug|x64.ActiveCfg = Debug|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.Debug|x64.Build.0 = Debug|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.Release|x64.ActiveCfg = Release|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.Release|x64.Build.0 = Release|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.Debug|x64.ActiveCfg = Debug|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.Debug|x64.Build.0 = Debug|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.Release|x64.ActiveCfg = Release|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.Release|x64.Build.0 = Release|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.Debug|x64.ActiveCfg = Debug|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.Debug|x64.Build.0 = Debug|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.Release|x64.ActiveCfg = Release|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.Release|x64.Build.0 = Release|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.Debug|x64.ActiveCfg = Debug|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.Debug|x64.Build.0 = Debug|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.Release|x64.ActiveCfg = Release|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.Release|x64.Build.0 = Release|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {564F1C50-19FC-3C38-BCB2-602A91D58E37} - EndGlobalSection - GlobalSection(ExtensibilityAddIns) = postSolution - EndGlobalSection -EndGlobal