diff --git a/src/Gui/3Dconnexion/GuiNativeEventWin32.cpp b/src/Gui/3Dconnexion/GuiNativeEventWin32.cpp index ec519955fb..24726546be 100644 --- a/src/Gui/3Dconnexion/GuiNativeEventWin32.cpp +++ b/src/Gui/3Dconnexion/GuiNativeEventWin32.cpp @@ -71,8 +71,8 @@ Gui::GuiNativeEvent* Gui::GuiNativeEvent::gMouseInput = 0; #define _TRACE_RI_TYPE 0 #define _TRACE_RIDI_DEVICENAME 0 #define _TRACE_RIDI_DEVICEINFO 0 -#define _TRACE_RI_RAWDATA 1 -#define _TRACE_3DINPUT_PERIOD 1 +#define _TRACE_RI_RAWDATA 0 +#define _TRACE_3DINPUT_PERIOD 0 #ifdef _WIN64 typedef unsigned __int64 QWORD; diff --git a/src/Gui/Inventor/MarkerBitmaps.cpp b/src/Gui/Inventor/MarkerBitmaps.cpp index 732cbe5f36..90ed4a6af2 100644 --- a/src/Gui/Inventor/MarkerBitmaps.cpp +++ b/src/Gui/Inventor/MarkerBitmaps.cpp @@ -345,12 +345,12 @@ const char circleLine11_marker[CIRCLE_LINE11_WIDTH * CIRCLE_LINE11_HEIGHT + 1] = " " " xxxxxx " " xxxxxxxx " -" xx xx " +" xxx xxx" " xx xx" " xx xx" " xx xx" " xx xx" -" xx xx " +" xxx xxx" " xxxxxxxx " " xxxxxx "}; @@ -381,7 +381,7 @@ const char circleLine15_marker[CIRCLE_LINE15_WIDTH * CIRCLE_LINE15_HEIGHT + 1] = " " " xxxxxx " " xxxxxxxxxx " -" xx xx " +" xxx xxx " " xx xx " " xx xx" " xx xx" @@ -390,7 +390,7 @@ const char circleLine15_marker[CIRCLE_LINE15_WIDTH * CIRCLE_LINE15_HEIGHT + 1] = " xx xx" " xx xx" " xx xx " -" xx xxxx" +" xxx xxx " " xxxxxxxxxx " " xxxxxx "}; @@ -401,12 +401,12 @@ const char circleFilled11_marker[CIRCLE_FILLED11_WIDTH * CIRCLE_FILLED11_HEIGHT " " " xxxxxx " " xxxxxxxx " -" xxxxxxxx " " xxxxxxxxxx" " xxxxxxxxxx" " xxxxxxxxxx" " xxxxxxxxxx" -" xxxxxxxx " +" xxxxxxxxxx" +" xxxxxxxxxx" " xxxxxxxx " " xxxxxx "}; @@ -446,7 +446,7 @@ const char circleFilled15_marker[CIRCLE_FILLED15_WIDTH * CIRCLE_FILLED15_HEIGHT " xxxxxxxxxxxxxx" " xxxxxxxxxxxxxx" " xxxxxxxxxxxx " -" xxxxxxxxxxxx" +" xxxxxxxxxxxx " " xxxxxxxxxx " " xxxxxx "}; diff --git a/src/Gui/View3DInventor.cpp b/src/Gui/View3DInventor.cpp index 396da77057..43a923a66b 100644 --- a/src/Gui/View3DInventor.cpp +++ b/src/Gui/View3DInventor.cpp @@ -774,47 +774,53 @@ bool View3DInventor::setCamera(const char* pCamera) SoNode * Cam; SoDB::read(&in,Cam); - if (!Cam){ + if (!Cam || !Cam->isOfType(SoCamera::getClassTypeId())) { throw Base::RuntimeError("Camera settings failed to read"); } + // this is to make sure to reliably delete the node + CoinPtr camPtr(Cam, true); + // toggle between perspective and orthographic camera - if (Cam->getTypeId() != CamViewer->getTypeId()) - { + if (Cam->getTypeId() != CamViewer->getTypeId()) { _viewer->setCameraType(Cam->getTypeId()); CamViewer = _viewer->getSoRenderManager()->getCamera(); } - SoPerspectiveCamera * CamViewerP = 0; - SoOrthographicCamera * CamViewerO = 0; + SoPerspectiveCamera * CamViewerP = nullptr; + SoOrthographicCamera * CamViewerO = nullptr; if (CamViewer->getTypeId() == SoPerspectiveCamera::getClassTypeId()) { - CamViewerP = (SoPerspectiveCamera *)CamViewer; // safe downward cast, knows the type - } else if (CamViewer->getTypeId() == SoOrthographicCamera::getClassTypeId()) { - CamViewerO = (SoOrthographicCamera *)CamViewer; // safe downward cast, knows the type + CamViewerP = static_cast(CamViewer); // safe downward cast, knows the type + } + else if (CamViewer->getTypeId() == SoOrthographicCamera::getClassTypeId()) { + CamViewerO = static_cast(CamViewer); // safe downward cast, knows the type } if (Cam->getTypeId() == SoPerspectiveCamera::getClassTypeId()) { if (CamViewerP){ - CamViewerP->position = ((SoPerspectiveCamera *)Cam)->position; - CamViewerP->orientation = ((SoPerspectiveCamera *)Cam)->orientation; - CamViewerP->nearDistance = ((SoPerspectiveCamera *)Cam)->nearDistance; - CamViewerP->farDistance = ((SoPerspectiveCamera *)Cam)->farDistance; - CamViewerP->focalDistance = ((SoPerspectiveCamera *)Cam)->focalDistance; - } else { + CamViewerP->position = static_cast(Cam)->position; + CamViewerP->orientation = static_cast(Cam)->orientation; + CamViewerP->nearDistance = static_cast(Cam)->nearDistance; + CamViewerP->farDistance = static_cast(Cam)->farDistance; + CamViewerP->focalDistance = static_cast(Cam)->focalDistance; + } + else { throw Base::TypeError("Camera type mismatch"); } - } else if (Cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) { + } + else if (Cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) { if (CamViewerO){ - CamViewerO->viewportMapping = ((SoOrthographicCamera *)Cam)->viewportMapping; - CamViewerO->position = ((SoOrthographicCamera *)Cam)->position; - CamViewerO->orientation = ((SoOrthographicCamera *)Cam)->orientation; - CamViewerO->nearDistance = ((SoOrthographicCamera *)Cam)->nearDistance; - CamViewerO->farDistance = ((SoOrthographicCamera *)Cam)->farDistance; - CamViewerO->focalDistance = ((SoOrthographicCamera *)Cam)->focalDistance; - CamViewerO->aspectRatio = ((SoOrthographicCamera *)Cam)->aspectRatio ; - CamViewerO->height = ((SoOrthographicCamera *)Cam)->height; - } else { + CamViewerO->viewportMapping = static_cast(Cam)->viewportMapping; + CamViewerO->position = static_cast(Cam)->position; + CamViewerO->orientation = static_cast(Cam)->orientation; + CamViewerO->nearDistance = static_cast(Cam)->nearDistance; + CamViewerO->farDistance = static_cast(Cam)->farDistance; + CamViewerO->focalDistance = static_cast(Cam)->focalDistance; + CamViewerO->aspectRatio = static_cast(Cam)->aspectRatio ; + CamViewerO->height = static_cast(Cam)->height; + } + else { throw Base::TypeError("Camera type mismatch"); } } diff --git a/src/Gui/ViewProviderLine.cpp b/src/Gui/ViewProviderLine.cpp index 6294d73bf9..bdae7a5f8b 100644 --- a/src/Gui/ViewProviderLine.cpp +++ b/src/Gui/ViewProviderLine.cpp @@ -68,13 +68,11 @@ void ViewProviderLine::attach ( App::DocumentObject *obj ) { sep->addChild ( pCoords ); SoIndexedLineSet *pLines = new SoIndexedLineSet (); - pLines->ref(); pLines->coordIndex.setNum(3); pLines->coordIndex.setValues(0, 3, lines); sep->addChild ( pLines ); SoTranslation *textTranslation = new SoTranslation (); - textTranslation->ref (); textTranslation->translation.setValue ( SbVec3f ( -size * 49. / 50., size / 30., 0 ) ); sep->addChild ( textTranslation ); diff --git a/src/Gui/ViewProviderPlane.cpp b/src/Gui/ViewProviderPlane.cpp index c93e1b8fd9..02fa1b550e 100644 --- a/src/Gui/ViewProviderPlane.cpp +++ b/src/Gui/ViewProviderPlane.cpp @@ -70,13 +70,11 @@ void ViewProviderPlane::attach ( App::DocumentObject *obj ) { sep->addChild ( pCoords ); SoIndexedLineSet *pLines = new SoIndexedLineSet (); - pLines->ref(); pLines->coordIndex.setNum(6); pLines->coordIndex.setValues(0, 6, lines); sep->addChild ( pLines ); SoTranslation *textTranslation = new SoTranslation (); - textTranslation->ref (); textTranslation->translation.setValue ( SbVec3f ( -size * 49. / 50., size * 9./10., 0 ) ); sep->addChild ( textTranslation ); diff --git a/src/Mod/Arch/ArchSchedule.py b/src/Mod/Arch/ArchSchedule.py index 9a11a60e5f..57003b1441 100644 --- a/src/Mod/Arch/ArchSchedule.py +++ b/src/Mod/Arch/ArchSchedule.py @@ -220,7 +220,7 @@ class _ArchSchedule: prop = args[0].upper() fval = args[1].upper() if prop == "TYPE": - prop == "IFCTYPE" + prop = "IFCTYPE" if inv: if prop in props: csprop = o.PropertiesList[props.index(prop)] diff --git a/src/Mod/Draft/draftguitools/gui_snapper.py b/src/Mod/Draft/draftguitools/gui_snapper.py index 71692219ac..bd880e6030 100644 --- a/src/Mod/Draft/draftguitools/gui_snapper.py +++ b/src/Mod/Draft/draftguitools/gui_snapper.py @@ -410,14 +410,8 @@ class Snapper: if (not self.maxEdges) or (len(shape.Edges) <= self.maxEdges): if "Edge" in comp: # we are snapping to an edge - edge = None if shape.ShapeType == "Edge": edge = shape - else: - en = int(comp[4:])-1 - if len(shape.Edges) > en: - edge = shape.Edges[en] - if edge: snaps.extend(self.snapToEndpoints(edge)) snaps.extend(self.snapToMidpoint(edge)) snaps.extend(self.snapToPerpendicular(edge, lastpoint)) @@ -433,9 +427,9 @@ class Snapper: # extra ellipse options snaps.extend(self.snapToCenter(edge)) elif "Face" in comp: - en = int(comp[4:])-1 - if len(shape.Faces) > en: - face = shape.Faces[en] + # we are snapping to a face + if shape.ShapeType == "Face": + face = shape snaps.extend(self.snapToFace(face)) elif "Vertex" in comp: # directly snapped to a vertex diff --git a/src/Mod/Part/App/PropertyGeometryList.cpp b/src/Mod/Part/App/PropertyGeometryList.cpp index 432d3610df..3061c3d60d 100644 --- a/src/Mod/Part/App/PropertyGeometryList.cpp +++ b/src/Mod/Part/App/PropertyGeometryList.cpp @@ -226,7 +226,7 @@ void PropertyGeometryList::Restore(Base::XMLReader &reader) reader.readEndElement("GeometryList"); // assignment - setValues(values); + setValues(std::move(values)); } App::Property *PropertyGeometryList::Copy(void) const diff --git a/src/Mod/Part/Gui/ViewProviderExt.cpp b/src/Mod/Part/Gui/ViewProviderExt.cpp index 909049fdbf..5dcfd97135 100644 --- a/src/Mod/Part/Gui/ViewProviderExt.cpp +++ b/src/Mod/Part/Gui/ViewProviderExt.cpp @@ -1133,7 +1133,10 @@ void ViewProviderPartExt::updateVisual() const TopoDS_Face &actFace = TopoDS::Face(faceMap(i)); // get the mesh of the shape Handle (Poly_Triangulation) mesh = BRep_Tool::Triangulation(actFace,aLoc); - if (mesh.IsNull()) continue; + if (mesh.IsNull()) { + parts[ii] = 0; + continue; + } // getting the transformation of the shape/face gp_Trsf myTransf; diff --git a/src/Mod/Path/App/CommandPy.xml b/src/Mod/Path/App/CommandPy.xml index 14e6df4109..1588b9dbe1 100644 --- a/src/Mod/Path/App/CommandPy.xml +++ b/src/Mod/Path/App/CommandPy.xml @@ -43,7 +43,7 @@ pairs, or a placement, or a vector - toGCode(): returns a GCode representation of the command + setFromGCode(): sets the path from the contents of the given GCode string @@ -51,5 +51,8 @@ pairs, or a placement, or a vector transform(Placement): returns a copy of this command transformed by the given placement + + mutable Py::Dict parameters_copy_dict; + diff --git a/src/Mod/Path/App/CommandPyImp.cpp b/src/Mod/Path/App/CommandPyImp.cpp index 98dc904264..88bd30fce5 100644 --- a/src/Mod/Path/App/CommandPyImp.cpp +++ b/src/Mod/Path/App/CommandPyImp.cpp @@ -56,6 +56,11 @@ std::string CommandPy::representation(void) const return str.str(); } +// +// Py::Dict parameters_copy_dict is now a class member to avoid delete/create/copy on every read access from python code +// Now the pre-filled Py::Dict is returned which is more consistent with normal python behaviour. +// It should be cleared whenever the c++ Parameters object is changed eg setParameters() or other objects invalidate its content, eg setPlacement() +// https://forum.freecadweb.org/viewtopic.php?f=15&t=50583 PyObject *CommandPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper { @@ -117,6 +122,7 @@ int CommandPy::PyInit(PyObject* args, PyObject* kwd) } getCommandPtr()->Parameters[ckey]=cvalue; } + parameters_copy_dict.clear(); return 0; } PyErr_Clear(); // set by PyArg_ParseTuple() @@ -157,11 +163,13 @@ void CommandPy::setName(Py::String arg) Py::Dict CommandPy::getParameters(void) const { - Py::Dict dict; - for(std::map::iterator i = getCommandPtr()->Parameters.begin(); i != getCommandPtr()->Parameters.end(); ++i) { - dict.setItem(i->first, Py::Float(i->second)); + // dict now a class member , https://forum.freecadweb.org/viewtopic.php?f=15&t=50583 + if (parameters_copy_dict.length()==0) { + for(std::map::iterator i = getCommandPtr()->Parameters.begin(); i != getCommandPtr()->Parameters.end(); ++i) { + parameters_copy_dict.setItem(i->first, Py::Float(i->second)); + } } - return dict; + return parameters_copy_dict; } void CommandPy::setParameters(Py::Dict arg) @@ -200,6 +208,7 @@ void CommandPy::setParameters(Py::Dict arg) throw Py::TypeError("The dictionary can only contain number values"); } getCommandPtr()->Parameters[ckey]=cvalue; + parameters_copy_dict.clear(); } } @@ -224,6 +233,7 @@ PyObject* CommandPy::setFromGCode(PyObject *args) std::string gcode(pstr); try { getCommandPtr()->setFromGCode(gcode); + parameters_copy_dict.clear(); } catch (const Base::Exception& e) { PyErr_SetString(PyExc_ValueError, e.what()); @@ -249,6 +259,7 @@ void CommandPy::setPlacement(Py::Object arg) Py::Type PlacementType(pyType.o); if(arg.isType(PlacementType)) { getCommandPtr()->setFromPlacement( *static_cast((*arg))->getPlacementPtr() ); + parameters_copy_dict.clear(); } else throw Py::TypeError("Argument must be a placement"); } @@ -259,6 +270,7 @@ PyObject* CommandPy::transform(PyObject *args) if ( PyArg_ParseTuple(args, "O!", &(Base::PlacementPy::Type), &placement) ) { Base::PlacementPy *p = static_cast(placement); Path::Command trCmd = getCommandPtr()->transform( *p->getPlacementPtr() ); + parameters_copy_dict.clear(); return new CommandPy(new Path::Command(trCmd)); } else throw Py::TypeError("Argument must be a placement"); @@ -302,6 +314,7 @@ int CommandPy::setCustomAttributes(const char* attr, PyObject* obj) return 0; } getCommandPtr()->Parameters[satt]=cvalue; + parameters_copy_dict.clear(); return 1; } } diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 444be1ecd4..1e1c4e305c 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -96,6 +96,10 @@ SET(PathScripts_SRCS PathScripts/PathProfileFaces.py PathScripts/PathProfileFacesGui.py PathScripts/PathProfileGui.py + PathScripts/PathProperty.py + PathScripts/PathPropertyBag.py + PathScripts/PathPropertyBagGui.py + PathScripts/PathPropertyEditor.py PathScripts/PathSanity.py PathScripts/PathSelection.py PathScripts/PathSetupSheet.py @@ -198,6 +202,7 @@ SET(PathTests_SRCS PathTests/TestPathOpTools.py PathTests/TestPathPost.py PathTests/TestPathPreferences.py + PathTests/TestPathPropertyBag.py PathTests/TestPathSetupSheet.py PathTests/TestPathStock.py PathTests/TestPathThreadMilling.py @@ -208,6 +213,9 @@ SET(PathTests_SRCS PathTests/TestPathUtil.py PathTests/TestPathVcarve.py PathTests/TestPathVoronoi.py + PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb + PathTests/Tools/Library/test-path-tool-bit-library-00.fctl + PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd PathTests/boxtest.fcstd PathTests/test_centroid_00.ngc PathTests/test_geomop.fcstd @@ -279,6 +287,14 @@ INSTALL( Mod/Path/PathTests ) +INSTALL( + DIRECTORY + PathTests/Tools + DESTINATION + Mod/Path/PathTests +) + + INSTALL( FILES ${PathScripts_post_SRCS} diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index c99246c667..832f74f868 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -121,6 +121,8 @@ panels/PageOpVcarveEdit.ui panels/PathEdit.ui panels/PointEdit.ui + panels/PropertyBag.ui + panels/PropertyCreate.ui panels/SetupGlobal.ui panels/SetupOp.ui panels/ToolBitEditor.ui diff --git a/src/Mod/Path/Gui/Resources/panels/PageBaseGeometryEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageBaseGeometryEdit.ui index 3ea92feef0..42441cbc2c 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageBaseGeometryEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageBaseGeometryEdit.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 250 + 476 + 342 @@ -23,94 +23,28 @@ :/icons/Path_BaseGeometry.svg:/icons/Path_BaseGeometry.svg - - - - - - - Import - - - - - - - <html><head/><body><p>Remove the selected list items from the list of base geometries. The operation will not be applied to them.</p></body></html> - - - Remove - - - - - - - - 0 - 0 - - - - <html><head/><body><p>List of operations with Base Geometry in current Job.</p></body></html> - - - - - - - true - - - - 0 - 0 - - - - <html><head/><body><p>Select one or more features in the 3d view and press 'Add' to add them as the base items for this operation.</p><p><br/></p><p>Selected features can be deleted entirely.</p></body></html> - - - QAbstractItemView::ExtendedSelection - - - - - - - <html><head/><body><p>Clears list of base geometries.</p></body></html> - - - Clear - - - - - - - <html><head/><body><p>Add selected features to the list of base geometries for this operation.</p></body></html> - - - Add - - - - - - - All objects will be processed using the same operation properties - - - Qt::AlignCenter - - - true - - - - + + + + + <html><head/><body><p>Add selected features to the list of base geometries for this operation.</p></body></html> + + + Add + + - + + + + <html><head/><body><p>Remove the selected list items from the list of base geometries. The operation will not be applied to them.</p></body></html> + + + Remove + + + + Qt::Vertical @@ -123,14 +57,70 @@ + + + + <html><head/><body><p>Clears list of base geometries.</p></body></html> + + + Clear + + + + + + + All objects will be processed using the same operation properties + + + Qt::AlignCenter + + + true + + + + + + + true + + + + 0 + 0 + + + + <html><head/><body><p>Select one or more features in the 3d view and press 'Add' to add them as the base items for this operation.</p><p><br/></p><p>Selected features can be deleted entirely.</p></body></html> + + + QAbstractItemView::ExtendedSelection + + + + + + + Import + + + + + + + + 0 + 0 + + + + <html><head/><body><p>List of operations with Base Geometry in current Job.</p></body></html> + + + - - baseList - addBase - deleteBase - clearBase - diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui b/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui new file mode 100644 index 0000000000..ac9af5c8d5 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PropertyBag.ui @@ -0,0 +1,78 @@ + + + Form + + + + 0 + 0 + 552 + 651 + + + + Property Bag + + + + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::AllEditTriggers + + + true + + + true + + + false + + + + + + + + 0 + 0 + + + + + + + Remove + + + + + + + Modify... + + + + + + + Add... + + + + + + + + + + table + add + remove + + + + diff --git a/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui new file mode 100644 index 0000000000..c65c02ea82 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PropertyCreate.ui @@ -0,0 +1,182 @@ + + + Dialog + + + + 0 + 0 + 480 + 452 + + + + Create Property + + + + + + Name + + + + + + + <html><head/><body><p>Name of property.</p></body></html> + + + + + + + <html><head/><body><p>The category group the property belongs to.</p></body></html> + + + true + + + + + + + Group + + + + + + + <html><head/><body><p>The type of the property value.</p></body></html> + + + + + + + Type + + + + + + + val1,val2,val3,... + + + + + + + <html><head/><body><p>ToolTip to be displayed when user hovers mouse over property.</p></body></html> + + + true + + + + + + + Enums + + + + + + + ToolTip + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <html><head/><body><p>Check if you want to create several properties in a batch.</p></body></html> + + + Create another + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + propertyName + propertyGroup + propertyType + propertyEnum + propertyInfo + createAnother + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui b/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui index 1342da5649..d77a523a12 100644 --- a/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui +++ b/src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui @@ -6,7 +6,7 @@ 0 0 - 401 + 489 715 @@ -17,7 +17,7 @@ - + 0 0 @@ -32,6 +32,12 @@ + + + 0 + 0 + + Tool Bit @@ -65,6 +71,12 @@ + + + 0 + 0 + + 0 @@ -83,6 +95,9 @@ <html><head/><body><p>The file which defines the type and shape of the Tool Bit.</p></body></html> + + path + @@ -104,7 +119,7 @@ - Bit Parameter + Parameter @@ -184,7 +199,7 @@ - + 0 @@ -200,9 +215,6 @@ QAbstractItemView::AllEditTriggers - - true - diff --git a/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui b/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui index 70650bf817..805f7a3e3d 100644 --- a/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/ToolBitLibraryEdit.ui @@ -173,6 +173,12 @@ QFrame::Box + + QAbstractItemView::NoEditTriggers + + + false + diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index e26c2fe0da..20e35d8590 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -154,6 +154,10 @@ class PathWorkbench (Workbench): if extracmdlist: self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], extracmdlist) + self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], ["Separator"]) + self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP("Path", "Utils")], + ["Path_PropertyBag"]) + self.dressupcmds = dressupcmdlist curveAccuracy = PathPreferences.defaultLibAreaCurveAccuracy() diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py b/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py index 85dbb81e0a..c768fd53ab 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathCustomGui.py b/src/Mod/Path/PathScripts/PathCustomGui.py index 987bcfb50e..658468a4e0 100644 --- a/src/Mod/Path/PathScripts/PathCustomGui.py +++ b/src/Mod/Path/PathScripts/PathCustomGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCustom as PathCustom import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathDeburrGui.py b/src/Mod/Path/PathScripts/PathDeburrGui.py index 1b0a0da690..d98642f4dd 100644 --- a/src/Mod/Path/PathScripts/PathDeburrGui.py +++ b/src/Mod/Path/PathScripts/PathDeburrGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathDeburr as PathDeburr import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py b/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py index 34cffb1d68..6152c51433 100644 --- a/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py +++ b/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathDressupPathBoundary as PathDressupPathBoundary import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathDressupTagGui.py b/src/Mod/Path/PathScripts/PathDressupTagGui.py index 0a6c01dc6b..be312176a1 100644 --- a/src/Mod/Path/PathScripts/PathDressupTagGui.py +++ b/src/Mod/Path/PathScripts/PathDressupTagGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGeom as PathGeom import PathScripts.PathGetPoint as PathGetPoint import PathScripts.PathDressupHoldingTags as PathDressupTag @@ -418,7 +419,7 @@ class PathDressupTagViewProvider: '''this makes sure that the base operation is added back to the job and visible''' # pylint: disable=unused-argument PathLog.track() - if self.obj.Base.ViewObject: + if self.obj.Base and self.obj.Base.ViewObject: self.obj.Base.ViewObject.Visibility = True job = PathUtils.findParentJob(self.obj) if arg1.Object and arg1.Object.Base and job: diff --git a/src/Mod/Path/PathScripts/PathDrillingGui.py b/src/Mod/Path/PathScripts/PathDrillingGui.py index 534d44e1a8..4ebee90d5f 100644 --- a/src/Mod/Path/PathScripts/PathDrillingGui.py +++ b/src/Mod/Path/PathScripts/PathDrillingGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathDrilling as PathDrilling import PathScripts.PathGui as PathGui diff --git a/src/Mod/Path/PathScripts/PathEngraveGui.py b/src/Mod/Path/PathScripts/PathEngraveGui.py index 95d5ce9dc5..6301c4882d 100644 --- a/src/Mod/Path/PathScripts/PathEngraveGui.py +++ b/src/Mod/Path/PathScripts/PathEngraveGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathEngrave as PathEngrave import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index c9fee42062..fb37725bf3 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -48,6 +48,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): 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). ''' + PathLog.track() value = widget.property('rawValue') attr = PathUtil.getProperty(obj, prop) attrValue = attr.Value if hasattr(attr, 'Value') else attr @@ -98,10 +99,12 @@ class QuantitySpinBox: PathLog.track(widget) self.widget = widget self.onBeforeChange = onBeforeChange + self.prop = None self.attachTo(obj, prop) def attachTo(self, obj, prop = None): '''attachTo(obj, prop=None) ... use an existing editor for the given object and property''' + PathLog.track(self.prop, prop) self.obj = obj self.prop = prop if obj and prop: @@ -119,12 +122,14 @@ class QuantitySpinBox: def expression(self): '''expression() ... returns the expression if one is bound to the property''' + PathLog.track(self.prop, self.valid) if self.valid: return self.widget.property('expression') return '' def setMinimum(self, quantity): '''setMinimum(quantity) ... set the minimum''' + PathLog.track(self.prop, self.valid) if self.valid: value = quantity.Value if hasattr(quantity, 'Value') else quantity self.widget.setProperty('setMinimum', value) @@ -133,6 +138,7 @@ class QuantitySpinBox: '''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.''' + PathLog.track(self.prop, self.valid) if self.valid: if quantity is None: quantity = PathUtil.getProperty(self.obj, self.prop) @@ -141,6 +147,7 @@ class QuantitySpinBox: def updateProperty(self): '''updateProperty() ... update the bound property with the value from the spin box''' + PathLog.track(self.prop, self.valid) 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 cd48d41309..405dcb4fcc 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -66,6 +66,7 @@ def Startup(): # from PathScripts import PathProfileEdgesGui # from PathScripts import PathProfileFacesGui from PathScripts import PathProfileGui + from PathScripts import PathPropertyBagGui from PathScripts import PathSanity from PathScripts import PathSetupSheetGui from PathScripts import PathSimpleCopy diff --git a/src/Mod/Path/PathScripts/PathHelixGui.py b/src/Mod/Path/PathScripts/PathHelixGui.py index 141cce33d9..b65048ee9c 100644 --- a/src/Mod/Path/PathScripts/PathHelixGui.py +++ b/src/Mod/Path/PathScripts/PathHelixGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathHelix as PathHelix import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index f5d2fa9db2..7b7fcc483b 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -31,6 +31,7 @@ from PySide import QtCore, QtGui import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathJob as PathJob import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathJobDlg as PathJobDlg @@ -521,6 +522,7 @@ class StockCreateCylinderEdit(StockEdit): class StockFromExistingEdit(StockEdit): Index = 3 StockType = PathStock.StockType.Unknown + StockLabelPrefix = 'Stock' def editorFrame(self): return self.form.stockFromExisting @@ -529,7 +531,7 @@ class StockFromExistingEdit(StockEdit): stock = self.form.stockExisting.itemData(self.form.stockExisting.currentIndex()) if not (hasattr(obj.Stock, 'Objects') and len(obj.Stock.Objects) == 1 and obj.Stock.Objects[0] == stock): if stock: - stock = PathJob.createResourceClone(obj, stock, 'Stock', 'Stock') + stock = PathJob.createResourceClone(obj, stock, self.StockLabelPrefix , 'Stock') stock.ViewObject.Visibility = True PathStock.SetupStockObject(stock, PathStock.StockType.Unknown) stock.Proxy.execute(stock) @@ -555,7 +557,9 @@ class StockFromExistingEdit(StockEdit): index = -1 for i, solid in enumerate(self.candidates(obj)): self.form.stockExisting.addItem(solid.Label, solid) - if solid.Label == stockName: + label="{}-{}".format(self.StockLabelPrefix, solid.Label) + + if label == stockName: index = i self.form.stockExisting.setCurrentIndex(index if index != -1 else 0) diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index 8b2679a295..c0e24b03b4 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -422,7 +422,10 @@ class ObjectOp(object): zmax = max(zmax, bb.ZMax) for sub in sublist: try: - fbb = base.Shape.getElement(sub).BoundBox + if sub: + fbb = base.Shape.getElement(sub).BoundBox + else: + fbb = base.Shape.BoundBox zmin = max(zmin, faceZmin(bb, fbb)) zmax = max(zmax, fbb.ZMax) except Part.OCCError as e: diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index 64d93f2966..0aa304a5fc 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGeom as PathGeom import PathScripts.PathGetPoint as PathGetPoint import PathScripts.PathGui as PathGui @@ -42,13 +43,8 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Base classes and framework for Path operation's UI" -LOGLEVEL = False - -if LOGLEVEL: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) -else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) def translate(context, text, disambig=None): @@ -566,6 +562,7 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): return False def addBase(self): + PathLog.track() if self.addBaseGeometry(FreeCADGui.Selection.getSelectionEx()): # self.obj.Proxy.execute(self.obj) self.setFields(self.obj) @@ -636,11 +633,18 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): # Set base geometry list window to resize based on contents # Code reference: # https://stackoverflow.com/questions/6337589/qlistwidget-adjust-size-to-content + # ml: disabling this logic because I can't get it to work on HPD monitor. + # On my systems the values returned by the list object are also incorrect on + # creation, leading to a list object of size 15. count() always returns 0 until + # the list is actually displayed. The same is true for sizeHintForRow(0), which + # returns -1 until the widget is rendered. The widget claims to have a size of + # (100, 30), once it becomes visible the size is (535, 192). + # Leaving the framework here in case somebody figures out how to set this up + # properly. qList = self.form.baseList - # qList.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - col = qList.width() # 300 row = (qList.count() + qList.frameWidth()) * 15 - qList.setFixedSize(col, row) + #qList.setMinimumHeight(row) + PathLog.debug("baseList({}, {}) {} * {}".format(qList.size(), row, qList.count(), qList.sizeHintForRow(0))) class TaskPanelBaseLocationPage(TaskPanelPage): diff --git a/src/Mod/Path/PathScripts/PathPocketBaseGui.py b/src/Mod/Path/PathScripts/PathPocketBaseGui.py index 915b6ea71a..68920b0a5e 100644 --- a/src/Mod/Path/PathScripts/PathPocketBaseGui.py +++ b/src/Mod/Path/PathScripts/PathPocketBaseGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathPocketShapeGui.py b/src/Mod/Path/PathScripts/PathPocketShapeGui.py index 53bc26c336..4e1b651148 100644 --- a/src/Mod/Path/PathScripts/PathPocketShapeGui.py +++ b/src/Mod/Path/PathScripts/PathPocketShapeGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGeom as PathGeom import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index d914568631..a1c1e58fc5 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -151,26 +151,9 @@ def searchPathsPost(): return paths -def searchPathsTool(sub='Bit'): +def searchPathsTool(sub): paths = [] - - if 'Bit' == sub: - paths.append("{}/Bit".format(os.path.dirname(lastPathToolLibrary()))) - paths.append(lastPathToolBit()) - - if 'Library' == sub: - paths.append(lastPathToolLibrary()) - if 'Shape' == sub: - paths.append(lastPathToolShape()) - - def appendPath(p, sub): - if p: - paths.append(os.path.join(p, 'Tools', sub)) - paths.append(os.path.join(p, sub)) - paths.append(p) - appendPath(defaultFilePath(), sub) - appendPath(macroFilePath(), sub) - appendPath(os.path.join(FreeCAD.getHomePath(), "Mod/Path/"), sub) + paths.append(os.path.join(FreeCAD.getHomePath(), 'Mod', 'Path', 'Tools', sub)) return paths @@ -263,15 +246,19 @@ def setDefaultTaskPanelLayout(style): def experimentalFeaturesEnabled(): return preferences().GetBool(EnableExperimentalFeatures, False) + def suppressAllSpeedsWarning(): return preferences().GetBool(WarningSuppressAllSpeeds, True) + def suppressRapidSpeedsWarning(): return suppressAllSpeedsWarning() or preferences().GetBool(WarningSuppressRapidSpeeds, True) + def suppressSelectionModeWarning(): return preferences().GetBool(WarningSuppressSelectionMode, True) + def suppressOpenCamLibWarning(): return preferences().GetBool(WarningSuppressOpenCamLib, True) @@ -316,7 +303,8 @@ def lastPathToolLibrary(): def setLastPathToolLibrary(path): PathLog.track(path) curLib = lastFileToolLibrary() - if os.path.split(curLib)[0] != path: + PathLog.debug('curLib: {}'.format(curLib)) + if curLib and os.path.split(curLib)[0] != path: setLastFileToolLibrary('') # a path is known but not specific file return preferences().SetString(LastPathToolLibrary, path) diff --git a/src/Mod/Path/PathScripts/PathProbeGui.py b/src/Mod/Path/PathScripts/PathProbeGui.py index a00ec913e8..29c97d7adc 100644 --- a/src/Mod/Path/PathScripts/PathProbeGui.py +++ b/src/Mod/Path/PathScripts/PathProbeGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathProbe as PathProbe import PathScripts.PathOpGui as PathOpGui import PathScripts.PathGui as PathGui diff --git a/src/Mod/Path/PathScripts/PathProfileGui.py b/src/Mod/Path/PathScripts/PathProfileGui.py index a7f77d7062..8a85a6abfa 100644 --- a/src/Mod/Path/PathScripts/PathProfileGui.py +++ b/src/Mod/Path/PathScripts/PathProfileGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui import PathScripts.PathProfile as PathProfile diff --git a/src/Mod/Path/PathScripts/PathProperty.py b/src/Mod/Path/PathScripts/PathProperty.py new file mode 100644 index 0000000000..7e3eeb1e6f --- /dev/null +++ b/src/Mod/Path/PathScripts/PathProperty.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 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 * +# * * +# *************************************************************************** + +import PathScripts.PathLog as PathLog + +__title__ = "Property type abstraction for editing purposes" +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "Prototype objects to allow extraction of setup sheet values and editing." + + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +#PathLog.trackModule(PathLog.thisModule()) + +class Property(object): + '''Base class for all prototype properties''' + def __init__(self, name, propType, category, info): + self.name = name + self.propType = propType + self.category = category + self.info = info + self.editorMode = 0 + self.value = None + + def setValue(self, value): + self.value = value + def getValue(self): + return self.value + + def setEditorMode(self, mode): + self.editorMode = mode + + def displayString(self): + if self.value is None: + t = self.typeString() + p = 'an' if t[0] in ['A', 'E', 'I', 'O', 'U'] else 'a' + return "%s %s" % (p, t) + return self.value + + def typeString(self): + return "Property" + + def setupProperty(self, obj, name, category, value): + created = False + if not hasattr(obj, name): + obj.addProperty(self.propType, name, category, self.info) + self.initProperty(obj, name) + created = True + setattr(obj, name, value) + return created + + def initProperty(self, obj, name): + pass + + def setValueFromString(self, string): + self.setValue(self.valueFromString(string)) + + def valueFromString(self, string): + return string + +class PropertyEnumeration(Property): + def typeString(self): + return "Enumeration" + + def setValue(self, value): + if list == type(value): + self.enums = value # pylint: disable=attribute-defined-outside-init + else: + super(PropertyEnumeration, self).setValue(value) + + def getEnumValues(self): + return self.enums + + def initProperty(self, obj, name): + setattr(obj, name, self.enums) + +class PropertyQuantity(Property): + def displayString(self): + if self.value is None: + return Property.displayString(self) + return self.value.getUserPreferred()[0] + +class PropertyAngle(PropertyQuantity): + def typeString(self): + return "Angle" + +class PropertyDistance(PropertyQuantity): + def typeString(self): + return "Distance" + +class PropertyLength(PropertyQuantity): + def typeString(self): + return "Length" + +class PropertyPercent(Property): + def typeString(self): + return "Percent" + +class PropertyFloat(Property): + def typeString(self): + return "Float" + + def valueFromString(self, string): + return float(string) + +class PropertyInteger(Property): + def typeString(self): + return "Integer" + + def valueFromString(self, string): + return int(string) + +class PropertyBool(Property): + def typeString(self): + return "Bool" + + def valueFromString(self, string): + return bool(string) + +class PropertyString(Property): + def typeString(self): + return "String" + +class PropertyMap(Property): + def typeString(self): + return "Map" + + def displayString(self, value): + return str(value) + +class OpPrototype(object): + + PropertyType = { + 'App::PropertyAngle': PropertyAngle, + 'App::PropertyBool': PropertyBool, + 'App::PropertyDistance': PropertyDistance, + 'App::PropertyEnumeration': PropertyEnumeration, + 'App::PropertyFile': PropertyString, + 'App::PropertyFloat': PropertyFloat, + 'App::PropertyFloatConstraint': Property, + 'App::PropertyFloatList': Property, + 'App::PropertyInteger': PropertyInteger, + 'App::PropertyIntegerList': PropertyInteger, + 'App::PropertyLength': PropertyLength, + 'App::PropertyLink': Property, + 'App::PropertyLinkList': Property, + 'App::PropertyLinkSubListGlobal': Property, + 'App::PropertyMap': PropertyMap, + 'App::PropertyPercent': PropertyPercent, + 'App::PropertyString': PropertyString, + 'App::PropertyStringList': Property, + 'App::PropertyVectorDistance': Property, + 'App::PropertyVectorList': Property, + 'Part::PropertyPartShape': Property, + } + + def __init__(self, name): + self.Label = name + self.properties = {} + self.DoNotSetDefaultValues = True + self.Proxy = None + + def __setattr__(self, name, val): + if name in ['Label', 'DoNotSetDefaultValues', 'properties', 'Proxy']: + if name == 'Proxy': + val = None # make sure the proxy is never set + return super(OpPrototype, self).__setattr__(name, val) + self.properties[name].setValue(val) + + def addProperty(self, typeString, name, category, info = None): + prop = self.PropertyType[typeString](name, typeString, category, info) + self.properties[name] = prop + return self + + def setEditorMode(self, name, mode): + self.properties[name].setEditorMode(mode) + + def getProperty(self, name): + return self.properties[name] + + def setupProperties(self, setup): + return [p for p in self.properties if p.name in setup] diff --git a/src/Mod/Path/PathScripts/PathPropertyBag.py b/src/Mod/Path/PathScripts/PathPropertyBag.py new file mode 100644 index 0000000000..2b21be2c6b --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPropertyBag.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 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 * +# * * +# *************************************************************************** + +import FreeCAD +import PySide + +__title__ = 'Generic property container to store some values.' +__author__ = 'sliptonic (Brad Collette)' +__url__ = 'https://www.freecadweb.org' +__doc__ = 'A generic container for typed properties in arbitrary categories.' + +def translate(context, text, disambig=None): + return PySide.QtCore.QCoreApplication.translate(context, text, disambig) + + +SupportedPropertyType = { + 'Angle' : 'App::PropertyAngle', + 'Bool' : 'App::PropertyBool', + 'Distance' : 'App::PropertyDistance', + 'Enumeration' : 'App::PropertyEnumeration', + 'File' : 'App::PropertyFile', + 'Float' : 'App::PropertyFloat', + 'Integer' : 'App::PropertyInteger', + 'Length' : 'App::PropertyLength', + 'Percent' : 'App::PropertyPercent', + 'String' : 'App::PropertyString', + } + +def getPropertyTypeName(o): + for typ in SupportedPropertyType: + if SupportedPropertyType[typ] == o: + return typ + raise IndexError() + +class PropertyBag(object): + '''Property container object.''' + + CustomPropertyGroups = 'CustomPropertyGroups' + CustomPropertyGroupDefault = 'User' + + def __init__(self, obj): + obj.addProperty('App::PropertyStringList', self.CustomPropertyGroups, 'Base', PySide.QtCore.QT_TRANSLATE_NOOP('PathPropertyBag', 'List of custom property groups')) + self.onDocumentRestored(obj) + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + def onDocumentRestored(self, obj): + self.obj = obj + obj.setEditorMode(self.CustomPropertyGroups, 2) # hide + + def getCustomProperties(self): + '''getCustomProperties() ... Return a list of all custom properties created in this container.''' + return [p for p in self.obj.PropertiesList if self.obj.getGroupOfProperty(p) in self.obj.CustomPropertyGroups] + + def addCustomProperty(self, propertyType, name, group=None, desc=None): + '''addCustomProperty(propertyType, name, group=None, desc=None) ... adds a custom property and tracks its group.''' + if desc is None: + desc = '' + if group is None: + group = self.CustomPropertyGroupDefault + groups = self.obj.CustomPropertyGroups + if not group in groups: + groups.append(group) + self.obj.CustomPropertyGroups = groups + self.obj.addProperty(propertyType, name, group, desc) + + def refreshCustomPropertyGroups(self): + '''refreshCustomPropertyGroups() ... removes empty property groups, should be called after deleting properties.''' + customGroups = [] + for p in self.obj.PropertiesList: + group = self.obj.getGroupOfProperty(p) + if group in self.obj.CustomPropertyGroups and not group in customGroups: + customGroups.append(group) + self.obj.CustomPropertyGroups = customGroups + + +def Create(name = 'PropertyBag'): + obj = FreeCAD.ActiveDocument.addObject('App::FeaturePython', name) + obj.Proxy = PropertyBag(obj) + return obj + +def IsPropertyBag(obj): + '''Returns True if the supplied object is a property container (or its Proxy).''' + + if type(obj) == PropertyBag: + return True + if hasattr(obj, 'Proxy'): + return IsPropertyBag(obj.Proxy) + return False + diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py new file mode 100644 index 0000000000..54f55d4a56 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -0,0 +1,425 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 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 * +# * * +# *************************************************************************** + +import FreeCAD +import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathScripts.PathIconViewProvider as PathIconViewProvider +import PathScripts.PathLog as PathLog +import PathScripts.PathPropertyBag as PathPropertyBag +import PathScripts.PathPropertyEditor as PathPropertyEditor +import PathScripts.PathUtil as PathUtil + +from PySide import QtCore, QtGui + +__title__ = "Property Bag Editor" +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "Task panel editor for a PropertyBag" + +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) + +class ViewProvider(object): + '''ViewProvider for a PropertyBag. + It's sole job is to provide an icon and invoke the TaskPanel on edit.''' + + def __init__(self, vobj, name): + PathLog.track(name) + vobj.Proxy = self + self.icon = name + # mode = 2 + self.obj = None + self.vobj = None + + def attach(self, vobj): + PathLog.track() + self.vobj = vobj + self.obj = vobj.Object + + def getIcon(self): + return ":/icons/Path-SetupSheet.svg" + + def __getstate__(self): + return None + + def __setstate__(self, state): + # pylint: disable=unused-argument + return None + + def getDisplayMode(self, mode): + # pylint: disable=unused-argument + return 'Default' + + def setEdit(self, vobj, mode=0): + # pylint: disable=unused-argument + PathLog.track() + taskPanel = TaskPanel(vobj) + FreeCADGui.Control.closeDialog() + FreeCADGui.Control.showDialog(taskPanel) + taskPanel.setupUi() + return True + + def unsetEdit(self, vobj, mode): + # pylint: disable=unused-argument + FreeCADGui.Control.closeDialog() + return + + def claimChildren(self): + return [] + + def doubleClicked(self, vobj): + self.setEdit(vobj) + +class Delegate(QtGui.QStyledItemDelegate): + RoleObject = QtCore.Qt.UserRole + 1 + RoleProperty = QtCore.Qt.UserRole + 2 + RoleEditor = QtCore.Qt.UserRole + 3 + + + #def paint(self, painter, option, index): + # #PathLog.track(index.column(), type(option)) + + def createEditor(self, parent, option, index): + # pylint: disable=unused-argument + editor = PathPropertyEditor.Editor(index.data(self.RoleObject), index.data(self.RoleProperty)) + index.model().setData(index, editor, self.RoleEditor) + return editor.widget(parent) + + def setEditorData(self, widget, index): + PathLog.track(index.row(), index.column()) + index.data(self.RoleEditor).setEditorData(widget) + + def setModelData(self, widget, model, index): + # pylint: disable=unused-argument + PathLog.track(index.row(), index.column()) + editor = index.data(self.RoleEditor) + editor.setModelData(widget) + index.model().setData(index, editor.displayString(), QtCore.Qt.DisplayRole) + + def updateEditorGeometry(self, widget, option, index): + # pylint: disable=unused-argument + widget.setGeometry(option.rect) + +class PropertyCreate(object): + + def __init__(self, obj, grp, typ, another): + self.obj = obj + self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyCreate.ui") + + obj.Proxy.refreshCustomPropertyGroups() + for g in sorted(obj.CustomPropertyGroups): + self.form.propertyGroup.addItem(g) + if grp: + self.form.propertyGroup.setCurrentText(grp) + + for t in sorted(PathPropertyBag.SupportedPropertyType): + self.form.propertyType.addItem(t) + if PathPropertyBag.SupportedPropertyType[t] == typ: + typ = t + if typ: + self.form.propertyType.setCurrentText(typ) + else: + self.form.propertyType.setCurrentText('String') + self.form.createAnother.setChecked(another) + + self.form.propertyGroup.currentTextChanged.connect(self.updateUI) + self.form.propertyGroup.currentIndexChanged.connect(self.updateUI) + self.form.propertyName.textChanged.connect(self.updateUI) + self.form.propertyType.currentIndexChanged.connect(self.updateUI) + self.form.propertyEnum.textChanged.connect(self.updateUI) + + def updateUI(self): + typeSet = True + if self.propertyIsEnumeration(): + self.form.labelEnum.setEnabled(True) + self.form.propertyEnum.setEnabled(True) + typeSet = self.form.propertyEnum.text().strip() != '' + else: + self.form.labelEnum.setEnabled(False) + self.form.propertyEnum.setEnabled(False) + if self.form.propertyEnum.text().strip(): + self.form.propertyEnum.setText('') + + ok = self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok) + + if typeSet and self.propertyName() and self.propertyGroup(): + ok.setEnabled(True) + else: + ok.setEnabled(False) + + def propertyName(self): + return self.form.propertyName.text().strip() + def propertyGroup(self): + return self.form.propertyGroup.currentText().strip() + def propertyType(self): + return PathPropertyBag.SupportedPropertyType[self.form.propertyType.currentText()].strip() + def propertyInfo(self): + return self.form.propertyInfo.toPlainText().strip() + def createAnother(self): + return self.form.createAnother.isChecked() + def propertyEnumerations(self): + return [s.strip() for s in self.form.propertyEnum.text().strip().split(',')] + def propertyIsEnumeration(self): + return self.propertyType() == 'App::PropertyEnumeration' + + def exec_(self, name): + if name: + # property exists - this is an edit operation + self.form.propertyName.setText(name) + if self.propertyIsEnumeration(): + self.form.propertyEnum.setText(','.join(self.obj.getEnumerationsOfProperty(name))) + self.form.propertyInfo.setText(self.obj.getDocumentationOfProperty(name)) + + self.form.labelName.setEnabled(False) + self.form.propertyName.setEnabled(False) + self.form.labelType.setEnabled(False) + self.form.propertyType.setEnabled(False) + self.form.createAnother.setEnabled(False) + + else: + self.form.propertyName.setText('') + self.form.propertyInfo.setText('') + self.form.propertyEnum.setText('') + #self.form.propertyName.setFocus() + + self.updateUI() + + return self.form.exec_() + +Panel = [] + +class TaskPanel(object): + ColumnName = 0 + #ColumnType = 1 + ColumnVal = 1 + #TableHeaders = ['Property', 'Type', 'Value'] + TableHeaders = ['Property', 'Value'] + + def __init__(self, vobj): + self.obj = vobj.Object + self.props = sorted(self.obj.Proxy.getCustomProperties()) + self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui") + + # initialized later + self.model = None + self.delegate = None + FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Edit PropertyBag")) + Panel.append(self) + + def updateData(self, topLeft, bottomRight): + pass + + + def _setupProperty(self, i, name): + typ = PathPropertyBag.getPropertyTypeName(self.obj.getTypeIdOfProperty(name)) + val = PathUtil.getPropertyValueString(self.obj, name) + info = self.obj.getDocumentationOfProperty(name) + + self.model.setData(self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole) + #self.model.setData(self.model.index(i, self.ColumnType), typ, QtCore.Qt.EditRole) + self.model.setData(self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject) + self.model.setData(self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty) + self.model.setData(self.model.index(i, self.ColumnVal), val, QtCore.Qt.DisplayRole) + + self.model.setData(self.model.index(i, self.ColumnName), typ, QtCore.Qt.ToolTipRole) + #self.model.setData(self.model.index(i, self.ColumnType), info, QtCore.Qt.ToolTipRole) + self.model.setData(self.model.index(i, self.ColumnVal), info, QtCore.Qt.ToolTipRole) + + self.model.item(i, self.ColumnName).setEditable(False) + #self.model.item(i, self.ColumnType).setEditable(False) + + def setupUi(self): + PathLog.track() + + self.delegate = Delegate(self.form) + self.model = QtGui.QStandardItemModel(len(self.props), len(self.TableHeaders), self.form) + self.model.setHorizontalHeaderLabels(self.TableHeaders) + + for i,name in enumerate(self.props): + self._setupProperty(i, name) + + self.form.table.setModel(self.model) + self.form.table.setItemDelegateForColumn(self.ColumnVal, self.delegate) + self.form.table.resizeColumnsToContents() + + self.model.dataChanged.connect(self.updateData) + self.form.table.selectionModel().selectionChanged.connect(self.propertySelected) + self.form.add.clicked.connect(self.propertyAdd) + self.form.remove.clicked.connect(self.propertyRemove) + self.form.modify.clicked.connect(self.propertyModify) + self.form.table.doubleClicked.connect(self.propertyModifyIndex) + self.propertySelected([]) + + def accept(self): + FreeCAD.ActiveDocument.commitTransaction() + FreeCADGui.ActiveDocument.resetEdit() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + + def reject(self): + FreeCAD.ActiveDocument.abortTransaction() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + + def propertySelected(self, selection): + PathLog.track() + if selection: + self.form.modify.setEnabled(True) + self.form.remove.setEnabled(True) + else: + self.form.modify.setEnabled(False) + self.form.remove.setEnabled(False) + + def addCustomProperty(self, obj, dialog): + name = dialog.propertyName() + typ = dialog.propertyType() + grp = dialog.propertyGroup() + info = dialog.propertyInfo() + self.obj.Proxy.addCustomProperty(typ, name, grp, info) + if dialog.propertyIsEnumeration(): + setattr(self.obj, name, dialog.propertyEnumerations()) + return (name, info) + + def propertyAdd(self): + PathLog.track() + more = False + grp = None + typ = None + while True: + dialog = PropertyCreate(self.obj, grp, typ, more) + if dialog.exec_(None): + # if we block signals the view doesn't get updated, surprise, surprise + #self.model.blockSignals(True) + name, info = self.addCustomProperty(self.obj, dialog) + index = 0 + for i in range(self.model.rowCount()): + index = i + if self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) > dialog.propertyName(): + break + self.model.insertRows(index, 1) + self._setupProperty(index, name) + self.form.table.selectionModel().setCurrentIndex(self.model.index(index, 0), QtCore.QItemSelectionModel.Rows) + #self.model.blockSignals(False) + more = dialog.createAnother() + else: + more = False + if not more: + break + + def propertyModifyIndex(self, index): + PathLog.track(index.row(), index.column()) + row = index.row() + + obj = self.model.item(row, self.ColumnVal).data(Delegate.RoleObject) + nam = self.model.item(row, self.ColumnVal).data(Delegate.RoleProperty) + grp = obj.getGroupOfProperty(nam) + typ = obj.getTypeIdOfProperty(nam) + + dialog = PropertyCreate(self.obj, grp, typ, False) + if dialog.exec_(nam): + val = getattr(obj, nam) + obj.removeProperty(nam) + name, info = self.addCustomProperty(self.obj, dialog) + try: + setattr(obj, nam, val) + except: + # this can happen if the old enumeration value doesn't exist anymore + pass + newVal = PathUtil.getPropertyValueString(obj, nam) + self.model.setData(self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole) + + #self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole) + self.model.setData(self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole) + + def propertyModify(self): + PathLog.track() + rows = [] + for index in self.form.table.selectionModel().selectedIndexes(): + row = index.row() + if row in rows: + continue + rows.append(row) + + self.propertyModifyIndex(index) + + + def propertyRemove(self): + PathLog.track() + # first find all rows which need to be removed + rows = [] + for index in self.form.table.selectionModel().selectedIndexes(): + if not index.row() in rows: + rows.append(index.row()) + + # then remove them in reverse order so the indexes of the remaining rows + # to delete are still valid + for row in reversed(sorted(rows)): + self.obj.removeProperty(self.model.item(row).data(QtCore.Qt.EditRole)) + self.model.removeRow(row) + + +def Create(name = 'PropertyBag'): + '''Create(name = 'PropertyBag') ... creates a new setup sheet''' + FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Create PropertyBag")) + pcont = PathPropertyBag.Create(name) + PathIconViewProvider.Attach(pcont.ViewObject, name) + return pcont + +PathIconViewProvider.RegisterViewProvider('PropertyBag', ViewProvider) + +class PropertyBagCreateCommand(object): + '''Command to create a property container object''' + + def __init__(self): + pass + + def GetResources(self): + return {'MenuText': translate('PathPropertyBag', 'Property Bag'), + 'ToolTip': translate('PathPropertyBag', 'Creates an object which can be used to store reference properties.')} + + def IsActive(self): + return not FreeCAD.ActiveDocument is None + + def Activated(self): + sel = FreeCADGui.Selection.getSelectionEx() + obj = Create() + body = None + if sel: + if 'PartDesign::Body' == sel[0].Object.TypeId: + body = sel[0].Object + elif hasattr(sel[0].Object, 'getParentGeoFeatureGroup'): + body = sel[0].Object.getParentGeoFeatureGroup() + if body: + obj.Label = 'Attributes' + group = body.Group + group.append(obj) + body.Group = group + +if FreeCAD.GuiUp: + FreeCADGui.addCommand('Path_PropertyBag', PropertyBagCreateCommand()) + +FreeCAD.Console.PrintLog("Loading PathPropertyBagGui ... done\n") diff --git a/src/Mod/Path/PathScripts/PathPropertyEditor.py b/src/Mod/Path/PathScripts/PathPropertyEditor.py new file mode 100644 index 0000000000..f376e351fd --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPropertyEditor.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 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 * +# * * +# *************************************************************************** + +import FreeCAD +import PathScripts.PathLog as PathLog +import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype + +from PySide import QtCore, QtGui + +__title__ = "Path Property Editor" +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "Task panel editor for Properties" + +# Qt translation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +#PathLog.trackModule(PathLog.thisModule()) + + +class _PropertyEditor(object): + '''Base class of all property editors - just outlines the TableView delegate interface.''' + def __init__(self, obj, prop): + self.obj = obj + self.prop = prop + + def widget(self, parent): + '''widget(parent) ... called by the delegate to get a new editor widget. + Must be implemented by subclasses and return the widget.''' + pass # pylint: disable=unnecessary-pass + + def setEditorData(self, widget): + '''setEditorData(widget) ... called by the delegate to initialize the editor. + The widget is the object returned by widget(). + Must be implemented by subclasses.''' + pass # pylint: disable=unnecessary-pass + + def setModelData(self, widget): + '''setModelData(widget) ... called by the delegate to store new values. + Must be implemented by subclasses.''' + pass # pylint: disable=unnecessary-pass + + def propertyValue(self): + return self.obj.getPropertyByName(self.prop) + + def setProperty(self, value): + setattr(self.obj, self.prop, value) + + def displayString(self): + return self.propertyValue() + +class _PropertyEditorBool(_PropertyEditor): + '''Editor for boolean values - uses a combo box.''' + + def widget(self, parent): + return QtGui.QComboBox(parent) + + def setEditorData(self, widget): + widget.clear() + widget.addItems([str(False), str(True)]) + index = 1 if self.propertyValue() else 0 + widget.setCurrentIndex(index) + + def setModelData(self, widget): + self.setProperty(widget.currentText() == str(True)) + +class _PropertyEditorString(_PropertyEditor): + '''Editor for string values - uses a line edit.''' + + def widget(self, parent): + return QtGui.QLineEdit(parent) + + def setEditorData(self, widget): + text = '' if self.propertyValue() is None else self.propertyValue() + widget.setText(text) + + def setModelData(self, widget): + self.setProperty(widget.text()) + +class _PropertyEditorQuantity(_PropertyEditor): + + def widget(self, parent): + return QtGui.QLineEdit(parent) + + def setEditorData(self, widget): + quantity = self.propertyValue() + if quantity is None: + quantity = self.defaultQuantity() + widget.setText(quantity.getUserPreferred()[0]) + + def defaultQuantity(self): + pass + + def setModelData(self, widget): + self.setProperty(FreeCAD.Units.Quantity(widget.text())) + + def displayString(self): + if self.propertyValue() is None: + return '' + return self.propertyValue().getUserPreferred()[0] + +class _PropertyEditorAngle(_PropertyEditorQuantity): + '''Editor for angle values - uses a line edit''' + + def defaultQuantity(self): + return FreeCAD.Units.Quantity(0, FreeCAD.Units.Angle) + +class _PropertyEditorLength(_PropertyEditorQuantity): + '''Editor for length values - uses a line edit.''' + + def defaultQuantity(self): + return FreeCAD.Units.Quantity(0, FreeCAD.Units.Length) + +class _PropertyEditorPercent(_PropertyEditor): + '''Editor for percent values - uses a spin box.''' + + def widget(self, parent): + return QtGui.QSpinBox(parent) + + def setEditorData(self, widget): + widget.setRange(0, 100) + value = self.propertyValue() + if value is None: + value = 0 + widget.setValue(value) + + def setModelData(self, widget): + self.setProperty(widget.value()) + +class _PropertyEditorInteger(_PropertyEditor): + '''Editor for integer values - uses a spin box.''' + + def widget(self, parent): + return QtGui.QSpinBox(parent) + + def setEditorData(self, widget): + value = self.propertyValue() + if value is None: + value = 0 + widget.setValue(value) + + def setModelData(self, widget): + self.setProperty(widget.value()) + +class _PropertyEditorFloat(_PropertyEditor): + '''Editor for float values - uses a double spin box.''' + + def widget(self, parent): + return QtGui.QDoubleSpinBox(parent) + + def setEditorData(self, widget): + value = self.propertyValue() + if value is None: + value = 0.0 + widget.setValue(value) + + def setModelData(self, widget): + self.setProperty(widget.value()) + +class _PropertyEditorFile(_PropertyEditor): + + def widget(self, parent): + return QtGui.QLineEdit(parent) + + def setEditorData(self, widget): + text = '' if self.propertyValue() is None else self.propertyValue() + widget.setText(text) + + def setModelData(self, widget): + self.setProperty(widget.text()) + +class _PropertyEditorEnumeration(_PropertyEditor): + + def widget(self, parent): + return QtGui.QComboBox(parent) + + def setEditorData(self, widget): + widget.clear() + widget.addItems(self.obj.getEnumerationsOfProperty(self.prop)) + widget.setCurrentText(self.propertyValue()) + + def setModelData(self, widget): + self.setProperty(widget.currentText()) + +_EditorFactory = { + 'App::PropertyAngle' : _PropertyEditorAngle, + 'App::PropertyBool' : _PropertyEditorBool, + 'App::PropertyDistance' : _PropertyEditorLength, + 'App::PropertyEnumeration' : _PropertyEditorEnumeration, + #'App::PropertyFile' : _PropertyEditorFile, + 'App::PropertyFloat' : _PropertyEditorFloat, + 'App::PropertyInteger' : _PropertyEditorInteger, + 'App::PropertyLength' : _PropertyEditorLength, + 'App::PropertyPercent' : _PropertyEditorPercent, + 'App::PropertyString' : _PropertyEditorString, + } + +def Types(): + '''Return the types of properties supported.''' + return [t for t in _EditorFactory] + +def Editor(obj, prop): + '''Returns an editor class to be used for the given property.''' + factory = _EditorFactory[obj.getTypeIdOfProperty(prop)] + if factory: + return factory(obj, prop) + return None diff --git a/src/Mod/Path/PathScripts/PathSetupSheetGui.py b/src/Mod/Path/PathScripts/PathSetupSheetGui.py index d2ceb3956b..1584b964a0 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetGui.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathIconViewProvider as PathIconViewProvider import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathSimpleCopy.py b/src/Mod/Path/PathScripts/PathSimpleCopy.py index 2901166801..6c6cb5d5a6 100644 --- a/src/Mod/Path/PathScripts/PathSimpleCopy.py +++ b/src/Mod/Path/PathScripts/PathSimpleCopy.py @@ -67,8 +67,7 @@ class CommandPathSimpleCopy: FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.addModule("PathScripts.PathCustom") - FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","' + selection[0].Name + '_SimpleCopy")') - FreeCADGui.doCommand('PathScripts.PathCustom.ObjectCustom(obj)') + FreeCADGui.doCommand('obj = PathScripts.PathCustom.Create("' + selection[0].Name + '_SimpleCopy")') FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') FreeCADGui.doCommand('obj.Gcode = [c.toGCode() for c in srcpath.Commands]') FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') diff --git a/src/Mod/Path/PathScripts/PathSimulatorGui.py b/src/Mod/Path/PathScripts/PathSimulatorGui.py index 086db6b145..3a01944abc 100644 --- a/src/Mod/Path/PathScripts/PathSimulatorGui.py +++ b/src/Mod/Path/PathScripts/PathSimulatorGui.py @@ -22,6 +22,7 @@ import FreeCAD import Path +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathSlotGui.py b/src/Mod/Path/PathScripts/PathSlotGui.py index 397ec14fdf..518209e562 100644 --- a/src/Mod/Path/PathScripts/PathSlotGui.py +++ b/src/Mod/Path/PathScripts/PathSlotGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathSlot as PathSlot import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index 038a670c52..25a5cea8c7 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathSurface as PathSurface import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/PathThreadMillingGui.py b/src/Mod/Path/PathScripts/PathThreadMillingGui.py index fab21692a7..885f129a6b 100644 --- a/src/Mod/Path/PathScripts/PathThreadMillingGui.py +++ b/src/Mod/Path/PathScripts/PathThreadMillingGui.py @@ -23,6 +23,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathThreadMilling as PathThreadMilling import PathScripts.PathGui as PathGui diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 5ffe7503dd..38e8a4ce4e 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -24,6 +24,7 @@ import FreeCAD import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences +import PathScripts.PathPropertyBag as PathPropertyBag import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype import PathScripts.PathUtil as PathUtil import PySide @@ -42,74 +43,73 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Class to deal with and represent a tool bit." -# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -# PathLog.trackModule() +PropertyGroupShape = 'Shape' + +_DebugFindTool = False + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +#PathLog.trackModule() def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) +def _findToolFile(name, containerFile, typ): + PathLog.track(name) + if os.path.exists(name): # absolute reference + return name -ParameterTypeConstraint = { - 'Angle': 'App::PropertyAngle', - 'Distance': 'App::PropertyLength', - 'DistanceX': 'App::PropertyLength', - 'DistanceY': 'App::PropertyLength', - 'Radius': 'App::PropertyLength'} + if containerFile: + rootPath = os.path.dirname(os.path.dirname(containerFile)) + paths = [os.path.join(rootPath, typ)] + else: + paths = [] + paths.extend(PathPreferences.searchPathsTool(typ)) + + def _findFile(path, name): + PathLog.track(path, name) + fullPath = os.path.join(path, name) + if os.path.exists(fullPath): + return (True, fullPath) + for root, ds, fs in os.walk(path): + for d in ds: + found, fullPath = _findFile(d, name) + if found: + return (True, fullPath) + return (False, None) -def _findTool(path, typ, dbg=False): - if os.path.exists(path): # absolute reference - if dbg: - PathLog.debug("Found {} at {}".format(typ, path)) - return path - - def searchFor(pname, fname): - # PathLog.debug("pname: {} fname: {}".format(pname, fname)) - if dbg: - PathLog.debug("Looking for {}".format(pname)) - if fname: - for p in PathPreferences.searchPathsTool(typ): - PathLog.track(p) - f = os.path.join(p, fname) - if dbg: - PathLog.debug(" Checking {}".format(f)) - if os.path.exists(f): - if dbg: - PathLog.debug(" Found {} at {}".format(typ, f)) - return f - if pname and os.path.sep != pname: - PathLog.track(pname) - ppname, pfname = os.path.split(pname) - ffname = os.path.join(pfname, fname) if fname else pfname - return searchFor(ppname, ffname) - return None - - return searchFor(path, '') + for p in paths: + found, path = _findFile(p, name) + if found: + return path + return None -def findShape(path): - ''' - findShape(path) ... search for path, full and partially - in all known shape directories. - ''' - return _findTool(path, 'Shape') +def findToolShape(name, path=None): + '''findToolShape(name, path) ... search for name, if relative path look in path''' + PathLog.track(name, path) + return _findToolFile(name, path, 'Shape') -def findBit(path): - PathLog.track(path) - if path.endswith('.fctb'): - return _findTool(path, 'Bit') - return _findTool("{}.fctb".format(path), 'Bit') +def findToolBit(name, path=None): + '''findToolBit(name, path) ... search for name, if relative path look in path''' + PathLog.track(name, path) + if name.endswith('.fctb'): + return _findToolFile(name, path, 'Bit') + return _findToolFile("{}.fctb".format(name), path, 'Bit') -def findLibrary(path, dbg=False): - if path.endswith('.fctl'): - return _findTool(path, 'Library', dbg) - return _findTool("{}.fctl".format(path), 'Library', dbg) +def findToolLibrary(name, path=None): + '''findToolLibrary(name, path) ... search for name, if relative path look in path''' + PathLog.track(name, path) + if name.endswith('.fctl'): + return _findToolFile(name, path, 'Library') + return _findToolFile("{}.fctl".format(name), path, 'Library') def _findRelativePath(path, typ): + PathLog.track(path, typ) relative = path for p in PathPreferences.searchPathsTool(typ): if path.startswith(p): @@ -132,48 +132,19 @@ def findRelativePathTool(path): def findRelativePathLibrary(path): return _findRelativePath(path, 'Library') - -def updateConstraint(sketch, name, value): - for i, constraint in enumerate(sketch.Constraints): - if constraint.Name.split(';')[0] == name: - constr = None - if constraint.Type in ['DistanceX', 'DistanceY', 'Distance', 'Radius', 'Angle']: - constr = Sketcher.Constraint(constraint.Type, constraint.First, constraint.FirstPos, constraint.Second, constraint.SecondPos, value) - else: - print(constraint.Name, constraint.Type) - - if constr is not None: - if not PathGeom.isRoughly(constraint.Value, value.Value): - PathLog.track(name, constraint.Type, - 'update', i, "(%.2f -> %.2f)" - % (constraint.Value, value.Value)) - sketch.delConstraint(i) - sketch.recompute() - n = sketch.addConstraint(constr) - sketch.renameConstraint(n, constraint.Name) - else: - PathLog.track(name, constraint.Type, 'unchanged') - break - - -PropertyGroupBit = 'Bit' -PropertyGroupAttribute = 'Attribute' - - class ToolBit(object): - def __init__(self, obj, shapeFile): - PathLog.track(obj.Label, shapeFile) + def __init__(self, obj, shapeFile, path=None): + PathLog.track(obj.Label, shapeFile, path) self.obj = obj - obj.addProperty('App::PropertyFile', 'BitShape', 'Base', - translate('PathToolBit', 'Shape for bit shape')) - obj.addProperty('App::PropertyLink', 'BitBody', 'Base', - translate('PathToolBit', - 'The parametrized body representing the tool bit')) - obj.addProperty('App::PropertyFile', 'File', 'Base', - translate('PathToolBit', 'The file of the tool')) - obj.addProperty('App::PropertyString', 'ShapeName', 'Base', - translate('PathToolBit', 'The name of the shape file')) + obj.addProperty('App::PropertyFile', 'BitShape', 'Base', translate('PathToolBit', 'Shape for bit shape')) + obj.addProperty('App::PropertyLink', 'BitBody', 'Base', translate('PathToolBit', 'The parametrized body representing the tool bit')) + obj.addProperty('App::PropertyFile', 'File', 'Base', translate('PathToolBit', 'The file of the tool')) + obj.addProperty('App::PropertyString', 'ShapeName', 'Base', translate('PathToolBit', 'The name of the shape file')) + obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', translate('PathToolBit', 'List of all properties inherited from the bit')) + + if path: + obj.File = path if shapeFile is None: obj.BitShape = 'endmill.fcstd' self._setupBitShape(obj) @@ -193,12 +164,6 @@ class ToolBit(object): break return None - def propertyNamesBit(self, obj): - return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupBit] - - def propertyNamesAttribute(self, obj): - return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupAttribute] - def onDocumentRestored(self, obj): # when files are shared it is essential to be able to change/set the shape file, # otherwise the file is hard to use @@ -206,19 +171,39 @@ class ToolBit(object): obj.setEditorMode('BitBody', 2) obj.setEditorMode('File', 1) obj.setEditorMode('Shape', 2) + if not hasattr(obj, 'BitPropertyNames'): + obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', translate('PathToolBit', 'List of all properties inherited from the bit')) + propNames = [] + for prop in obj.PropertiesList: + if obj.getGroupOfProperty(prop) == 'Bit': + val = obj.getPropertyByName(prop) + typ = obj.getTypeIdOfProperty(prop) + dsc = obj.getDocumentationOfProperty(prop) - for prop in self.propertyNamesBit(obj): - obj.setEditorMode(prop, 1) - # I currently don't see why these need to be read-only - # for prop in self.propertyNamesAttribute(obj): - # obj.setEditorMode(prop, 1) + obj.removeProperty(prop) + obj.addProperty(typ, prop, PropertyGroupShape, dsc) + + PathUtil.setProperty(obj, prop, val) + propNames.append(prop) + elif obj.getGroupOfProperty(prop) == 'Attribute': + propNames.append(prop) + obj.BitPropertyNames = propNames + obj.setEditorMode('BitPropertyNames', 2) + + for prop in obj.BitPropertyNames: + if obj.getGroupOfProperty(prop) == PropertyGroupShape: + # properties in the Shape group can only be modified while the actual + # shape is loaded, so we have to disable direct property editing + obj.setEditorMode(prop, 1) + else: + # all other custom properties can and should be edited directly in the + # property editor widget, not much value in re-implementing that + obj.setEditorMode(prop, 0) def onChanged(self, obj, prop): PathLog.track(obj.Label, prop) if prop == 'BitShape' and 'Restore' not in obj.State: self._setupBitShape(obj) - # elif obj.getGroupOfProperty(prop) == PropertyGroupBit: - # self._updateBitShape(obj, [prop]) def onDelete(self, obj, arg2=None): PathLog.track(obj.Label) @@ -227,12 +212,19 @@ class ToolBit(object): def _updateBitShape(self, obj, properties=None): if obj.BitBody is not None: - if not properties: - properties = self.propertyNamesBit(obj) - for prop in properties: - for sketch in [o for o in obj.BitBody.Group if o.TypeId == 'Sketcher::SketchObject']: - PathLog.track(obj.Label, sketch.Label, prop) - updateConstraint(sketch, prop, obj.getPropertyByName(prop)) + for attributes in [o for o in obj.BitBody.Group if hasattr(o, 'Proxy') and hasattr(o.Proxy, 'getCustomProperties')]: + for prop in attributes.Proxy.getCustomProperties(): + # the property might not exist in our local object (new attribute in shape) + # for such attributes we just keep the default + if hasattr(obj, prop): + setattr(attributes, prop, obj.getPropertyByName(prop)) + else: + # if the template shape has a new attribute defined we should add that + # to the local object + self._setupProperty(obj, prop, attributes) + propNames = obj.BitPropertyNames + propNames.append(prop) + obj.BitPropertyNames = propNames self._copyBitShape(obj) def _copyBitShape(self, obj): @@ -243,6 +235,7 @@ class ToolBit(object): obj.Shape = Part.Shape() def _loadBitBody(self, obj, path=None): + PathLog.track(obj.Label, path) p = path if path else obj.BitShape docOpened = False doc = None @@ -251,12 +244,15 @@ class ToolBit(object): doc = FreeCAD.getDocument(d) break if doc is None: - p = findShape(p) + p = findToolShape(p, path if path else obj.File) if not path and p != obj.BitShape: obj.BitShape = p + PathLog.debug("ToolBit {} using shape file: {}".format(obj.Label, p)) doc = FreeCAD.openDocument(p, True) obj.ShapeName = doc.Name docOpened = True + else: + PathLog.debug("ToolBit {} already open: {}".format(obj.Label, doc)) return (doc, docOpened) def _removeBitBody(self, obj): @@ -269,7 +265,7 @@ class ToolBit(object): PathLog.track(obj.Label) self._removeBitBody(obj) self._copyBitShape(obj) - for prop in self.propertyNamesBit(obj): + for prop in obj.BitPropertyNames: obj.removeProperty(prop) def loadBitBody(self, obj, force=False): @@ -287,6 +283,20 @@ class ToolBit(object): def unloadBitBody(self, obj): self._removeBitBody(obj) + def _setupProperty(self, obj, prop, orig): + # extract property parameters and values so it can be copied + val = orig.getPropertyByName(prop) + typ = orig.getTypeIdOfProperty(prop) + grp = orig.getGroupOfProperty(prop) + dsc = orig.getDocumentationOfProperty(prop) + + obj.addProperty(typ, prop, grp, dsc) + if 'App::PropertyEnumeration' == typ: + setattr(obj, prop, orig.getEnumerationsOfProperty(prop)) + + obj.setEditorMode(prop, 1) + PathUtil.setProperty(obj, prop, val) + def _setupBitShape(self, obj, path=None): PathLog.track(obj.Label) @@ -296,6 +306,8 @@ class ToolBit(object): obj.Label = doc.RootObjects[0].Label self._deleteBitSetup(obj) bitBody = obj.Document.copyObject(doc.RootObjects[0], True) + + docName = doc.Name if docOpened: FreeCAD.setActiveDocument(activeDoc.Name) FreeCAD.closeDocument(doc.Name) @@ -303,29 +315,44 @@ class ToolBit(object): if bitBody.ViewObject: bitBody.ViewObject.Visibility = False - for sketch in [o for o in bitBody.Group if o.TypeId == 'Sketcher::SketchObject']: - for constraint in [c for c in sketch.Constraints if c.Name != '']: - typ = ParameterTypeConstraint.get(constraint.Type) - PathLog.track(constraint, typ) - if typ is not None: - parts = [p.strip() for p in constraint.Name.split(';')] - prop = parts[0] - desc = '' - if len(parts) > 1: - desc = parts[1] - obj.addProperty(typ, prop, PropertyGroupBit, desc) - obj.setEditorMode(prop, 1) - value = constraint.Value - if constraint.Type == 'Angle': - value = value * 180 / math.pi - PathUtil.setProperty(obj, prop, value) + PathLog.debug("bitBody.{} ({}): {}".format(bitBody.Label, bitBody.Name, type(bitBody))) + + propNames = [] + for attributes in [o for o in bitBody.Group if PathPropertyBag.IsPropertyBag(o)]: + PathLog.debug("Process properties from {}".format(attributes.Label)) + for prop in attributes.Proxy.getCustomProperties(): + self._setupProperty(obj, prop, attributes) + propNames.append(prop) + if not propNames: + PathLog.error(translate('PathToolBit', 'Did not find a PropertyBag in {} - not a ToolBit shape?').format(docName)) + # has to happen last because it could trigger op.execute evaluations + obj.BitPropertyNames = propNames obj.BitBody = bitBody self._copyBitShape(obj) + def toolShapeProperties(self, obj): + '''toolShapeProperties(obj) ... return all properties defining it's shape''' + return sorted([prop for prop in obj.BitPropertyNames if obj.getGroupOfProperty(prop) == PropertyGroupShape]) + + def toolAdditionalProperties(self, obj): + '''toolShapeProperties(obj) ... return all properties unrelated to it's shape''' + return sorted([prop for prop in obj.BitPropertyNames if obj.getGroupOfProperty(prop) != PropertyGroupShape]) + + def toolGroupsAndProperties(self, obj, includeShape=True): + '''toolGroupsAndProperties(obj) ... returns a dictionary of group names with a list of property names.''' + category = {} + for prop in obj.BitPropertyNames: + group = obj.getGroupOfProperty(prop) + if includeShape or group != PropertyGroupShape: + properties = category.get(group, []) + properties.append(prop) + category[group] = properties + return category + def getBitThumbnail(self, obj): if obj.BitShape: - path = findShape(obj.BitShape) + path = findToolShape(obj.BitShape) if path: with open(path, 'rb') as fd: try: @@ -347,8 +374,7 @@ class ToolBit(object): obj.File = path return True except (OSError, IOError) as e: - PathLog.error("Could not save tool {} to {} ({})".format( - obj.Label, path, e)) + PathLog.error("Could not save tool {} to {} ({})".format(obj.Label, path, e)) raise def templateAttrs(self, obj): @@ -360,16 +386,10 @@ class ToolBit(object): else: attrs['shape'] = findRelativePathShape(obj.BitShape) params = {} - for name in self.propertyNamesBit(obj): + for name in obj.BitPropertyNames: params[name] = PathUtil.getPropertyValueString(obj, name) attrs['parameter'] = params params = {} - for name in self.propertyNamesAttribute(obj): - if name == "UserAttributes": - for key, value in obj.UserAttributes.items(): - params[key] = value - else: - params[name] = PathUtil.getPropertyValueString(obj, name) attrs['attribute'] = params return attrs @@ -380,73 +400,33 @@ def Declaration(path): return json.load(fp) -class AttributePrototype(PathSetupSheetOpPrototype.OpPrototype): - - def __init__(self): - PathSetupSheetOpPrototype.OpPrototype.__init__(self, 'ToolBitAttribute') - self.addProperty('App::PropertyEnumeration', 'Material', - PropertyGroupAttribute, - translate('PathToolBit', 'Tool bit material')) - self.Material = ['Carbide', 'CastAlloy', 'Ceramics', 'Diamond', - 'HighCarbonToolSteel', 'HighSpeedSteel', 'Sialon'] - self.addProperty('App::PropertyDistance', 'LengthOffset', - PropertyGroupAttribute, translate('PathToolBit', - 'Length offset in Z direction')) - self.addProperty('App::PropertyInteger', 'Flutes', - PropertyGroupAttribute, translate('PathToolBit', - 'The number of flutes')) - self.addProperty('App::PropertyDistance', 'ChipLoad', - PropertyGroupAttribute, translate('PathToolBit', - 'Chipload as per manufacturer')) - self.addProperty('App::PropertyMap', 'UserAttributes', - PropertyGroupAttribute, translate('PathToolBit', - 'User Defined Values')) - self.addProperty('App::PropertyBool', 'SpindlePower', - PropertyGroupAttribute, translate('PathToolBit', - 'Whether Spindle Power should be allowed')) - - class ToolBitFactory(object): - def CreateFromAttrs(self, attrs, name='ToolBit'): - PathLog.debug(attrs) - obj = Factory.Create(name, attrs['shape']) + def CreateFromAttrs(self, attrs, name='ToolBit', path=None): + PathLog.track(attrs, path) + obj = Factory.Create(name, attrs['shape'], path) obj.Label = attrs['name'] params = attrs['parameter'] for prop in params: PathUtil.setProperty(obj, prop, params[prop]) obj.Proxy._updateBitShape(obj) obj.Proxy.unloadBitBody(obj) - params = attrs['attribute'] - proto = AttributePrototype() - uservals = {} - for pname in params: - try: - prop = proto.getProperty(pname) - prop.setupProperty(obj, pname, PropertyGroupAttribute, prop.valueFromString(params[pname])) - except Exception: - prop = proto.getProperty("UserAttributes") - uservals.update({pname: params[pname]}) - - if len(uservals.items()) > 0: - prop.setupProperty(obj, "UserAttributes", - PropertyGroupAttribute, uservals) - return obj def CreateFrom(self, path, name='ToolBit'): + PathLog.track(name, path) try: data = Declaration(path) - bit = Factory.CreateFromAttrs(data, name) - bit.File = path + bit = Factory.CreateFromAttrs(data, name, path) return bit except (OSError, IOError) as e: PathLog.error("%s not a valid tool file (%s)" % (path, e)) raise - def Create(self, name='ToolBit', shapeFile=None): + def Create(self, name='ToolBit', shapeFile=None, path=None): + PathLog.track(name, shapeFile, path) obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', name) - obj.Proxy = ToolBit(obj, shapeFile) + obj.Proxy = ToolBit(obj, shapeFile, path) return obj diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 3488bcc95d..3028bd7bb1 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -20,12 +20,14 @@ # * * # *************************************************************************** +import FreeCAD import FreeCADGui import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathSetupSheetGui as PathSetupSheetGui +import PathScripts.PathPropertyEditor as PathPropertyEditor import PathScripts.PathToolBit as PathToolBit +import PathScripts.PathUtil as PathUtil import os import re @@ -39,6 +41,31 @@ PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) +class _Delegate(QtGui.QStyledItemDelegate): + '''Handles the creation of an appropriate editing widget for a given property.''' + ObjectRole = QtCore.Qt.UserRole + 1 + PropertyRole = QtCore.Qt.UserRole + 2 + EditorRole = QtCore.Qt.UserRole + 3 + + def createEditor(self, parent, option, index): + editor = index.data(self.EditorRole) + if editor is None: + obj = index.data(self.ObjectRole) + prp = index.data(self.PropertyRole) + editor = PathPropertyEditor.Editor(obj, prp) + index.model().setData(index, editor, self.EditorRole) + return editor.widget(parent) + + def setEditorData(self, widget, index): + # called to update the widget with the current data + index.data(self.EditorRole).setEditorData(widget) + + def setModelData(self, widget, model, index): + # called to update the model with the data from the widget + editor = index.data(self.EditorRole) + editor.setModelData(widget) + index.model().setData(index, PathUtil.getPropertyValueString(editor.obj, editor.prop), QtCore.Qt.DisplayRole) + class ToolBitEditor(object): '''UI and controller for editing a ToolBit. @@ -84,32 +111,35 @@ class ToolBitEditor(object): layout = self.form.bitParams.layout() ui = FreeCADGui.UiLoader() - nr = 0 # for all properties either assign them to existing labels and editors # or create additional ones for them if not enough have already been # created. - for name in tool.PropertiesList: - if tool.getGroupOfProperty(name) == PathToolBit.PropertyGroupBit: - if nr < len(self.widgets): - PathLog.debug("re-use row: {} [{}]".format(nr, name)) - label, qsb, editor = self.widgets[nr] - label.setText(labelText(name)) - editor.attachTo(tool, name) - label.show() - qsb.show() - else: - qsb = ui.createWidget('Gui::QuantitySpinBox') - editor = PathGui.QuantitySpinBox(qsb, tool, name) - label = QtGui.QLabel(labelText(name)) - self.widgets.append((label, qsb, editor)) - PathLog.debug("create row: {} [{}]".format(nr, name)) - if nr >= layout.rowCount(): - layout.addRow(label, qsb) - nr = nr + 1 + usedRows = 0 + for nr, name in enumerate(tool.Proxy.toolShapeProperties(tool)): + if nr < len(self.widgets): + PathLog.debug("re-use row: {} [{}]".format(nr, name)) + label, qsb, editor = self.widgets[nr] + label.setText(labelText(name)) + editor.attachTo(tool, name) + label.show() + qsb.show() + else: + qsb = ui.createWidget('Gui::QuantitySpinBox') + editor = PathGui.QuantitySpinBox(qsb, tool, name) + label = QtGui.QLabel(labelText(name)) + self.widgets.append((label, qsb, editor)) + PathLog.debug("create row: {} [{}] {}".format(nr, name, type(qsb))) + if hasattr(qsb, 'editingFinished'): + qsb.editingFinished.connect(self.updateTool) + + if nr >= layout.rowCount(): + layout.addRow(label, qsb) + usedRows = usedRows + 1 # hide all rows which aren't being used - for i in range(nr, len(self.widgets)): + PathLog.track(usedRows, len(self.widgets)) + for i in range(usedRows, len(self.widgets)): label, qsb, editor = self.widgets[i] label.hide() qsb.hide() @@ -124,98 +154,48 @@ class ToolBitEditor(object): def setupAttributes(self, tool): PathLog.track() - self.proto = PathToolBit.AttributePrototype() - self.props = sorted(self.proto.properties) - self.delegate = PathSetupSheetGui.Delegate(self.form) - self.model = QtGui.QStandardItemModel(len(self.props)-1, 3, self.form) - self.model.setHorizontalHeaderLabels(['Set', 'Property', 'Value']) - for i, name in enumerate(self.props): - PathLog.debug("propname: %s " % name) + setup = True + if not hasattr(self, 'delegate'): + self.delegate = _Delegate(self.form.attrTree) + self.model = QtGui.QStandardItemModel(self.form.attrTree) + self.model.setHorizontalHeaderLabels(['Property', 'Value']) + else: + self.model.removeRows(0, self.model.rowCount()) + setup = False - prop = self.proto.getProperty(name) - isset = hasattr(tool, name) + attributes = tool.Proxy.toolGroupsAndProperties(tool, False) + for name in attributes: + group = QtGui.QStandardItem() + group.setData(name, QtCore.Qt.EditRole) + group.setEditable(False) + for prop in attributes[name]: + label = QtGui.QStandardItem() + label.setData(prop, QtCore.Qt.EditRole) + label.setEditable(False) - if isset: - prop.setValue(getattr(tool, name)) + value = QtGui.QStandardItem() + value.setData(PathUtil.getPropertyValueString(tool, prop), QtCore.Qt.DisplayRole) + value.setData(tool, _Delegate.ObjectRole) + value.setData(prop, _Delegate.PropertyRole) - if name == "UserAttributes": - continue + group.appendRow([label, value]) + self.model.appendRow(group) - else: - self.model.setData(self.model.index(i, 0), isset, - QtCore.Qt.EditRole) - self.model.setData(self.model.index(i, 1), name, - QtCore.Qt.EditRole) - self.model.setData(self.model.index(i, 2), prop, - PathSetupSheetGui.Delegate.PropertyRole) - self.model.setData(self.model.index(i, 2), - prop.displayString(), - QtCore.Qt.DisplayRole) - - self.model.item(i, 0).setCheckable(True) - self.model.item(i, 0).setText('') - self.model.item(i, 1).setEditable(False) - self.model.item(i, 1).setToolTip(prop.info) - self.model.item(i, 2).setToolTip(prop.info) - - if isset: - self.model.item(i, 0).setCheckState(QtCore.Qt.Checked) - else: - self.model.item(i, 0).setCheckState(QtCore.Qt.Unchecked) - self.model.item(i, 1).setEnabled(False) - self.model.item(i, 2).setEnabled(False) - - if hasattr(tool, "UserAttributes"): - for key, value in tool.UserAttributes.items(): - PathLog.debug(key, value) - c1 = QtGui.QStandardItem() - c1.setCheckable(False) - c1.setEditable(False) - c1.setCheckState(QtCore.Qt.CheckState.Checked) - - c1.setText('') - c2 = QtGui.QStandardItem(key) - c2.setEditable(False) - c3 = QtGui.QStandardItem(value) - c3.setEditable(False) - - self.model.appendRow([c1, c2, c3]) - - self.form.attrTable.setModel(self.model) - self.form.attrTable.setItemDelegateForColumn(2, self.delegate) - self.form.attrTable.resizeColumnsToContents() - self.form.attrTable.verticalHeader().hide() - - self.model.dataChanged.connect(self.updateData) - - def updateData(self, topLeft, bottomRight): - PathLog.track() - if 0 == topLeft.column(): - isset = self.model.item(topLeft.row(), - 0).checkState() == QtCore.Qt.Checked - self.model.item(topLeft.row(), 1).setEnabled(isset) - self.model.item(topLeft.row(), 2).setEnabled(isset) + if setup: + self.form.attrTree.setModel(self.model) + self.form.attrTree.setItemDelegateForColumn(1, self.delegate) + self.form.attrTree.expandAll() + self.form.attrTree.resizeColumnToContents(0) + self.form.attrTree.resizeColumnToContents(1) + #self.form.attrTree.collapseAll() def accept(self): PathLog.track() self.refresh() self.tool.Proxy.unloadBitBody(self.tool) - # get the attributes - for i, name in enumerate(self.props): - PathLog.debug('in accept: {}'.format(name)) - prop = self.proto.getProperty(name) - if self.model.item(i, 0) is not None: - enabled = self.model.item(i, 0).checkState() == QtCore.Qt.Checked - if enabled and not prop.getValue() is None: - prop.setupProperty(self.tool, name, - PathToolBit.PropertyGroupAttribute, - prop.getValue()) - elif hasattr(self.tool, name): - self.tool.removeProperty(name) - def reject(self): PathLog.track() self.tool.Proxy.unloadBitBody(self.tool) @@ -228,27 +208,45 @@ class ToolBitEditor(object): for lbl, qsb, editor in self.widgets: editor.updateSpinBox() + def _updateBitShape(self, shapePath): + # Only need to go through this exercise if the shape actually changed. + if self.tool.BitShape != shapePath: + # Before setting a new bitshape we need to make sure that none of + # editors fires an event and tries to access its old property, which + # might not exist anymore. + for lbl, qsb, editor in self.widgets: + editor.attachTo(self.tool, 'File') + self.tool.BitShape = shapePath + self.setupTool(self.tool) + self.form.toolName.setText(self.tool.Label) + if self.tool.BitBody and self.tool.BitBody.ViewObject: + if not self.tool.BitBody.ViewObject.Visibility: + self.tool.BitBody.ViewObject.Visibility = True + self.setupAttributes(self.tool) + return True + return False + def updateShape(self): PathLog.track() shapePath = str(self.form.shapePath.text()) # Only need to go through this exercise if the shape actually changed. - if self.tool.BitShape != shapePath: - self.tool.BitShape = shapePath - self.setupTool(self.tool) - self.form.toolName.setText(self.tool.Label) - + if self._updateBitShape(shapePath): for lbl, qsb, editor in self.widgets: editor.updateSpinBox() def updateTool(self): PathLog.track() - self.tool.Label = str(self.form.toolName.text()) - self.tool.BitShape = str(self.form.shapePath.text()) + + label = str(self.form.toolName.text()) + shape = str(self.form.shapePath.text()) + if self.tool.Label != label: + self.tool.Label = label + self._updateBitShape(shape) for lbl, qsb, editor in self.widgets: editor.updateProperty() - # self.tool.Proxy._updateBitShape(self.tool) + self.tool.Proxy._updateBitShape(self.tool) def refresh(self): PathLog.track() @@ -262,10 +260,7 @@ class ToolBitEditor(object): path = self.tool.BitShape if not path: path = PathPreferences.lastPathToolShape() - foo = QtGui.QFileDialog.getOpenFileName(self.form, - "Path - Tool Shape", - path, - "*.fcstd") + foo = QtGui.QFileDialog.getOpenFileName(self.form, "Path - Tool Shape", path, "*.fcstd") if foo and foo[0]: PathPreferences.setLastPathToolShape(os.path.dirname(foo[0])) self.form.shapePath.setText(foo[0]) diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index 1fe1df5370..1bf56aea68 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -170,13 +170,13 @@ class TaskPanel: class ToolBitGuiFactory(PathToolBit.ToolBitFactory): - def Create(self, name='ToolBit', shapeFile=None): + def Create(self, name='ToolBit', shapeFile=None, path=None): '''Create(name = 'ToolBit') ... creates a new tool bit. It is assumed the tool will be edited immediately so the internal bit body is still attached.''' - FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', - 'Create ToolBit')) - tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile) + PathLog.track(name, shapeFile, path) + FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Create ToolBit')) + tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile, path) PathIconViewProvider.Attach(tool.ViewObject, name) FreeCAD.ActiveDocument.commitTransaction() return tool @@ -247,7 +247,8 @@ def GetToolShapeFile(parent=None): location, '*.fcstd') if fname and fname[0]: if fname != location: - PathPreferences.setLastPathToolShape(location) + newloc = os.path.dirname(fname[0]) + PathPreferences.setLastPathToolShape(newloc) return fname[0] else: return None diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 33e8fb8e39..0abc2a6ae4 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -24,23 +24,25 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathToolBit as PathToolBit -import PathScripts.PathToolBitGui as PathToolBitGui import PathScripts.PathToolBitEdit as PathToolBitEdit +import PathScripts.PathToolBitGui as PathToolBitGui import PathScripts.PathToolControllerGui as PathToolControllerGui import PathScripts.PathUtilsGui as PathUtilsGui -from PySide import QtCore, QtGui import PySide +import glob import json import os -import glob -import uuid as UUID -from functools import partial import shutil +import uuid as UUID -# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +from functools import partial + + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # PathLog.trackModule(PathLog.thisModule()) _UuidRole = PySide.QtCore.Qt.UserRole + 1 @@ -50,6 +52,75 @@ _PathRole = PySide.QtCore.Qt.UserRole + 2 def translate(context, text, disambig=None): return PySide.QtCore.QCoreApplication.translate(context, text, disambig) +def checkWorkingDir(): + # users shouldn't use the example toolbits and libraries. + # working directory should be writable + PathLog.track() + + workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary()) + defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath()) + + PathLog.debug('workingdir: {} defaultdir: {}'.format(workingdir, defaultdir)) + + dirOK = lambda : workingdir != defaultdir and (os.access(workingdir, os.W_OK)) + + if dirOK(): + return True + + qm = PySide.QtGui.QMessageBox + ret = qm.question(None,'', "Toolbit working directory not set up. Do that now?", qm.Yes | qm.No) + + if ret == qm.No: + return False + + msg = translate("Path", "Choose a writable location for your toolbits", None) + while not dirOK(): + workingdir = PySide.QtGui.QFileDialog.getExistingDirectory(None, msg, + PathPreferences.filePath()) + + if workingdir[-8:] == os.path.sep + 'Library': + workingdir = workingdir[:-8] # trim off trailing /Library if user chose it + + PathPreferences.setLastPathToolLibrary("{}{}Library".format(workingdir, os.path.sep)) + PathPreferences.setLastPathToolBit("{}{}Bit".format(workingdir, os.path.sep)) + PathLog.debug('setting workingdir to: {}'.format(workingdir)) + + subdirlist = ['Bit', 'Library', 'Shape'] + mode = 0o777 + for dir in subdirlist.copy(): + subdir = "{}{}{}".format(workingdir, os.path.sep, dir) + if os.path.exists(subdir): + subdirlist.remove(dir) + + if len(subdirlist) >= 1: + needed = ', '.join([str(d) for d in subdirlist]) + qm = PySide.QtGui.QMessageBox + ret = qm.question(None,'', "Toolbit Working directory {} needs these sudirectories:\n {} \n Create them?".format(workingdir, needed), qm.Yes | qm.No) + + if ret == qm.No: + return False + else: + for dir in subdirlist: + subdir = "{}{}{}".format(workingdir, os.path.sep, dir) + os.mkdir(subdir, mode) + if dir != 'Shape': + qm = PySide.QtGui.QMessageBox + ret = qm.question(None,'', "Copy example files to new {} directory?".format(dir), qm.Yes | qm.No) + if ret == qm.Yes: + src="{}{}{}".format(defaultdir, os.path.sep, dir) + src_files = os.listdir(src) + for file_name in src_files: + full_file_name = os.path.join(src, file_name) + if os.path.isfile(full_file_name): + shutil.copy(full_file_name, subdir) + + + # if no library is set, choose the first one in the Library directory + if PathPreferences.lastFileToolLibrary() is None: + libFiles = [f for f in glob.glob(PathPreferences.lastPathToolLibrary() + os.path.sep + '*.fctl')] + PathPreferences.setLastFileToolLibrary(libFiles[0]) + + return True class _TableView(PySide.QtGui.QTableView): '''Subclass of QTableView to support rearrange and copying of ToolBits''' @@ -141,7 +212,7 @@ class ModelFactory(object): for toolBit in library['tools']: try: nr = toolBit['nr'] - bit = PathToolBit.findBit(toolBit['path']) + bit = PathToolBit.findToolBit(toolBit['path'], path) if bit: PathLog.track(bit) tool = PathToolBit.Declaration(bit) @@ -203,15 +274,15 @@ class ModelFactory(object): path = PathPreferences.lastPathToolLibrary() if os.path.isdir(path): # opening all tables in a directory - libFiles = [f for f in glob.glob(path + '/*.fctl')] + libFiles = [f for f in glob.glob(path + os.path.sep + '*.fctl')] libFiles.sort() for libFile in libFiles: loc, fnlong = os.path.split(libFile) fn, ext = os.path.splitext(fnlong) - libItem = QtGui.QStandardItem(fn) + libItem = PySide.QtGui.QStandardItem(fn) libItem.setToolTip(loc) libItem.setData(libFile, _PathRole) - libItem.setIcon(QtGui.QPixmap(':/icons/Path_ToolTable.svg')) + libItem.setIcon(PySide.QtGui.QPixmap(':/icons/Path_ToolTable.svg')) model.appendRow(libItem) PathLog.debug('model rows: {}'.format(model.rowCount())) @@ -241,6 +312,7 @@ class ToolBitSelector(object): '''Controller for displaying a library and creating ToolControllers''' def __init__(self): + checkWorkingDir() self.form = FreeCADGui.PySideUic.loadUi(':/panels/ToolBitSelector.ui') self.factory = ModelFactory() self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames())) @@ -250,20 +322,20 @@ class ToolBitSelector(object): def columnNames(self): return ['#', 'Tool'] - def curLib(self): + def currentLibrary(self, shortNameOnly): libfile = PathPreferences.lastFileToolLibrary() if libfile is None or libfile == "": return "" - else: - libfile = os.path.split(PathPreferences.lastFileToolLibrary())[1] - libfile = os.path.splitext(libfile)[0] + elif shortNameOnly: + return os.path.splitext(os.path.basename(libfile))[0] return libfile def loadData(self): PathLog.track() self.toolModel.clear() self.toolModel.setHorizontalHeaderLabels(self.columnNames()) - self.form.lblLibrary.setText(self.curLib()) + self.form.lblLibrary.setText(self.currentLibrary(True)) + self.form.lblLibrary.setToolTip(self.currentLibrary(False)) self.factory.libraryOpen(self.toolModel) self.toolModel.takeColumn(3) self.toolModel.takeColumn(2) @@ -271,7 +343,6 @@ class ToolBitSelector(object): def setupUI(self): PathLog.track() self.loadData() - self.form.tools.setModel(self.toolModel) self.form.tools.selectionModel().selectionChanged.connect(self.enableButtons) self.form.tools.doubleClicked.connect(partial(self.selectedOrAllToolControllers)) @@ -340,7 +411,7 @@ class ToolBitSelector(object): def open(self, path=None): ''' load library stored in path and bring up ui''' - docs = FreeCADGui.getMainWindow().findChildren(QtGui.QDockWidget) + docs = FreeCADGui.getMainWindow().findChildren(PySide.QtGui.QDockWidget) for doc in docs: if doc.objectName() == "ToolSelector": if doc.isVisible(): @@ -351,7 +422,7 @@ class ToolBitSelector(object): return mw = FreeCADGui.getMainWindow() - mw.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.form, + mw.addDockWidget(PySide.QtCore.Qt.RightDockWidgetArea, self.form, PySide.QtCore.Qt.Orientation.Vertical) @@ -361,9 +432,7 @@ class ToolBitLibrary(object): def __init__(self): PathLog.track() - if not self.checkWorkingDir(): - return - + checkWorkingDir() self.factory = ModelFactory() self.temptool = None self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames())) @@ -375,55 +444,6 @@ class ToolBitLibrary(object): self.setupUI() self.title = self.form.windowTitle() - def checkWorkingDir(self): - # users shouldn't use the example toolbits and libraries. - # working directory should be writable - PathLog.track() - - workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary()) - defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath()) - - dirOK = lambda : workingdir != defaultdir and (os.access(workingdir, os.W_OK)) - - if dirOK(): - return True - - qm = PySide.QtGui.QMessageBox - ret = qm.question(None,'', "Toolbit working directory not set up. Do that now?", qm.Yes | qm.No) - - if ret == qm.No: - return False - - msg = translate("Path", "Choose a writable location for your toolbits", None) - while not dirOK(): - workingdir = PySide.QtGui.QFileDialog.getExistingDirectory(None, msg, - PathPreferences.filePath()) - - PathPreferences.setLastPathToolLibrary("{}/Library".format(workingdir)) - - subdirlist = ['Bit', 'Library', 'Shape'] - mode = 0o777 - for dir in subdirlist: - subdir = "{}/{}".format(workingdir, dir) - if not os.path.exists(subdir): - qm = PySide.QtGui.QMessageBox - ret = qm.question(None,'', "Toolbit Working directory {} should contain a '{}' subdirectory. Create it?".format(workingdir, dir), qm.Yes | qm.No) - - if ret == qm.Yes: - os.mkdir(subdir, mode) - qm = PySide.QtGui.QMessageBox - ret = qm.question(None,'', "Copy example files to new {} directory?".format(dir), qm.Yes | qm.No) - if ret == qm.Yes: - src="{}/{}".format(defaultdir, dir) - src_files = os.listdir(src) - for file_name in src_files: - full_file_name = os.path.join(src, file_name) - if os.path.isfile(full_file_name): - shutil.copy(full_file_name, subdir) - - return True - - def toolBitNew(self): PathLog.track() @@ -439,7 +459,7 @@ class ToolBitLibrary(object): # Parse out the name of the file and write the structure loc, fil = os.path.split(filename) fname = os.path.splitext(fil)[0] - fullpath = "{}/{}.fctb".format(loc, fname) + fullpath = "{}{}{}.fctb".format(loc, os.path.sep, fname) PathLog.debug(fullpath) self.temptool = PathToolBit.ToolBitFactory().Create(name=fname) @@ -464,7 +484,7 @@ class ToolBitLibrary(object): loc, fil = os.path.split(f) fname = os.path.splitext(fil)[0] - fullpath = "{}/{}.fctb".format(loc, fname) + fullpath = "{}{}{}.fctb".format(loc, os.path.sep, fname) self.factory.newTool(self.toolModel, fullpath) @@ -557,8 +577,8 @@ class ToolBitLibrary(object): self.temptool = PathToolBit.ToolBitFactory().CreateFrom(tbpath, 'temptool') self.editor = PathToolBitEdit.ToolBitEditor(self.temptool, self.form.toolTableGroup, loadBitBody=False) - QBtn = QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel - buttonBox = QtGui.QDialogButtonBox(QBtn) + QBtn = PySide.QtGui.QDialogButtonBox.Ok | PySide.QtGui.QDialogButtonBox.Cancel + buttonBox = PySide.QtGui.QDialogButtonBox(QBtn) buttonBox.accepted.connect(self.accept) buttonBox.rejected.connect(self.reject) @@ -604,8 +624,9 @@ class ToolBitLibrary(object): else: tools.append({'nr': toolNr, 'path': PathToolBit.findRelativePathTool(toolPath)}) - with open(self.path, 'w') as fp: - json.dump(library, fp, sort_keys=True, indent=2) + if self.path is not None: + with open(self.path, 'w') as fp: + json.dump(library, fp, sort_keys=True, indent=2) def libraryOk(self): self.librarySave() @@ -652,7 +673,7 @@ class ToolBitLibrary(object): if curIndex: sm = self.form.TableList.selectionModel() - sm.select(curIndex, QtCore.QItemSelectionModel.Select) + sm.select(curIndex, PySide.QtCore.QItemSelectionModel.Select) self.toolTableView.setUpdatesEnabled(True) self.form.TableList.setUpdatesEnabled(True) diff --git a/src/Mod/Path/PathScripts/PathToolController.py b/src/Mod/Path/PathScripts/PathToolController.py index 199d5cdf22..3f8405ccd3 100644 --- a/src/Mod/Path/PathScripts/PathToolController.py +++ b/src/Mod/Path/PathScripts/PathToolController.py @@ -250,7 +250,8 @@ def Create(name='TC: Default Tool', tool=None, toolNumber=1, assignViewProvider= if tool.ViewObject: tool.ViewObject.Visibility = False - obj.Tool = tool + if tool: + obj.Tool = tool obj.ToolNumber = toolNumber return obj @@ -260,7 +261,7 @@ def FromTemplate(template, assignViewProvider=True): PathLog.track() name = template.get(ToolControllerTemplate.Name, ToolControllerTemplate.Label) - obj = Create(name, assignViewProvider=True) + obj = Create(name, tool=False, assignViewProvider=True) obj.Proxy.setFromTemplate(obj, template) return obj diff --git a/src/Mod/Path/PathScripts/PathToolControllerGui.py b/src/Mod/Path/PathScripts/PathToolControllerGui.py index 33e735a08d..905888026e 100644 --- a/src/Mod/Path/PathScripts/PathToolControllerGui.py +++ b/src/Mod/Path/PathScripts/PathToolControllerGui.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog diff --git a/src/Mod/Path/PathScripts/PathUtil.py b/src/Mod/Path/PathScripts/PathUtil.py index 40e59a78af..db43b466e5 100644 --- a/src/Mod/Path/PathScripts/PathUtil.py +++ b/src/Mod/Path/PathScripts/PathUtil.py @@ -71,6 +71,11 @@ def getPropertyValueString(obj, prop): def setProperty(obj, prop, value): '''setProperty(obj, prop, value) ... set the property value of obj's property defined by its canonical name.''' o, attr, name = _getProperty(obj, prop) # pylint: disable=unused-variable + if not attr is None and type(value) == str: + if type(attr) == int: + value = int(value, 0) + elif type(attr) == bool: + value = value.lower() in ['true', '1', 'yes', 'ok'] if o and name: setattr(o, name, value) diff --git a/src/Mod/Path/PathScripts/PathUtilsGui.py b/src/Mod/Path/PathScripts/PathUtilsGui.py index da33987017..a5df387eb3 100644 --- a/src/Mod/Path/PathScripts/PathUtilsGui.py +++ b/src/Mod/Path/PathScripts/PathUtilsGui.py @@ -21,6 +21,7 @@ # *************************************************************************** import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathUtils as PathUtils diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index dd713923d6..22c974d108 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -210,11 +210,6 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): QtCore.QT_TRANSLATE_NOOP("PathVcarve", "Additional base objects to be engraved")) obj.setEditorMode('BaseShapes', 2) # hide - if not hasattr(obj, 'BaseObject'): - obj.addProperty("App::PropertyLink", "BaseObject", "Path", - QtCore.QT_TRANSLATE_NOOP("PathVcarve", - "Additional base objects to be engraved")) - obj.setEditorMode('BaseObject', 2) # hide def initOperation(self, obj): '''initOperation(obj) ... create vcarve specific properties.''' @@ -242,7 +237,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): edges.append(_getPartEdge(e, geom)) return edges - def buildPathMedial(self, obj, Faces): + def buildPathMedial(self, obj, faces): '''constructs a medial axis path using openvoronoi''' def insert_many_wires(vd, wires): @@ -271,7 +266,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): VD.clear() voronoiWires = [] - for f in Faces: + for f in faces: vd = Path.Voronoi() insert_many_wires(vd, f.Wires) @@ -311,41 +306,39 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): PathLog.track() if not hasattr(obj.ToolController.Tool, "CuttingEdgeAngle"): - FreeCAD.Console.PrintError( - translate("Path_Vcarve", "VCarve requires an engraving \ - cutter with CuttingEdgeAngle") + "\n") + PathLog.error(translate("Path_Vcarve", "VCarve requires an engraving cutter with CuttingEdgeAngle")) if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0: - FreeCAD.Console.PrintError( - translate("Path_Vcarve", - "Engraver Cutting Edge Angle must be < 180 degrees.") + "\n") + PathLog.error(translate("Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees.")) return + try: - if obj.Base: - PathLog.track() - for base in obj.Base: - faces = [] - for sub in base[1]: - shape = getattr(base[0].Shape, sub) - if isinstance(shape, Part.Face): - faces.append(shape) + faces = [] - modelshape = Part.makeCompound(faces) + for base in obj.BaseShapes: + faces.extend(base.Shape.Faces) - elif len(self.model) == 1 and self.model[0].isDerivedFrom('Sketcher::SketchObject') or \ - self.model[0].isDerivedFrom('Part::Part2DObject'): - PathLog.track() + for base in obj.Base: + for sub in base[1]: + shape = getattr(base[0].Shape, sub) + if isinstance(shape, Part.Face): + faces.append(shape) - modelshape = self.model[0].Shape - self.buildPathMedial(obj, modelshape.Faces) + if not faces: + for model in self.model: + if model.isDerivedFrom('Sketcher::SketchObject') or model.isDerivedFrom('Part::Part2DObject'): + faces.extend(model.Shape.Faces) + + if faces: + self.buildPathMedial(obj, faces) + else: + PathLog.error(translate('PathVcarve', 'The Job Base Object has no engraveable element. Engraving operation will produce no output.')) except Exception as e: - PathLog.error(e) - traceback.print_exc() - PathLog.error(translate('PathVcarve', 'The Job Base Object has \ -no engraveable element. Engraving \ -operation will produce no output.')) - raise e + #PathLog.error(e) + #traceback.print_exc() + PathLog.error(translate('PathVcarve', 'Error processing Base object. Engraving operation will produce no output.')) + #raise e def opUpdateDepths(self, obj, ignoreErrors=False): '''updateDepths(obj) ... engraving is always done at the top most z-value''' diff --git a/src/Mod/Path/PathScripts/PathVcarveGui.py b/src/Mod/Path/PathScripts/PathVcarveGui.py index 5254a78a22..286cce9b28 100644 --- a/src/Mod/Path/PathScripts/PathVcarveGui.py +++ b/src/Mod/Path/PathScripts/PathVcarveGui.py @@ -23,6 +23,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathVcarve as PathVcarve import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui @@ -53,6 +54,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): return super(TaskPanelBaseGeometryPage, self) def addBaseGeometry(self, selection): + PathLog.track(selection) added = False shapes = self.obj.BaseShapes for sel in selection: @@ -78,10 +80,12 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): shapes.append(base) self.obj.BaseShapes = shapes added = True - else: - # user wants us to engrave an edge of face of a base model - base = self.super().addBaseGeometry(selection) - added = added or base + + if not added: + # user wants us to engrave an edge of face of a base model + PathLog.info(" call default") + base = self.super().addBaseGeometry(selection) + added = added or base return added diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index 07958536fd..089e15256a 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -24,6 +24,7 @@ import FreeCAD import FreeCADGui +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathWaterline as PathWaterline import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui diff --git a/src/Mod/Path/PathScripts/post/fanuc_post.py b/src/Mod/Path/PathScripts/post/fanuc_post.py new file mode 100644 index 0000000000..d2bb6acb0e --- /dev/null +++ b/src/Mod/Path/PathScripts/post/fanuc_post.py @@ -0,0 +1,513 @@ +# *************************************************************************** +# * Copyright (c) 2014 sliptonic * +# * Copyright (c) 2021 shadowbane1000 * +# * * +# * 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 Lesser 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 * +# * * +# ***************************************************************************/ +from __future__ import print_function +import FreeCAD +from FreeCAD import Units +import Path +import argparse +import datetime +import shlex +import os.path +from PathScripts import PostUtils +from PathScripts import PathUtils + +TOOLTIP = ''' +This is a postprocessor file for the Path workbench. It is used to +take a pseudo-gcode fragment outputted by a Path object, and output +real GCode suitable should be suitable for most Fanuc controllers. +It has only been tested on a 21i-MB controller on a 3 axis mill. +This postprocessor, once placed in the appropriate PathScripts folder, +can be used directly from inside FreeCAD, via the GUI importer or via +python scripts with: + +import fanuc_post +fanuc_post.export(object,"/path/to/file.ncc","") +''' + +now = datetime.datetime.now() + +parser = argparse.ArgumentParser(prog='fanuc', add_help=False) +parser.add_argument('--no-header', action='store_true', help='suppress header output') +parser.add_argument('--no-comments', action='store_true', help='suppress comment output') +parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers') +parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output') +parser.add_argument('--precision', default='3', help='number of digits of precision, default=3') +parser.add_argument('--preamble', help='set commands to be issued before the first command, default="G17\nG90"') +parser.add_argument('--postamble', help='set commands to be issued after the last command, default="M05\nG17 G90\nM2"') +parser.add_argument('--inches', action='store_true', help='Convert output for US imperial mode (G20)') +parser.add_argument('--no-modal', action='store_true', help='Don\'t output the Same G-command Name USE NonModal Mode') +parser.add_argument('--no-axis-modal', action='store_true', help='Don\'t output the Same Axis Value Mode') +parser.add_argument('--no-tlo', action='store_true', help='suppress tool length offset (G43) following tool changes') + +TOOLTIP_ARGS = parser.format_help() + +# These globals set common customization preferences +OUTPUT_COMMENTS = True +OUTPUT_HEADER = True +OUTPUT_LINE_NUMBERS = False +SHOW_EDITOR = True +MODAL = True # if true commands are suppressed if the same as previous line. +USE_TLO = True # if true G43 will be output following tool changes +OUTPUT_DOUBLES = False # if false duplicate axis values are suppressed if the same as previous line. +COMMAND_SPACE = " " +LINENR = 100 # line number starting value + +# These globals will be reflected in the Machine configuration of the project +UNITS = "G21" # G21 for metric, G20 for us standard +UNIT_SPEED_FORMAT = 'mm/min' +UNIT_FORMAT = 'mm' + +MACHINE_NAME = "fanuc" +CORNER_MIN = {'x': 0, 'y': 0, 'z': 0} +CORNER_MAX = {'x': 500, 'y': 300, 'z': 300} +PRECISION = 3 + +# this global is used to pass spindle speed from the tool command into the machining command for +# rigid tapping. +tapSpeed = 0 + +# Preamble text will appear at the beginning of the GCODE output file. +PREAMBLE = '''G17 G54 G40 G49 G80 G90 +''' + +# Postamble text will appear following the last operation. +POSTAMBLE = '''M05 +G17 G54 G90 G80 G40 +M6 T0 +M2 +''' + +# Pre operation text will be inserted before every operation +PRE_OPERATION = '''''' + +# Post operation text will be inserted after every operation +POST_OPERATION = '''''' + +# Tool Change commands will be inserted before a tool change +TOOL_CHANGE = '''''' + +# to distinguish python built-in open function from the one declared below +if open.__module__ in ['__builtin__','io']: + pythonopen = open + + +def processArguments(argstring): + # pylint: disable=global-statement + global OUTPUT_HEADER + global OUTPUT_COMMENTS + global OUTPUT_LINE_NUMBERS + global SHOW_EDITOR + global PRECISION + global PREAMBLE + global POSTAMBLE + global UNITS + global UNIT_SPEED_FORMAT + global UNIT_FORMAT + global MODAL + global USE_TLO + global OUTPUT_DOUBLES + + try: + args = parser.parse_args(shlex.split(argstring)) + if args.no_header: + OUTPUT_HEADER = False + if args.no_comments: + OUTPUT_COMMENTS = False + if args.line_numbers: + OUTPUT_LINE_NUMBERS = True + if args.no_show_editor: + SHOW_EDITOR = False + print("Show editor = %d" % SHOW_EDITOR) + PRECISION = args.precision + if args.preamble is not None: + PREAMBLE = args.preamble + if args.postamble is not None: + POSTAMBLE = args.postamble + if args.inches: + UNITS = 'G20' + UNIT_SPEED_FORMAT = 'in/min' + UNIT_FORMAT = 'in' + PRECISION = 4 + if args.no_modal: + MODAL = False + if args.no_tlo: + USE_TLO = False + if args.no_axis_modal: + OUTPUT_DOUBLES = true + + except Exception: # pylint: disable=broad-except + return False + + return True + + +def export(objectslist, filename, argstring): + # pylint: disable=global-statement + if not processArguments(argstring): + return None + global UNITS + global UNIT_FORMAT + global UNIT_SPEED_FORMAT + global HORIZRAPID + global VERTRAPID + + for obj in objectslist: + if not hasattr(obj, "Path"): + print("the object " + obj.Name + " is not a path. Please select only path and Compounds.") + return None + + print("postprocessing...") + gcode = "" + + # write header + if OUTPUT_HEADER: + gcode += "%\n" + gcode += ";\n" + gcode += os.path.split(filename)[-1]+" ("+"FREECAD-FILENAME-GOES-HERE" + ", " + "JOB-NAME-GOES-HERE"+")\n" + gcode += linenumber() + "("+filename.upper()+",EXPORTED BY FREECAD!)\n" + gcode += linenumber() + "(POST PROCESSOR: " + __name__.upper() + ")\n" + gcode += linenumber() + "(OUTPUT TIME:" + str(now).upper() + ")\n" + + # Write the preamble + if OUTPUT_COMMENTS: + gcode += linenumber() + "(BEGIN PREAMBLE)\n" + for line in PREAMBLE.splitlines(False): + gcode += linenumber() + line + "\n" + gcode += linenumber() + UNITS + "\n" + + for obj in objectslist: + + # Skip inactive operations + if hasattr(obj, 'Active'): + if not obj.Active: + continue + if hasattr(obj, 'Base') and hasattr(obj.Base, 'Active'): + if not obj.Base.Active: + continue + + # fetch machine details + job = PathUtils.findParentJob(obj) + + myMachine = 'not set' + + if hasattr(job, "MachineName"): + myMachine = job.MachineName + + if hasattr(job, "MachineUnits"): + if job.MachineUnits == "Metric": + UNITS = "G21" + UNIT_FORMAT = 'mm' + UNIT_SPEED_FORMAT = 'mm/min' + else: + UNITS = "G20" + UNIT_FORMAT = 'in' + UNIT_SPEED_FORMAT = 'in/min' + + if hasattr(job, "SetupSheet"): + if hasattr(job.SetupSheet, "HorizRapid"): + HORIZRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity) + if hasattr(job.SetupSheet, "VertRapid"): + VERTRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity) + + # do the pre_op + if OUTPUT_COMMENTS: + gcode += linenumber() + "(BEGIN OPERATION: %s)\n" % obj.Label.upper() + gcode += linenumber() + "(MACHINE: %s, %s)\n" % (myMachine.upper(), UNIT_SPEED_FORMAT.upper()) + for line in PRE_OPERATION.splitlines(True): + gcode += linenumber() + line + + # get coolant mode + coolantMode = 'None' + if hasattr(obj, "CoolantMode") or hasattr(obj, 'Base') and hasattr(obj.Base, "CoolantMode"): + if hasattr(obj, "CoolantMode"): + coolantMode = obj.CoolantMode + else: + coolantMode = obj.Base.CoolantMode + + # turn coolant on if required + if OUTPUT_COMMENTS: + if not coolantMode == 'None': + gcode += linenumber() + '(COOLANT ON:' + coolantMode.upper() + ')\n' + if coolantMode == 'Flood': + gcode += linenumber() + 'M8' + '\n' + if coolantMode == 'Mist': + gcode += linenumber() + 'M7' + '\n' + + # process the operation gcode + gcode += parse(obj) + + # do the post_op + if OUTPUT_COMMENTS: + gcode += linenumber() + "(FINISH OPERATION: %s)\n" % obj.Label.upper() + for line in POST_OPERATION.splitlines(True): + gcode += linenumber() + line + + # turn coolant off if required + if not coolantMode == 'None': + if OUTPUT_COMMENTS: + gcode += linenumber() + '(COOLANT OFF:' + coolantMode.upper() + ')\n' + gcode += linenumber() +'M9' + '\n' + + # do the post_amble + if OUTPUT_COMMENTS: + gcode += "(BEGIN POSTAMBLE)\n" + for line in POSTAMBLE.splitlines(True): + gcode += linenumber() + line + gcode += "%\n" + + if FreeCAD.GuiUp and SHOW_EDITOR: + dia = PostUtils.GCodeEditorDialog() + dia.editor.setText(gcode) + result = dia.exec_() + if result: + final = dia.editor.toPlainText() + else: + final = gcode + else: + final = gcode + + print("done postprocessing.") + + if not filename == '-': + gfile = pythonopen(filename, "w") + gfile.write(final) + gfile.close() + + return final + + +def linenumber(): + # pylint: disable=global-statement + global LINENR + if OUTPUT_LINE_NUMBERS is True: + LINENR += 10 + return "N" + str(LINENR) + " " + return "" + + +def parse(pathobj): + # pylint: disable=global-statement + global PRECISION + global MODAL + global OUTPUT_DOUBLES + global UNIT_FORMAT + global UNIT_SPEED_FORMAT + global tapSpeed + + out = "" + lastcommand = None + precision_string = '.' + str(PRECISION) + 'f' + currLocation = {} # keep track for no doubles + print("Startup!") + + # the order of parameters + # arcs need work. original code from mach3_4 doesn't want K properties on XY plane. Not sure + # what fanuc does here. + params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H', 'D', 'P'] + firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0}) + currLocation.update(firstmove.Parameters) # set First location Parameters + + if hasattr(pathobj, "Group"): # We have a compound or project. + # if OUTPUT_COMMENTS: + # out += linenumber() + "(compound: " + pathobj.Label + ")\n" + for p in pathobj.Group: + out += parse(p) + return out + else: # parsing simple path + + # groups might contain non-path things like stock. + if not hasattr(pathobj, "Path"): + return out + + # if OUTPUT_COMMENTS: + # out += linenumber() + "(" + pathobj.Label + ")\n" + + adaptiveOp = False + opHorizRapid = 0 + opVertRapid = 0 + + if 'Adaptive' in pathobj.Name: + adaptiveOp = True + if hasattr(pathobj, 'ToolController'): + if hasattr(pathobj.ToolController, 'HorizRapid') and pathobj.ToolController.HorizRapid > 0: + opHorizRapid = Units.Quantity(pathobj.ToolController.HorizRapid, FreeCAD.Units.Velocity) + else: + FreeCAD.Console.PrintWarning('Tool Controller Horizontal Rapid Values are unset'+ '\n') + + if hasattr(pathobj.ToolController, 'VertRapid') and pathobj.ToolController.VertRapid > 0: + opVertRapid = Units.Quantity(pathobj.ToolController.VertRapid, FreeCAD.Units.Velocity) + else: + FreeCAD.Console.PrintWarning('Tool Controller Vertical Rapid Values are unset'+ '\n') + + for index,c in enumerate(pathobj.Path.Commands): + + outstring = [] + command = c.Name + if index+1 == len(pathobj.Path.Commands): + nextcommand = "" + else: + nextcommand = pathobj.Path.Commands[index+1].Name + + if adaptiveOp and c.Name in ["G0", "G00"]: + if opHorizRapid and opVertRapid: + command = 'G1' + else: + outstring.append('(TOOL CONTROLLER RAPID VALUES ARE UNSET)' + '\n') + + # suppress moves in fixture selection + if pathobj.Label == "Fixture": + if command == "G0": + continue + + # if it's a tap, we rigid tap, so don't start the spindle yet... + if command == "M03" or command == "M3": + if pathobj.Tool.ToolType == "Tap": + tapSpeed = int(pathobj.SpindleSpeed) + continue + + # convert drill cycles to tap cycles if tool is a tap + if command == "G81" or command == "G83": + if hasattr(pathobj, 'ToolController') and pathobj.ToolController.Tool.ToolType == "Tap": + command = "G84" + out += linenumber() + "G95\n" + paramstring = "" + for param in [ "X", "Y" ]: + if param in c.Parameters: + if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]): + continue + else: + pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + paramstring += " " + param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string) + if paramstring != "": + out += linenumber() + "G00"+paramstring+"\n" + + if "S" in c.Parameters: + tapSpeed = int(c.Parameters['S']) + out += "M29 S"+str(tapSpeed)+"\n" + + for param in [ "Z", "R" ]: + if param in c.Parameters: + if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]): + continue + else: + pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + paramstring += " " + param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string) + # in this mode, F is the distance per revolution of the thread (pitch) + # P is the dwell time in seconds at the bottom of the thread + # Q is the peck depth of the threading operation + for param in [ "F", "P", "Q" ]: + if param in c.Parameters: + value = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + paramstring += " " + param + format(float(value.getValueAs(UNIT_FORMAT)), precision_string) + + out += linenumber() + "G84" + paramstring + "\n" + out += linenumber() + "G80\n" + out += linenumber() + "G94\n" + continue + + + outstring.append(command) + + # if modal: suppress the command if it is the same as the last one + if MODAL is True: + if command == lastcommand: + outstring.pop(0) + + # suppress a G80 between two identical command + if command == "G80" and lastcommand == nextcommand: + continue + + if c.Name[0] == '(' and not OUTPUT_COMMENTS: # command is a comment + continue + + # Now add the remaining parameters in order + for param in params: + if param in c.Parameters: + if param == 'F' and (currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES): + if c.Name not in ["G0", "G00"]: # fanuc doesn't use rapid speeds + speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity) + if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0: + outstring.append(param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string)) + else: + continue + elif param == 'T': + outstring.append(param + str(int(c.Parameters['T']))) + elif param == 'H': + outstring.append(param + str(int(c.Parameters['H']))) + elif param == 'D': + outstring.append(param + str(int(c.Parameters['D']))) + elif param == 'S': + outstring.append(param + str(int(c.Parameters['S']))) + currentSpeed = int(c.Parameters['S']) + else: + if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]): + continue + else: + pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + outstring.append( + param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string)) + + if adaptiveOp and c.Name in ["G0", "G00"]: + if opHorizRapid and opVertRapid: + if 'Z' not in c.Parameters: + outstring.append('F' + format(float(opHorizRapid.getValueAs(UNIT_SPEED_FORMAT)), precision_string)) + else: + outstring.append('F' + format(float(opVertRapid.getValueAs(UNIT_SPEED_FORMAT)), precision_string)) + + # store the latest command + lastcommand = command + currLocation.update(c.Parameters) + + # Check for Tool Change: + if command == 'M6': + # stop the spindle + currentSpeed = 0 + out += linenumber() + "M5\n" + for line in TOOL_CHANGE.splitlines(True): + out += linenumber() + line + + # add height offset + if USE_TLO: + tool_height = '\nG43 H' + str(int(c.Parameters['T'])) + outstring.append(tool_height) + + if command == "message": + if OUTPUT_COMMENTS is False: + out = [] + else: + outstring.pop(0) # remove the command + + # prepend a line number and append a newline + if len(outstring) >= 1: + if OUTPUT_LINE_NUMBERS: + outstring.insert(0, (linenumber())) + + # append the line to the final output + for w in outstring: + out += w.upper() + COMMAND_SPACE + out = out.strip() + "\n" + + return out + +# print(__name__ + " gcode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/philips_post.py b/src/Mod/Path/PathScripts/post/philips_post.py index 41203ef075..f06d2d666d 100644 --- a/src/Mod/Path/PathScripts/post/philips_post.py +++ b/src/Mod/Path/PathScripts/post/philips_post.py @@ -384,7 +384,7 @@ def export(objectslist, filename, argstring): # #\better: append iff MODAL == False # if command == lastcommand: # outstring.pop(0) - if c.Parameters >= 1: + if len(c.Parameters) >= 1: for param in params: # test print("param: " + param + ", command: " + command) if param in c.Parameters: diff --git a/src/Mod/Path/PathTests/TestPathPropertyBag.py b/src/Mod/Path/PathTests/TestPathPropertyBag.py new file mode 100644 index 0000000000..974ca3bd6c --- /dev/null +++ b/src/Mod/Path/PathTests/TestPathPropertyBag.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2021 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 * +# * * +# *************************************************************************** + +import FreeCAD +import PathScripts.PathPropertyBag as PathPropertyBag +import PathTests.PathTestUtils as PathTestUtils + +class TestPathPropertyBag(PathTestUtils.PathTestBase): + + def setUp(self): + self.doc = FreeCAD.newDocument('test-property-bag') + + def tearDown(self): + FreeCAD.closeDocument(self.doc.Name) + + def test00(self): + '''basic PropertyBag creation and access test''' + bag = PathPropertyBag.Create() + self.assertTrue(hasattr(bag, 'Proxy')) + self.assertEqual(bag.Proxy.getCustomProperties(), []) + self.assertEqual(bag.CustomPropertyGroups, []) + + def test01(self): + '''adding properties to a PropertyBag is tracked properly''' + bag = PathPropertyBag.Create() + proxy = bag.Proxy + proxy.addCustomProperty('App::PropertyString', 'Title', 'Address', 'Some description') + self.assertTrue(hasattr(bag, 'Title')) + bag.Title = 'Madame' + self.assertEqual(bag.Title, 'Madame') + self.assertEqual(bag.Proxy.getCustomProperties(), ['Title']) + self.assertEqual(bag.CustomPropertyGroups, ['Address']) + + def test02(self): + '''refreshCustomPropertyGroups deletes empty groups''' + bag = PathPropertyBag.Create() + proxy = bag.Proxy + proxy.addCustomProperty('App::PropertyString', 'Title', 'Address', 'Some description') + bag.Title = 'Madame' + bag.removeProperty('Title') + proxy.refreshCustomPropertyGroups() + self.assertEqual(bag.Proxy.getCustomProperties(), []) + self.assertEqual(bag.CustomPropertyGroups, []) + + def test03(self): + '''refreshCustomPropertyGroups does not delete non-empty groups''' + bag = PathPropertyBag.Create() + proxy = bag.Proxy + proxy.addCustomProperty('App::PropertyString', 'Title', 'Address', 'Some description') + proxy.addCustomProperty('App::PropertyString', 'Gender', 'Attributes') + bag.Title = 'Madame' + bag.Gender = 'Female' + bag.removeProperty('Gender') + proxy.refreshCustomPropertyGroups() + self.assertEqual(bag.Proxy.getCustomProperties(), ['Title']) + self.assertEqual(bag.CustomPropertyGroups, ['Address']) + diff --git a/src/Mod/Path/PathTests/TestPathToolBit.py b/src/Mod/Path/PathTests/TestPathToolBit.py index dd82c7fc63..7a684b3697 100644 --- a/src/Mod/Path/PathTests/TestPathToolBit.py +++ b/src/Mod/Path/PathTests/TestPathToolBit.py @@ -22,37 +22,143 @@ import PathScripts.PathToolBit as PathToolBit import PathTests.PathTestUtils as PathTestUtils +import glob +import os +TestToolDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'Tools') +TestInvalidDir = os.path.join(TestToolDir, 'some', 'silly', 'path', 'that', 'should', 'not', 'exist') + +TestToolBitName = 'test-path-tool-bit-bit-00.fctb' +TestToolShapeName = 'test-path-tool-bit-shape-00.fcstd' +TestToolLibraryName = 'test-path-tool-bit-library-00.fctl' + +def testToolShape(path = TestToolDir, name = TestToolShapeName): + return os.path.join(path, 'Shape', name) + +def testToolBit(path = TestToolDir, name = TestToolBitName): + return os.path.join(path, 'Bit', name) + +def testToolLibrary(path = TestToolDir, name = TestToolLibraryName): + return os.path.join(path, 'Library', name) + +def printTree(path, indent): + print("{} {}".format(indent, os.path.basename(path))) + if os.path.isdir(path): + if os.path.basename(path).startswith('__'): + print("{} ...".format(indent)) + else: + for foo in sorted(glob.glob(os.path.join(path, '*'))): + printTree(foo, "{} ".format(indent)) class TestPathToolBit(PathTestUtils.PathTestBase): - def test00(self): - '''Find a tool shapee from file name''' + def test(self): + '''Log test setup directory structure''' + # Enable this test if there are errors showing up in the build system with the + # paths that work OK locally. It'll print out the directory tree, and if it + # doesn't look right you know where to look for it + print() + print("realpath : {}".format(os.path.realpath(__file__))) + print(" Tools : {}".format(TestToolDir)) + print(" dir : {}".format(os.path.dirname(os.path.realpath(__file__)))) + printTree(os.path.dirname(os.path.realpath(__file__)), " :") - path = PathToolBit.findShape('endmill.fcstd') + def test00(self): + '''Find a tool shape from file name''' + path = PathToolBit.findToolShape('endmill.fcstd') self.assertIsNot(path, None) self.assertNotEqual(path, 'endmill.fcstd') - def test01(self): - '''Find a tool shapee from an invalid absolute path.''' - path = PathToolBit.findShape('/this/is/unlikely/a/valid/path/v-bit.fcstd') + def test01(self): + '''Not find a relative path shape if not stored in default location''' + path = PathToolBit.findToolShape(TestToolShapeName) + self.assertIsNone(path) + + + def test02(self): + '''Find a relative path shape if it's local to a bit path''' + path = PathToolBit.findToolShape(TestToolShapeName, testToolBit()) self.assertIsNot(path, None) - self.assertNotEqual(path, '/this/is/unlikely/a/valid/path/v-bit.fcstd') + self.assertEqual(path, testToolShape()) + + + def test03(self): + '''Not find a tool shape from an invalid absolute path.''' + path = PathToolBit.findToolShape(testToolShape(TestInvalidDir)) + self.assertIsNone(path) + + + def test04(self): + '''Find a tool shape from a valid absolute path.''' + path = PathToolBit.findToolShape(testToolShape()) + self.assertIsNot(path, None) + self.assertEqual(path, testToolShape()) def test10(self): - '''find the relative path of a tool bit''' - shape = 'endmill.fcstd' - path = PathToolBit.findShape(shape) + '''Find a tool bit from file name''' + path = PathToolBit.findToolBit('5mm_Endmill.fctb') self.assertIsNot(path, None) - self.assertGreater(len(path), len(shape)) - rel = PathToolBit.findRelativePathShape(path) - self.assertEqual(rel, shape) + self.assertNotEqual(path, '5mm_Endmill.fctb') + def test11(self): - '''store full path if relative path isn't found''' - path = '/this/is/unlikely/a/valid/path/v-bit.fcstd' - rel = PathToolBit.findRelativePathShape(path) - self.assertEqual(rel, path) + '''Not find a relative path bit if not stored in default location''' + path = PathToolBit.findToolBit(TestToolBitName) + self.assertIsNone(path) + + + def test12(self): + '''Find a relative path bit if it's local to a library path''' + path = PathToolBit.findToolBit(TestToolBitName, testToolLibrary()) + self.assertIsNot(path, None) + self.assertEqual(path, testToolBit()) + + + def test13(self): + '''Not find a tool bit from an invalid absolute path.''' + path = PathToolBit.findToolBit(testToolBit(TestInvalidDir)) + self.assertIsNone(path) + + + def test14(self): + '''Find a tool bit from a valid absolute path.''' + path = PathToolBit.findToolBit(testToolBit()) + self.assertIsNot(path, None) + self.assertEqual(path, testToolBit()) + + + + def test20(self): + '''Find a tool library from file name''' + path = PathToolBit.findToolLibrary('Default.fctl') + self.assertIsNot(path, None) + self.assertNotEqual(path, 'Default.fctl') + + + def test21(self): + '''Not find a relative path library if not stored in default location''' + path = PathToolBit.findToolLibrary(TestToolLibraryName) + self.assertIsNone(path) + + + def test22(self): + '''[skipped] Find a relative path library if it's local to ''' + # this is not a valid test for libraries because t + self.assertTrue(True) + + + def test23(self): + '''Not find a tool library from an invalid absolute path.''' + path = PathToolBit.findToolLibrary(testToolLibrary(TestInvalidDir)) + self.assertIsNone(path) + + + def test24(self): + '''Find a tool library from a valid absolute path.''' + path = PathToolBit.findToolBit(testToolBit()) + self.assertIsNot(path, None) + self.assertEqual(path, testToolBit()) + diff --git a/src/Mod/Path/PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb b/src/Mod/Path/PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb new file mode 100644 index 0000000000..7cc72ba33c --- /dev/null +++ b/src/Mod/Path/PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb @@ -0,0 +1,12 @@ +{ + "version": 2, + "name": "5mm Endmill", + "shape": "endmill.fcstd", + "parameter": { + "CuttingEdgeHeight": "30.0000 mm", + "Diameter": "5.0000 mm", + "Length": "50.0000 mm", + "ShankDiameter": "3.0000 mm" + }, + "attribute": {} +} diff --git a/src/Mod/Path/PathTests/Tools/Library/test-path-tool-bit-library-00.fctl b/src/Mod/Path/PathTests/Tools/Library/test-path-tool-bit-library-00.fctl new file mode 100644 index 0000000000..60de98e08e --- /dev/null +++ b/src/Mod/Path/PathTests/Tools/Library/test-path-tool-bit-library-00.fctl @@ -0,0 +1,41 @@ +{ + "tools": [ + { + "nr": 1, + "path": "5mm_Endmill.fctb" + }, + { + "nr": 2, + "path": "5mm_Drill.fctb" + }, + { + "nr": 3, + "path": "6mm_Ball_End.fctb" + }, + { + "nr": 4, + "path": "6mm_Bullnose.fctb" + }, + { + "nr": 5, + "path": "60degree_Vbit.fctb" + }, + { + "nr": 6, + "path": "45degree_chamfer.fctb" + }, + { + "nr": 7, + "path": "slittingsaw.fctb" + }, + { + "nr": 8, + "path": "probe.fctb" + }, + { + "nr": 9, + "path": "5mm-thread-cutter.fctb" + } + ], + "version": 1 +} diff --git a/src/Mod/Path/PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd b/src/Mod/Path/PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd new file mode 100644 index 0000000000..5b5a76dc41 Binary files /dev/null and b/src/Mod/Path/PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd differ diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 0adebedfc6..0be621b040 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -24,6 +24,7 @@ import TestApp from PathTests.TestPathLog import TestPathLog from PathTests.TestPathPreferences import TestPathPreferences +from PathTests.TestPathPropertyBag import TestPathPropertyBag from PathTests.TestPathCore import TestPathCore #from PathTests.TestPathPost import PathPostTestCases from PathTests.TestPathGeom import TestPathGeom @@ -66,4 +67,5 @@ False if TestPathToolBit.__name__ else True False if TestPathVoronoi.__name__ else True False if TestPathThreadMilling.__name__ else True False if TestPathVcarve.__name__ else True +False if TestPathPropertyBag.__name__ else True diff --git a/src/Mod/Path/Tools/Bit/45degree_chamfer.fctb b/src/Mod/Path/Tools/Bit/45degree_chamfer.fctb index 6c1231ed0f..77e53e523c 100644 --- a/src/Mod/Path/Tools/Bit/45degree_chamfer.fctb +++ b/src/Mod/Path/Tools/Bit/45degree_chamfer.fctb @@ -11,4 +11,4 @@ "ShankDiameter": "6.3500 mm" }, "attribute": {} -} \ No newline at end of file +} diff --git a/src/Mod/Path/Tools/Bit/5mm-thread-cutter.fctb b/src/Mod/Path/Tools/Bit/5mm-thread-cutter.fctb index 265978053b..2a5f0253cd 100644 --- a/src/Mod/Path/Tools/Bit/5mm-thread-cutter.fctb +++ b/src/Mod/Path/Tools/Bit/5mm-thread-cutter.fctb @@ -1,14 +1,14 @@ { "version": 2, - "name": "3mm-thread-cutter", + "name": "5mm-thread-cutter", "shape": "thread-mill.fcstd", "parameter": { "Crest": "0.10 mm", "Diameter": "5.00 mm", "Length": "50.00 mm", "NeckDiameter": "3.00 mm", - "NeckHeight": "20.00 mm", + "NeckLength": "20.00 mm", "ShankDiameter": "5.00 mm" }, "attribute": {} -} \ No newline at end of file +} diff --git a/src/Mod/Path/Tools/Bit/probe.fctb b/src/Mod/Path/Tools/Bit/probe.fctb index b92828e7ac..31fa354ff6 100644 --- a/src/Mod/Path/Tools/Bit/probe.fctb +++ b/src/Mod/Path/Tools/Bit/probe.fctb @@ -1,6 +1,6 @@ { "version": 2, - "name": "Probe004", + "name": "Probe", "shape": "probe.fcstd", "parameter": { "Diameter": "6.0000 mm", diff --git a/src/Mod/Path/Tools/Bit/slittingsaw.fctb b/src/Mod/Path/Tools/Bit/slittingsaw.fctb index e9d33fe571..5b63c060ed 100644 --- a/src/Mod/Path/Tools/Bit/slittingsaw.fctb +++ b/src/Mod/Path/Tools/Bit/slittingsaw.fctb @@ -4,8 +4,8 @@ "shape": "slittingsaw.fcstd", "parameter": { "BladeThickness": "3.0000 mm", - "BoltHeight": "3.0000 mm", - "BoltWidth": "8.0000 mm", + "CapHeight": "3.0000 mm", + "CapDiameter": "8.0000 mm", "Diameter": "76.2000 mm", "Length": "50.0000 mm", "ShankDiameter": "19.0500 mm" diff --git a/src/Mod/Path/Tools/README.md b/src/Mod/Path/Tools/README.md index cdaed5a5b4..8f46b421bc 100644 --- a/src/Mod/Path/Tools/README.md +++ b/src/Mod/Path/Tools/README.md @@ -1,6 +1,6 @@ # Tools -Each tool is stored as a JSON file which has the template's path and values for all named constraints of the template. +Each tool is stored as a JSON file which has the shape's path and values for all attributes of the shape. It also includes all additional parameters and their values. Storing a tool as a JSON file sounds great but eliminates the option of an accurate thumbnail. On the other hand, @@ -8,10 +8,10 @@ storing each tool as a `*.fcstd` file requires more space and does not allow for extensive tool aresenal they might want to script the generation of tools which is easily done for a `*.json` file but practically impossible for `*.fcstd` files. -When a tool is instantiated in a job the PDN body is created from the template and the constraints are set according -to the values from the JSON file. All additional parameters are created as properties on the object. This provides the -the correct shape and dimensions which can be used to generate a point cloud or mesh for advanced algorithms (and -potentially simulation). +When a tool is instantiated in a job the PDN body is created from the shape and the attributes and constraints are set +according to the values from the JSON file. All additional parameters are created as properties on the object. This +provides the the correct shape and dimensions which can be used to generate a point cloud or mesh for advanced +algorithms (and potentially simulation). # Tool Libraries @@ -55,33 +55,34 @@ TechDraw's templates. ## How to create a new tool 1. Set the tool's Label, this will show up in the object tree -1. Select a tool shape from the existing templates. If your tool doesn't exist, you'll have to create a new template, +1. Select a tool shape from the existing shape files. If your tool doesn't exist, you'll have to create a new shape, see below for details. -1. Each template has its own set of parameters, fill them with the tool's values. +1. Each tool bit shape has its own set of parameters, fill them with the tool's values. 1. Select additional parameters 1. Save the tool under path/file that makes sense to you ## How to create a new tool bit Shape -A tool bit template represents the physical shape of a tool. It does not completely describe the bit - for that some -additional parameters are needed which will be added when an actual bit is parametrized from the template. +The shape file for a tool bit is expected to contain a PD body which represents the tool as a 3d solid. The PD body +should be parametric based on a a PropertyBag object so that, when the properties of the PropertyBag are changed the +solid is updated to the correct representation. 1. Create a new FreeCAD document 1. Open the `PartDesign` workbench, create a body and give the body a label you want to show up in the bit selection. -1. Create a sketch in the XZ plane and draw half the profile of the bit. - * Put the top center of the bit on the origin (0,0) -1. For any constraint serving as a parameter for the tool (like overall Length) create a named constraint - * The name is the label of the input field - * Names are split at CamelCase boundaries into words in the edit dialog - * Use a `;` in the name to add help text which will show up as the entry fields tool tip - * If the tool is used by legacy ops it should at least have one constraint called `Diameter` - * Use construction lines for constraints that are not directly accessible, like `Diameter` and `Angle` -1. Any unnamed constraint will not be editable for a specific tool -1. Once the sketch is fully constrained, close the sketch -1. Rotate the sketch around the z-axis +1. Open the Path workbench and (with the PD body selected) create a PropertyBag, + menu 'Path' -> 'Utils' -> 'Property Bag' + * this creates a PropertyBag object inside the Body (assuming it was selected) + * add properties to which define the tool bit's shape and put those into the group 'Shape' + * add any other properties to the bag which might be useful for the tool bit +1. Construct the body of the tool bit and assign experssions referencing properties from the PropertyBag (in the + `Shape` Group) for all constraints. + * Position the tip of the tool bit on the origin (0,0) 1. Save the document as a new file in the Shape directory * Before saving the document make sure you have _Save Thumbnail_ selected, and _Add program logo_ deselected in FreeCAD's preferences. * Also make sure to switch to _Front View_ and _Fit content to screen_ - * Whatever you see when saving the document will end up being the visual representation of the template + * Whatever you see when saving the document will end up being the visual representation of tool bits with this shape + +Not that 'Shape' is the only property group which has special meaning for tool bits. All other property groups are +copied verbatim to the ToolBit object when one is created. diff --git a/src/Mod/Path/Tools/Shape/ballend.fcstd b/src/Mod/Path/Tools/Shape/ballend.fcstd index c3c5ee8b25..14b2a65efa 100644 Binary files a/src/Mod/Path/Tools/Shape/ballend.fcstd and b/src/Mod/Path/Tools/Shape/ballend.fcstd differ diff --git a/src/Mod/Path/Tools/Shape/bullnose.fcstd b/src/Mod/Path/Tools/Shape/bullnose.fcstd index c35ae78d81..63a50c4969 100644 Binary files a/src/Mod/Path/Tools/Shape/bullnose.fcstd and b/src/Mod/Path/Tools/Shape/bullnose.fcstd differ diff --git a/src/Mod/Path/Tools/Shape/chamfer.fcstd b/src/Mod/Path/Tools/Shape/chamfer.fcstd index 3470b4a3c6..59c7cb1a3a 100644 Binary files a/src/Mod/Path/Tools/Shape/chamfer.fcstd and b/src/Mod/Path/Tools/Shape/chamfer.fcstd differ diff --git a/src/Mod/Path/Tools/Shape/drill.fcstd b/src/Mod/Path/Tools/Shape/drill.fcstd index aa2a626d02..da1e277778 100644 Binary files a/src/Mod/Path/Tools/Shape/drill.fcstd and b/src/Mod/Path/Tools/Shape/drill.fcstd differ diff --git a/src/Mod/Path/Tools/Shape/endmill.fcstd b/src/Mod/Path/Tools/Shape/endmill.fcstd index 98888db1e3..b3b1ae18a4 100644 Binary files a/src/Mod/Path/Tools/Shape/endmill.fcstd and b/src/Mod/Path/Tools/Shape/endmill.fcstd differ diff --git a/src/Mod/Path/Tools/Shape/probe.fcstd b/src/Mod/Path/Tools/Shape/probe.fcstd index 5f853f415b..b4f20fa949 100644 Binary files a/src/Mod/Path/Tools/Shape/probe.fcstd and b/src/Mod/Path/Tools/Shape/probe.fcstd differ diff --git a/src/Mod/Path/Tools/Shape/slittingsaw.fcstd b/src/Mod/Path/Tools/Shape/slittingsaw.fcstd index 81d3d7f3d9..6694d911f7 100644 Binary files a/src/Mod/Path/Tools/Shape/slittingsaw.fcstd and b/src/Mod/Path/Tools/Shape/slittingsaw.fcstd differ diff --git a/src/Mod/Path/Tools/Shape/thread-mill.fcstd b/src/Mod/Path/Tools/Shape/thread-mill.fcstd index d97e56241f..c69e915396 100644 Binary files a/src/Mod/Path/Tools/Shape/thread-mill.fcstd and b/src/Mod/Path/Tools/Shape/thread-mill.fcstd differ diff --git a/src/Mod/Path/Tools/Shape/v-bit.fcstd b/src/Mod/Path/Tools/Shape/v-bit.fcstd index 3168e5fe8d..51db09d7c7 100644 Binary files a/src/Mod/Path/Tools/Shape/v-bit.fcstd and b/src/Mod/Path/Tools/Shape/v-bit.fcstd differ diff --git a/src/Mod/Path/Tools/toolbit-attributes.py b/src/Mod/Path/Tools/toolbit-attributes.py new file mode 100755 index 0000000000..2f04ed6a58 --- /dev/null +++ b/src/Mod/Path/Tools/toolbit-attributes.py @@ -0,0 +1,152 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2021 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 * +# * * +# *************************************************************************** +# +# Script for manipulating toolbit shapes in a batch. This is handy for making +# attributes consistent over multiple or all shape file. +# +# Most commands are straight forward, except for --set which can be used to +# set the actual value of a property, or, in the case of enumerations it can +# also be used to set the enum values. This might be particularly useful when +# adding more materials. +# +# The following example moves all properties from the "Extra" group into the +# "Attributes" group. Note that the Attributes group might or might not +# already exist. If it does exist the specified group gets merged in. +# +# ./toolbit-attributes.py --move 'Extra:Attributes' src/Mod/Path/Tools/Shape/*.fcstd +# +# This example sets the Flutes value of all Shapes to 0: +# +# ./toolbit-attributes.py --set Flutes=0 src/Mod/Path/Tools/Shape/*.fcstd +# +# Finally, this example sets the enumerations of the Material attribute: +# +# ./toolbit-attributes.py --set 'Material=[HSS,Carbide,Tool Steel,Titanium]' src/Mod/Path/Tools/Shape/*.fcstd +# +# After running this tool it might be necessary to open the shape files +# manually and make sure they are visible and the thumbprint image is +# saved. +# +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# !!! Final note: this is a dangerous tool and should not be shipped with FC. !!!! +# !!! It's sole purpose is to make life of the developers easier and ensure !!!! +# !!! consistent attributes across all toobit shapes. !!!! +# !!! A single typo can ruin a lot of toolbit shapes - make sure to only use !!!! +# !!! it if those shape files are under a version control system and you can !!!! +# !!! back out the changes easily. !!!! +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +import argparse +import os +import sys + +parser = argparse.ArgumentParser() +parser.add_argument('path', nargs='+', help='Shape file to process') +parser.add_argument('--move', metavar=':', help='Move attributes from group 1 into group 2') +parser.add_argument('--delete', metavar='prop', help='Delete the given attribute') +parser.add_argument('--set', metavar='prop=value', help='Set property value') +parser.add_argument('--print', action='store_true', help='If set attributes are printed as discovered') +parser.add_argument('--print-all', action='store_true', help='If set Shape attributes are also printed') +parser.add_argument('--print-groups', action='store_true', help='If set all custom property groups are printed') +parser.add_argument('--save-changes', action='store_true', help='Unless specified the file is not saved') +parser.add_argument('--freecad', help='Directory FreeCAD binaries (libFreeCAD.so) if not installed') +args = parser.parse_args() + +if args.freecad: + sys.path.append(args.freecad) + +import FreeCAD +import Path +import PathScripts.PathPropertyBag as PathPropertyBag +import PathScripts.PathUtil as PathUtil + +set_var=None +set_val=None + +GroupMap = {} +if args.move: + g = args.move.split(':') + if len(g) != 2: + print("ERROR: {} not a valid group mapping".format(args.move)) + sys.exit(1) + GroupMap[g[0]] = g[1] + +if args.set: + s = args.set.split('=') + if len(s) != 2: + print("ERROR: {} not a valid group mapping".format(args.move)) + sys.exit(1) + set_var = s[0] + set_val = s[1] + +for i, fname in enumerate(args.path): + #print(fname) + doc = FreeCAD.openDocument(fname, False) + print("{}:".format(doc.Name)) + for o in doc.Objects: + if PathPropertyBag.IsPropertyBag(o): + if args.print_groups: + print(" {}: {}".format(o.Label, sorted(o.CustomPropertyGroups))) + else: + print(" {}:".format(o.Label)) + for p in o.Proxy.getCustomProperties(): + grp = o.getGroupOfProperty(p) + typ = o.getTypeIdOfProperty(p) + ttp = PathPropertyBag.getPropertyTypeName(typ) + val = PathUtil.getProperty(o, p) + dsc = o.getDocumentationOfProperty(p) + enm = '' + enum = [] + if ttp == 'Enumeration': + enum = o.getEnumerationsOfProperty(p) + enm = "{}".format(','.join(enum)) + if GroupMap.get(grp): + group = GroupMap.get(grp) + print("move: {}.{} -> {}".format(grp, p, group)) + o.removeProperty(p) + o.Proxy.addCustomProperty(typ, p, group, dsc) + if enum: + print("enum {}.{}: {}".format(group, p, enum)) + setattr(o, p, enum) + PathUtil.setProperty(o, p, val) + if p == set_var: + print("set {}.{} = {}".format(grp, p, set_val)) + if ttp == 'Enumeration' and set_val[0] == '[': + enum = set_val[1:-1].split(',') + setattr(o, p, enum) + else: + PathUtil.setProperty(o, p, set_val) + if p == args.delete: + print("delete {}.{}".format(grp, p)) + o.removeProperty(p) + if not args.print_all and grp == 'Shape': + continue + if args.print or args.print_all: + print(" {:10} {:20} {:20} {:10} {}".format(grp, p, ttp, str(val), enm)) + o.Proxy.refreshCustomPropertyGroups() + if args.save_changes: + doc.recompute() + doc.save() + FreeCAD.closeDocument(doc.Name) + +print('-done-') diff --git a/src/Mod/Sketcher/Gui/TaskSketcherGeneral.h b/src/Mod/Sketcher/Gui/TaskSketcherGeneral.h index 1cdd92923a..335af9c434 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherGeneral.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherGeneral.h @@ -28,8 +28,6 @@ #include #include -class Ui_TaskSketcherGeneral; - namespace App { class Property; } @@ -40,6 +38,7 @@ class ViewProvider; namespace SketcherGui { +class Ui_TaskSketcherGeneral; class ViewProviderSketch; class SketcherGeneralWidget : public QWidget diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 95b5b458de..098a62005c 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -6493,6 +6493,16 @@ void ViewProviderSketch::UpdateSolverInformation() .arg(tr("(click to select)")) .arg(appendRedundantMsg(getSketchObject()->getLastRedundant()))); } + + QString partiallyRedundantString; + + if(hasPartiallyRedundant) { + partiallyRedundantString = QString::fromLatin1("
%1%2
%3

") + .arg(tr("Sketch contains partially redundant constraints ")) + .arg(tr("(click to select)")) + .arg(appendPartiallyRedundantMsg(getSketchObject()->getLastPartiallyRedundant())); + } + if (getSketchObject()->getLastSolverStatus() == 0) { if (dofs == 0) { // color the sketch as fully constrained if it has geometry (other than the axes) @@ -6500,31 +6510,19 @@ void ViewProviderSketch::UpdateSolverInformation() edit->FullyConstrained = true; if (!hasRedundancies) { - signalSetUp(QString::fromLatin1("%1").arg(tr("Fully constrained sketch"))); + signalSetUp(QString::fromLatin1("%1 %2").arg(tr("Fully constrained sketch")).arg(partiallyRedundantString)); } } else if (!hasRedundancies) { QString infoString; if (dofs == 1) - infoString = tr("Under-constrained sketch with 1 degree of freedom. %1") - .arg(hasPartiallyRedundant? - QString::fromLatin1("
%1%2
%3

") - .arg(tr("Sketch contains partially redundant constraints ")) - .arg(tr("(click to select)")) - .arg(appendPartiallyRedundantMsg(getSketchObject()->getLastPartiallyRedundant())) - : QString()); + signalSetUp(tr("Under-constrained sketch with 1 degree of freedom. %1") + .arg(partiallyRedundantString)); else - infoString = tr("Under-constrained sketch with %1 degrees of freedom. %2") + signalSetUp(tr("Under-constrained sketch with %1 degrees of freedom. %2") .arg(dofs) - .arg(hasPartiallyRedundant? - QString::fromLatin1("
%1%2
%3

") - .arg(tr("Sketch contains partially redundant constraints ")) - .arg(tr("(click to select)")) - .arg(appendPartiallyRedundantMsg(getSketchObject()->getLastPartiallyRedundant())) - : QString()); - - signalSetUp(infoString); + .arg(partiallyRedundantString)); } signalSolved(QString::fromLatin1("%1").arg(tr("Solved in %1 sec").arg(getSketchObject()->getLastSolveTime()))); diff --git a/src/Mod/Start/StartPage/EnableDownload.py b/src/Mod/Start/StartPage/EnableDownload.py index 4baca89549..f7f117969e 100644 --- a/src/Mod/Start/StartPage/EnableDownload.py +++ b/src/Mod/Start/StartPage/EnableDownload.py @@ -20,6 +20,6 @@ #* * #*************************************************************************** -import FreeCAD,FreeCADGui +import FreeCAD rf=FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start") rf.SetBool("AllowDownload",True) diff --git a/src/Mod/Start/StartPage/LoadMRU.py b/src/Mod/Start/StartPage/LoadMRU.py index 4c7889daa3..7b7e14c7d6 100644 --- a/src/Mod/Start/StartPage/LoadMRU.py +++ b/src/Mod/Start/StartPage/LoadMRU.py @@ -20,7 +20,7 @@ #* * #*************************************************************************** -import FreeCADGui,sys +import FreeCADGui # MRU will be given before this script is run rf=FreeCAD.ParamGet("User parameter:BaseApp/Preferences/RecentFiles") FreeCADGui.loadFile(rf.GetString("MRU"+str(MRU))) diff --git a/src/Mod/Start/StartPage/StartPage.js b/src/Mod/Start/StartPage/StartPage.js index ca70689613..ad906f89e9 100644 --- a/src/Mod/Start/StartPage/StartPage.js +++ b/src/Mod/Start/StartPage/StartPage.js @@ -31,7 +31,7 @@ function load() { if (allowDownloads == 1) { // load latest commits - ddiv = document.getElementById("commits"); + var ddiv = document.getElementById("commits"); ddiv.innerHTML = "Connecting..."; var tobj=new JSONscriptRequest('https://api.github.com/repos/FreeCAD/FreeCAD/commits?callback=printCommits'); tobj.buildScriptTag(); // Build the script tag @@ -61,7 +61,7 @@ function printCommits(data) { // json callback for git commits - ddiv = document.getElementById('commits'); + var ddiv = document.getElementById('commits'); ddiv.innerHTML = "Received"; var html = ['
    ']; for (var i = 0; i < 25; i++) { @@ -76,7 +76,7 @@ function printAddons(data) { // json callback for addons list - ddiv = document.getElementById('addons'); + var ddiv = document.getElementById('addons'); ddiv.innerHTML = "Received"; var html = ['
      ']; var blacklist = ['addons_installer.FCMacro','FreeCAD-Addon-Details.md','README.md']; @@ -98,7 +98,7 @@ function printForum(data) { // json callback for forum posts - ddiv = document.getElementById('forum'); + var ddiv = document.getElementById('forum'); ddiv.innerHTML = "Received"; var html = ['
        ']; for (var i = 0; i < 25; i++) { diff --git a/src/Mod/Start/StartPage/StartPage.py b/src/Mod/Start/StartPage/StartPage.py index 5fc176f778..a19c46949c 100644 --- a/src/Mod/Start/StartPage/StartPage.py +++ b/src/Mod/Start/StartPage/StartPage.py @@ -25,7 +25,7 @@ # the html code of the start page. It is built only once per FreeCAD session for now... import six -import sys,os,FreeCAD,FreeCADGui,tempfile,time,zipfile,urllib,re +import sys,os,FreeCAD,FreeCADGui,tempfile,time,zipfile,re from . import TranslationTexts from PySide import QtCore,QtGui @@ -107,7 +107,7 @@ def getInfo(filename): try: import gnome.ui import gnomevfs - except: + except Exception: # alternative method import hashlib fhash = hashlib.md5(("file://"+path).encode("utf8")).hexdigest() @@ -140,7 +140,7 @@ def getInfo(filename): if filename.lower().endswith(".fcstd"): try: zfile=zipfile.ZipFile(filename) - except: + except Exception: print("Cannot read file: ",filename) return None files=zfile.namelist() @@ -366,7 +366,6 @@ def handle(): # build SECTION_RECENTFILES - SECTION_RECENTFILES = encode("") rf = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/RecentFiles") rfcount = rf.GetInt("RecentFiles",0) SECTION_RECENTFILES = encode("

        "+TranslationTexts.T_RECENTFILES+"

        ") @@ -493,7 +492,7 @@ def handle(): img = os.path.join(resources_dir,"images/freecad.png") iconbank[wb] = img UL_WORKBENCHES += '
      • ' - UL_WORKBENCHES += ' ' + UL_WORKBENCHES += ' ' UL_WORKBENCHES += ''+wn.replace("ReverseEngineering","ReverseEng")+'' UL_WORKBENCHES += '
      • ' UL_WORKBENCHES += '
      ' @@ -503,19 +502,19 @@ def handle(): try: import dxfLibrary - except: + except Exception: pass else: wblist.append("dxf-library") try: import RebarTools - except: + except Exception: pass else: wblist.append("reinforcement") try: import CADExchangerIO - except: + except Exception: pass else: wblist.append("cadexchanger") diff --git a/src/Mod/Surface/Gui/Command.cpp b/src/Mod/Surface/Gui/Command.cpp index abc31b9b23..705a6a822f 100644 --- a/src/Mod/Surface/Gui/Command.cpp +++ b/src/Mod/Surface/Gui/Command.cpp @@ -171,7 +171,7 @@ CmdSurfaceGeomFillSurface::CmdSurfaceGeomFillSurface() sToolTipText = QT_TR_NOOP("Creates a surface from two, three or four boundary edges."); sWhatsThis = "Surface_GeomFillSurface"; sStatusTip = sToolTipText; - sPixmap = "Surface_BSplineSurface"; + sPixmap = "Surface_GeomFillSurface"; } bool CmdSurfaceGeomFillSurface::isActive(void) @@ -237,7 +237,7 @@ CmdSurfaceExtendFace::CmdSurfaceExtendFace() "with its local U and V parameters."); sWhatsThis = "Surface_ExtendFace"; sStatusTip = sToolTipText; - sPixmap = "Surface_Extend"; + sPixmap = "Surface_ExtendFace"; } void CmdSurfaceExtendFace::activated(int) diff --git a/src/Mod/Surface/Gui/Resources/Surface.qrc b/src/Mod/Surface/Gui/Resources/Surface.qrc index c09283aa91..7af306fa62 100644 --- a/src/Mod/Surface/Gui/Resources/Surface.qrc +++ b/src/Mod/Surface/Gui/Resources/Surface.qrc @@ -4,8 +4,9 @@ icons/Surface_BSplineSurface.svg icons/Surface_CurveOnMesh.svg icons/Surface_Cut.svg - icons/Surface_Extend.svg + icons/Surface_ExtendFace.svg icons/Surface_Filling.svg + icons/Surface_GeomFillSurface.svg icons/Surface_Sections.svg icons/Surface_Sewing.svg icons/Surface_Surface.svg diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_BSplineSurface.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_BSplineSurface.svg index 3e9b34bee4..a2644e009c 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_BSplineSurface.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_BSplineSurface.svg @@ -7,2106 +7,111 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - id="svg3364" + id="svg2985" height="64px" width="64px"> Surface_BSplineSurface + id="title889">Surface_BSplineSurface + id="defs2987"> + id="linearGradient4387"> - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + id="radialGradient3692" + xlink:href="#linearGradient3377" /> + id="linearGradient3377"> + id="stop3379" /> + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)" + gradientUnits="userSpaceOnUse" + id="radialGradient6412" + xlink:href="#linearGradient3377-3" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient3036"> + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + id="metadata2990"> @@ -2114,127 +119,75 @@ Surface_BSplineSurface - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_BSplineSurface.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface that has a thick, red, highlighted edge. This edge has three vertices indicated as circles as if it was a multipoint spline. It is based on the 'Surface' icon. + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + - surface - curve - spline - points + - - - - - - - - - + d="M 13.816245,52.162367 C 28.338105,17.330258 44.796216,62.805512 56.413699,28.940961" + style="fill:none;stroke:#fce94f;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - - - - - - - - - - - - + id="path3820-1-9-6" + d="M 5.4725393,24.503343 13.365603,45.716252 C 30.88904,17.984227 44.167745,56.981787 53.473496,28.274182 L 39.51487,7.9294358 C 31.707324,28.541126 16.157527,5.2639846 5.4725393,24.503343 Z" + style="display:inline;fill:none;fill-opacity:1;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_BezierSurface.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_BezierSurface.svg index d6abc7b517..d5179839bc 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_BezierSurface.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_BezierSurface.svg @@ -6,2307 +6,112 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - width="64px" + version="1.1" + id="svg2985" height="64px" - id="svg3364" - version="1.1"> + width="64px"> Surface_BezierSurface + id="title889">Surface_BezierSurface + id="defs2987"> + id="linearGradient4387"> - - - - - - + + + + - + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + id="radialGradient3692" + xlink:href="#linearGradient3377" /> + id="linearGradient3377"> + id="stop3379" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)" + gradientUnits="userSpaceOnUse" + id="radialGradient6412" + xlink:href="#linearGradient3377-3" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient3036"> + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + id="metadata2990"> @@ -2314,109 +119,91 @@ Surface_BezierSurface - - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_BezierSurface.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface, with one thick, red highlighted edge. The endpoints of this edge are marked with squares, which are tied to handles indicating control points for a bezier curve. It is based on the 'Surface' icon. + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + surface - curve - bezier - handles + - - - - - - - - - + style="display:inline;fill:url(#linearGradient2095);fill-opacity:1;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 3.1519208,24.413282 13.801284,52.472481 C 28.323144,17.640372 44.781255,63.115626 56.398738,29.251075 L 38.972508,3.1269928 C 30.809307,30.407148 17.749067,-0.16084869 3.1519208,24.413282 Z" + id="path3820-1-9" /> + style="fill:none;stroke:#172a04;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 13.801284,52.472481 C 28.323144,17.640372 44.781255,63.115626 56.398738,29.251075" + id="path3764" /> + id="path1219" + d="M 13.816245,52.162367 C 28.338105,17.330258 44.796216,62.805512 56.413699,28.940961" + style="fill:none;stroke:#fce94f;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + id="path3867" + d="M 48.620445,57.157022 55.854309,32.153751" + style="fill:#ce5c00;stroke:#ce5c00;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + id="path3820-1-9-6" + d="M 5.4725393,24.503343 13.365603,45.716252 C 31.320659,18.292526 44.167745,56.981787 53.473496,28.274182 L 39.51487,7.9294358 C 31.707324,28.541126 16.157527,5.2639846 5.4725393,24.503343 Z" + style="display:inline;fill:none;fill-opacity:1;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + + style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + style="fill:#8ae234;fill-opacity:1;fill-rule:nonzero;stroke:#172a04;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + style="fill:#8ae234;fill-opacity:1;fill-rule:nonzero;stroke:#172a04;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + style="fill:#fcaf3e;fill-opacity:1;stroke:#ce5c00;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_CurveOnMesh.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_CurveOnMesh.svg index c6bf9bf8ba..d2611f592f 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_CurveOnMesh.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_CurveOnMesh.svg @@ -7,2106 +7,111 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - id="svg3364" + id="svg2985" height="64px" width="64px"> Surface_CurveOnMesh + id="title866">Surface_CurveOnMesh + id="defs2987"> + id="linearGradient4387"> - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + id="radialGradient3692" + xlink:href="#linearGradient3377" /> + id="linearGradient3377"> + id="stop3379" /> + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)" + gradientUnits="userSpaceOnUse" + id="radialGradient6412" + xlink:href="#linearGradient3377-3" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient3036"> + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + id="metadata2990"> @@ -2114,95 +119,86 @@ Surface_CurveOnMesh - 2020-09-30 - [vocx] + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_CurveOnMesh.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface that has a thick, red, highlighted curve on top of, it in the middle of the shape. The surface has mesh lines. It is based on the 'Surface' icon. + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - - mesh - curve - spline - middle + surface + - - - - - - - - - + d="M 3.1519208,24.413282 17.899691,60.320494 c 14.52186,-34.832109 31.154371,7.93994 42.771854,-25.924611 L 38.972508,3.1269928 C 30.809307,30.407148 17.749067,-0.16084869 3.1519208,24.413282 Z" + style="display:inline;fill:url(#linearGradient2095);fill-opacity:1;stroke:#172a04;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + id="path864" + d="M 13.603223,48.657681 Z" + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="display:inline;fill:none;fill-opacity:1;stroke:#8ae234;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 5.3492197,24.626663 18.113409,55.643482 C 32.677175,26.678261 49.470489,63.641047 58.406281,34.501823 L 39.63819,7.4361573 C 30.967406,29.589343 16.774125,4.5857267 5.3492197,24.626663 Z" + id="path3820-1-9-6" /> + style="fill:none;stroke:#4e9a06;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 14.551524,40.360731 c 4.206197,-6.239691 10.886783,-4.698556 12.49142,-4.817808" + id="path891" /> + id="path859" + d="M 9.0688148,17.178428 24.67764,49.965681" + style="fill:#0b1521;stroke:#172a04;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - - - - + id="path861" + d="M 22.497637,14.213624 40.286466,47.088077" + style="fill:none;stroke:#172a04;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + + + + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Cut.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Cut.svg index cee2cf4fce..8a16b39b90 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Cut.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_Cut.svg @@ -7,3683 +7,111 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - id="svg3364" + id="svg2985" height="64px" width="64px"> Surface_Cut + id="title889">Surface_Cut + id="defs2987"> + id="linearGradient4387"> - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + id="radialGradient3692" + xlink:href="#linearGradient3377" /> + id="linearGradient3377"> + id="stop3379" /> + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + id="radialGradient6412" + xlink:href="#linearGradient3377-3" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient3036"> + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + x1="47" + y1="9" + x2="7" + y2="28" + gradientUnits="userSpaceOnUse" /> + + + + + id="metadata2990"> @@ -3691,86 +119,69 @@ Surface_Cut - - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Cut.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface. A semi-transparent plane cuts the curve in two. The position of the cut is marked with a red thick edge on the curve. It is based on the 'Surface' icon. + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + surface - plane - cut + - - - - - - - - + style="display:inline;fill:url(#linearGradient2095);fill-opacity:1;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 3.1519208,24.413282 17.899691,60.320494 c 14.52186,-34.832109 31.154371,7.93994 42.771854,-25.924611 L 38.972508,3.1269928 C 30.809307,30.407148 17.749067,-0.16084869 3.1519208,24.413282 Z" + id="path3820-1-9" /> + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 13.603223,48.657681 Z" + id="path864" /> + id="path3820-1-9-6" + d="M 5.3492197,24.626663 18.113409,55.643482 C 32.677175,26.678261 49.470489,63.641047 58.406281,34.501823 L 39.63819,7.4361573 C 30.967406,29.589343 16.774125,4.5857267 5.3492197,24.626663 Z" + style="display:inline;fill:none;fill-opacity:1;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + style="opacity:0.7175;fill:#ef2929;stroke:#280000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 57.281971,22.709091 57.096986,3.6995887 3.097977,11.384814 2.9746572,44.456724 9.7698774,41.423451 C 17.231743,18.328355 39.666029,41.309915 50.005085,20.047261 l 3.975198,3.989905 z" + id="path866" /> + id="path866-8" + d="M 57.281971,22.709091 57.096992,3.6995881 3.097977,11.384814 2.9746572,44.456724 9.7698772,41.423451 C 17.223185,18.328171 39.853014,41.194251 50.390456,19.754377 l 3.296943,4.452353 z" + style="opacity:1;fill:none;stroke:#280000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + id="path857-1" + d="M 50.227282,19.009631 C 40.095257,43.882552 19.635873,17.890993 9.5920156,41.158467" + style="fill:none;stroke:#0b1521;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + style="fill:none;stroke:#fce94f;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 50.140081,19.053231 C 40.008056,43.926152 19.548672,17.934593 9.5048155,41.202067" + id="path857" /> diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Extend.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Extend.svg deleted file mode 100644 index fb03b99117..0000000000 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Extend.svg +++ /dev/null @@ -1,5767 +0,0 @@ - - - Surface_Extend - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - Surface_Extend - 2020-09-30 - - - [vocx] - - - - - CC-BY-SA 4.0 - - - - - FreeCAD - - - http://www.freecadweb.org/wiki/index.php?title=Artwork - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Extend.svg - A purple curved surface that contains a smaller surface. The smaller surface has Its four edges in thick, red highlighted. Gray lines extend from the smaller, inner surface to the exterior one. It is based on the 'Surface' icon. - - - - - - - - - surface - curve - extrapolation - highlights - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_ExtendFace.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_ExtendFace.svg new file mode 100644 index 0000000000..d7af2c7f2d --- /dev/null +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_ExtendFace.svg @@ -0,0 +1,211 @@ + + + Surface_ExtendFace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Surface_ExtendFace + + + [bitacovir] + + + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + + + + + + surface + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Filling.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Filling.svg index 791cf3cff3..5daf7464f8 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Filling.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_Filling.svg @@ -6,1795 +6,89 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - version="1.1" - id="svg3364" + width="64px" height="64px" - width="64px"> + id="svg2985" + version="1.1"> Surface_Filling + id="title889">Surface_Filling + id="defs2987"> - - - - - - + id="linearGradient4387"> + id="stop4389" /> + id="stop4391" /> - - - - - - - - - - - - - - - - - + id="linearGradient6321"> + id="stop6323" /> - + id="stop6325" /> - - - - - - - - - + gradientTransform="translate(-0.23443224,0.23443198)" /> + id="linearGradient3377"> + style="stop-color:#faff2b;stop-opacity:1;" /> + style="stop-color:#ffaa00;stop-opacity:1;" /> + id="linearGradient3377-3"> + style="stop-color:#faff2b;stop-opacity:1;" /> + style="stop-color:#ffaa00;stop-opacity:1;" /> + gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)" + cx="45.883327" + cy="28.869568" + fx="45.883327" + fy="28.869568" + r="19.467436" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient3036"> + + + - - - - - - - - - - + xlink:href="#linearGradient1189" + gradientTransform="matrix(0.96812402,0,0,0.96755864,-0.72057496,-2.6783592)" /> + + + + + id="metadata2990"> @@ -1913,100 +119,61 @@ Surface_Filling - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - http://www.freecadweb.org/wiki/index.php?title=Artwork - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Filling.svg - A purple curved surface. Its four edges are thick and highlighted in red. It is based on the 'Surface' icon. + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + - surface - curve - edges - highlights + - - - - - - - - + id="path3820-1-9" + d="M 3.1519208,24.413282 17.899691,60.320494 c 14.52186,-34.832109 31.154371,7.93994 42.771854,-25.924611 L 38.972508,3.1269928 C 30.809307,30.407148 17.749067,-0.16084869 3.1519208,24.413282 Z" + style="display:inline;fill:url(#linearGradient2095);fill-opacity:1;stroke:#302b00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + style="display:inline;fill:none;fill-opacity:1;stroke:#fce94f;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 6.0274776,24.811642 18.175069,54.286966 C 33.540412,25.445065 49.03887,62.222871 57.666363,34.995102 L 39.76151,8.7926732 C 30.227488,30.267601 17.452383,4.7707061 6.0274776,24.811642 Z" + id="path3820-1-9-6" /> + style="fill:none;stroke:#302b00;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 8.3549048,24.972224 18.621264,49.266191 C 31.716699,27.213603 50.17526,55.741537 55.247193,35.269413 L 40.202199,12.856071 C 30.336022,29.759966 16.753608,10.080699 8.3549048,24.972224 Z" + id="path857" /> + style="fill:none;stroke:#0b1521;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 50.052882,19.009632 C 39.920857,43.882553 19.461473,17.890994 9.4176161,41.158468" + id="path857-1" /> - - - - - + id="path857-3" + d="M 49.965681,19.053232 C 39.833656,43.926153 19.374272,17.934594 9.3304161,41.202068" + style="fill:none;stroke:#fce94f;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_GeomFillSurface.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_GeomFillSurface.svg new file mode 100644 index 0000000000..2c6e085a7b --- /dev/null +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_GeomFillSurface.svg @@ -0,0 +1,171 @@ + + + Surface_GeomFillSurface + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Surface_GeomFillSurface + + + [bitacovir] + + + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + + + + + + surface + + + + + + + + + + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Sections.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Sections.svg index 95c5529f4a..f84f5b73fe 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Sections.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_Sections.svg @@ -6,2754 +6,150 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - width="64px" + version="1.1" + id="svg2985" height="64px" - id="svg3364" - version="1.1"> + width="64px"> Surface_Sections + id="title889">Surface_Sections + id="defs2987"> + id="linearGradient873"> + id="stop869" /> - - + id="stop871" /> + id="linearGradient863"> + + + + + + + + - + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + id="radialGradient3692" + xlink:href="#linearGradient3377" /> + id="linearGradient3377"> + id="stop3379" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)" + gradientUnits="userSpaceOnUse" + id="radialGradient6412" + xlink:href="#linearGradient3377-3" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient3036"> + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + x1="47" + y1="9" + x2="7" + y2="28" + gradientUnits="userSpaceOnUse" /> + + + + + + + id="metadata2990"> @@ -2761,139 +157,73 @@ Surface_Sections - - 2020-09-29 - [vocx] + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Sections.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + + + surface - curve - sections - edges - A purple surface made with several highlighted red edges which represent transversal sections of the surface. + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Sewing.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Sewing.svg index cf8e3b2e59..7411c1dcc6 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Sewing.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_Sewing.svg @@ -7,1806 +7,111 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - id="svg3364" + id="svg2985" height="64px" width="64px"> Surface_Sewing + id="title889">Surface_Sewing + id="defs2987"> + id="linearGradient4387"> - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + r="19.467436" + fy="28.869568" + fx="45.883327" + cy="28.869568" + cx="45.883327" + id="radialGradient3692" + xlink:href="#linearGradient3377" /> + id="linearGradient3377"> + id="stop3379" /> + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + id="radialGradient6412" + xlink:href="#linearGradient3377-3" /> + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient1189"> + + + + id="metadata2990"> @@ -1814,99 +119,73 @@ Surface_Sewing - - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Sewing.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface, with black edges, and one edge passing through the middle, dividing the curve in two parts. Six straight lines cross the middle edge, indicating stitches. It is based on the 'Surface' icon. + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + surface - curve - edges + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Surface.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Surface.svg index a0f51cfc3f..b3217de204 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Surface.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_Surface.svg @@ -7,1697 +7,111 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - id="svg3364" + id="svg2985" height="64px" width="64px"> Surface_Surface + id="title889">Surface_Surface + id="defs2987"> + id="linearGradient4387"> - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + id="linearGradient3377"> + id="stop3379" /> + id="stop3381" /> + id="linearGradient3377-3"> + id="stop3379-8" /> + id="stop3381-3" /> + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient1189"> + + + + id="metadata2990"> @@ -1705,75 +119,53 @@ Surface_Surface - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Surface.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface, with a linear gradient, and some highlights in light and dark purple. - + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + surface - curve - wave + - - - - - - - - - - - - - + + + + + diff --git a/src/Mod/Surface/Gui/Resources/icons/Surface_Workbench.svg b/src/Mod/Surface/Gui/Resources/icons/Surface_Workbench.svg index 35053ff75c..44e6791ec6 100644 --- a/src/Mod/Surface/Gui/Resources/icons/Surface_Workbench.svg +++ b/src/Mod/Surface/Gui/Resources/icons/Surface_Workbench.svg @@ -6,1698 +6,112 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - version="1.1" - id="svg3364" + width="64px" height="64px" - width="64px"> + id="svg2985" + version="1.1"> Surface_Workbench + id="title889">Surface_Workbench + id="defs2987"> - - - - - - + id="linearGradient4387"> + id="stop4389" /> + id="stop4391" /> - - - - - - - - - - - - - - - - - + id="linearGradient6321"> + id="stop6323" /> - - - - - - - - - + id="stop6325" /> + xlink:href="#linearGradient3377" + id="radialGradient3692" + cx="45.883327" + cy="28.869568" + fx="45.883327" + fy="28.869568" + r="19.467436" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-0.23443224,0.23443198)" /> + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + x2="7" + y1="9" + x1="47" + id="linearGradient2095" + xlink:href="#linearGradient1189" + gradientTransform="matrix(0.96812402,0,0,0.96755864,-0.72057496,-2.6783592)" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient1189"> + + + + id="metadata2990"> @@ -1705,75 +119,53 @@ Surface_Workbench - 2017-04-17 - Nate Miller + [bitacovir] - - - CC-BY-SA 4.0 - - + Part_Shape_from_Mesh + 2020/10/03 + http://www.freecadweb.org/wiki/index.php?title=Artwork FreeCAD - FreeCAD/src/Mod/Surface/Gui/Resources/icons/Surface_Workbench.svg - http://www.freecadweb.org/wiki/index.php?title=Artwork - A purple curved surface, with a linear gradient, and some highlights in light and dark purple. It is based on the 'Surface' icon. - + + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html - [vocx] + surface - curve - wave + - - - - - - - - - - - - - + + + + + diff --git a/src/Mod/TechDraw/App/QDomNodeModel.cpp b/src/Mod/TechDraw/App/QDomNodeModel.cpp index 1ecfa48d04..25a704a174 100644 --- a/src/Mod/TechDraw/App/QDomNodeModel.cpp +++ b/src/Mod/TechDraw/App/QDomNodeModel.cpp @@ -195,9 +195,10 @@ QXmlName QDomNodeModel::name ( const QXmlNodeModelIndex & ni ) const return QXmlName(m_Pool, n.nodeName(), QString(), QString()); } -QVector QDomNodeModel::namespaceBindings(const QXmlNodeModelIndex & ni ) const +QVector QDomNodeModel::namespaceBindings(const QXmlNodeModelIndex & ni) const { QDomNode n = toDomNode(ni); + bool xmlNamespaceWasDefined = false; QVector res; while (!n.isNull()) @@ -219,14 +220,27 @@ QVector QDomNodeModel::namespaceBindings(const QXmlNodeModelIndex & ni for (x = 0; x < res.size(); ++x) if (res.at(x).prefix(m_Pool) == p) break; - if (x >= res.size()) + if (x >= res.size()) { res.append(QXmlName(m_Pool, QString::fromUtf8("xmlns"), attrs.item(i).nodeValue(), p)); - } + if (p == QString::fromLatin1("xml")) + xmlNamespaceWasDefined = true; + } + } } n = n.parentNode(); } + // Per the XML standard: + // "The prefix xml is by definition bound to the namespace name http://www.w3.org/XML/1998/namespace. It MAY, but + // need not, be declared, and MUST NOT be bound to any other namespace name. Other prefixes MUST NOT be bound to + // this namespace name, and it MUST NOT be declared as the default namespace." + // + // If the document does not specifically include this namespace, add it now: + if (!xmlNamespaceWasDefined) { + res.append(QXmlName(m_Pool, QString::fromUtf8("xmlns"), QString::fromLatin1("http://www.w3.org/XML/1998/namespace"), QString::fromLatin1("xml"))); + } + return res; } diff --git a/src/Mod/TechDraw/Gui/Command.cpp b/src/Mod/TechDraw/Gui/Command.cpp index 687fd905ee..8fc52fbdd7 100644 --- a/src/Mod/TechDraw/Gui/Command.cpp +++ b/src/Mod/TechDraw/Gui/Command.cpp @@ -183,11 +183,13 @@ CmdTechDrawPageTemplate::CmdTechDrawPageTemplate() void CmdTechDrawPageTemplate::activated(int iMsg) { Q_UNUSED(iMsg); + QString work_dir = Gui::FileDialog::getWorkingDirectory(); QString templateDir = Preferences::defaultTemplateDir(); 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)"))); + Gui::FileDialog::setWorkingDirectory(work_dir); // Don't overwrite WD with templateDir if (templateFileName.isEmpty()) { return; diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui index 105a38b0a7..8838d9990d 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui @@ -569,6 +569,9 @@ Show Center Marks + + true + ShowCenterMarks diff --git a/src/Mod/TechDraw/Templates/Arch_A_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_A_Landscape.svg new file mode 100644 index 0000000000..6659dfce8b --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_A_Landscape.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch A + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_A_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_A_Portrait.svg new file mode 100644 index 0000000000..253821f556 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_A_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch A + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_B_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_B_Landscape.svg new file mode 100644 index 0000000000..caa5a6faca --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_B_Landscape.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch B + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_B_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_B_Portrait.svg new file mode 100644 index 0000000000..912e8be8b3 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_B_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch B + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_C_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_C_Landscape.svg new file mode 100644 index 0000000000..ae6791964e --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_C_Landscape.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch C + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_C_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_C_Portrait.svg new file mode 100644 index 0000000000..e0dd6cae46 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_C_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch C + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_D_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_D_Landscape.svg new file mode 100644 index 0000000000..d3660f8a4d --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_D_Landscape.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch D + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_D_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_D_Portrait.svg new file mode 100644 index 0000000000..d4087adbb3 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_D_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch D + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E1_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_E1_Landscape.svg new file mode 100644 index 0000000000..b9358b6ab0 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E1_Landscape.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E1 + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E1_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_E1_Portrait.svg new file mode 100644 index 0000000000..6e3967ae15 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E1_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E1 + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E2_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_E2_Landscape.svg new file mode 100644 index 0000000000..4711cf77c9 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E2_Landscape.svg @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E2 + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E2_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_E2_Portrait.svg new file mode 100644 index 0000000000..dab40ec5f6 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E2_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E2 + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E3_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_E3_Landscape.svg new file mode 100644 index 0000000000..71184d1dd9 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E3_Landscape.svg @@ -0,0 +1,358 @@ + + + _________ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E3 + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E3_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_E3_Portrait.svg new file mode 100644 index 0000000000..55af04f93e --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E3_Portrait.svg @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E3 + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Templates/Arch_E_Landscape.svg b/src/Mod/TechDraw/Templates/Arch_E_Landscape.svg new file mode 100644 index 0000000000..3e535aa440 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E_Landscape.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ + diff --git a/src/Mod/TechDraw/Templates/Arch_E_Portrait.svg b/src/Mod/TechDraw/Templates/Arch_E_Portrait.svg new file mode 100644 index 0000000000..93e7ffae38 --- /dev/null +++ b/src/Mod/TechDraw/Templates/Arch_E_Portrait.svg @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _________ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AUTHOR NAME + CREATION DATE + SUPERVISOR NAME + CHECK DATE + Arch E + SCALE + WEIGHT + NUMBER + SHEET + TITLE + SUBTITLE + COMPANY NAME + COPYRIGHT + _________ + _________ + _________ + _________ + _________ + _________ + _________ + _________ +