From 9f9ee6d89e132d22a84d7322022ea28feec11480 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 20 Jun 2020 11:28:48 +0200 Subject: [PATCH 01/11] Base: [skip ci] include sstream in swigpyrun source files --- src/Base/swigpyrun_1.3.25.cpp | 1 + src/Base/swigpyrun_1.3.33.cpp | 1 + src/Base/swigpyrun_1.3.36.cpp | 1 + src/Base/swigpyrun_1.3.38.cpp | 1 + src/Base/swigpyrun_1.3.40.cpp | 1 + 5 files changed, 5 insertions(+) diff --git a/src/Base/swigpyrun_1.3.25.cpp b/src/Base/swigpyrun_1.3.25.cpp index 3e6d07d5bd..0e83735de0 100644 --- a/src/Base/swigpyrun_1.3.25.cpp +++ b/src/Base/swigpyrun_1.3.25.cpp @@ -24,6 +24,7 @@ #include "PreCompiled.h" #include "PyExport.h" #include "Exception.h" +#include #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-register" diff --git a/src/Base/swigpyrun_1.3.33.cpp b/src/Base/swigpyrun_1.3.33.cpp index 21a94f9ac3..9e6c509ec4 100644 --- a/src/Base/swigpyrun_1.3.33.cpp +++ b/src/Base/swigpyrun_1.3.33.cpp @@ -24,6 +24,7 @@ #include "PreCompiled.h" #include "PyExport.h" #include "Exception.h" +#include #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-register" diff --git a/src/Base/swigpyrun_1.3.36.cpp b/src/Base/swigpyrun_1.3.36.cpp index b4374c3b97..7a35a164f3 100644 --- a/src/Base/swigpyrun_1.3.36.cpp +++ b/src/Base/swigpyrun_1.3.36.cpp @@ -24,6 +24,7 @@ #include "PreCompiled.h" #include "PyExport.h" #include "Exception.h" +#include #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-register" diff --git a/src/Base/swigpyrun_1.3.38.cpp b/src/Base/swigpyrun_1.3.38.cpp index 26ac8f6c8c..59d0f41d40 100644 --- a/src/Base/swigpyrun_1.3.38.cpp +++ b/src/Base/swigpyrun_1.3.38.cpp @@ -24,6 +24,7 @@ #include "PreCompiled.h" #include "PyExport.h" #include "Exception.h" +#include #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-register" diff --git a/src/Base/swigpyrun_1.3.40.cpp b/src/Base/swigpyrun_1.3.40.cpp index e11f174d71..09e87c88b9 100644 --- a/src/Base/swigpyrun_1.3.40.cpp +++ b/src/Base/swigpyrun_1.3.40.cpp @@ -24,6 +24,7 @@ #include "PreCompiled.h" #include "PyExport.h" #include "Exception.h" +#include #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-register" From 278202eb6ff26145c12f8c3efd715bb38ebb2bb3 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 20 Jun 2020 11:43:59 +0200 Subject: [PATCH 02/11] GuiPy: [skip ci] handle some Qt warnings when using GUI from Python + avoid warning: QEventLoop: Cannot be used without QApplication + avoid warning: QObject::startTimer: Timers can only be used with threads started with QThread --- src/Gui/Application.cpp | 9 ++++++--- src/Main/FreeCADGuiPy.cpp | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 66524c71af..dbb6c88a3d 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -147,14 +147,17 @@ namespace Gui { // Pimpl class struct ApplicationP { - ApplicationP() : + ApplicationP(bool GUIenabled) : activeDocument(0L), editDocument(0L), isClosing(false), startingUp(true) { // create the macro manager - macroMngr = new MacroManager(); + if (GUIenabled) + macroMngr = new MacroManager(); + else + macroMngr = nullptr; } ~ApplicationP() @@ -451,7 +454,7 @@ Application::Application(bool GUIenabled) View3DInventorViewerPy ::init_type(); AbstractSplitViewPy ::init_type(); - d = new ApplicationP; + d = new ApplicationP(GUIenabled); // global access Instance = this; diff --git a/src/Main/FreeCADGuiPy.cpp b/src/Main/FreeCADGuiPy.cpp index 55646793f4..3d7b5488a0 100644 --- a/src/Main/FreeCADGuiPy.cpp +++ b/src/Main/FreeCADGuiPy.cpp @@ -52,10 +52,30 @@ #include #include #include +#include +#include + +static bool _isSetupWithoutGui = false; static QWidget* setupMainWindow(); +class QtApplication : public QApplication { +public: + QtApplication(int &argc, char **argv) + : QApplication(argc, argv) { + } + bool notify (QObject * receiver, QEvent * event) { + try { + return QApplication::notify(receiver, event); + } + catch (const Base::SystemExitException& e) { + exit(e.getExitCode()); + return true; + } + } +}; + #if defined(Q_OS_WIN) HHOOK hhook; @@ -70,6 +90,11 @@ FilterProc(int nCode, WPARAM wParam, LPARAM lParam) { static PyObject * FreeCADGui_showMainWindow(PyObject * /*self*/, PyObject *args) { + if (_isSetupWithoutGui) { + PyErr_SetString(PyExc_RuntimeError, "Cannot call showMainWindow() after calling setupWithoutGUI()\n"); + return nullptr; + } + PyObject* inThread = Py_False; if (!PyArg_ParseTuple(args, "|O!", &PyBool_Type, &inThread)) return NULL; @@ -84,7 +109,7 @@ FreeCADGui_showMainWindow(PyObject * /*self*/, PyObject *args) #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); #endif - QApplication app(argc, argv); + QtApplication app(argc, argv); if (setupMainWindow()) { app.exec(); } @@ -95,14 +120,14 @@ FreeCADGui_showMainWindow(PyObject * /*self*/, PyObject *args) #if defined(Q_OS_WIN) static int argc = 0; static char **argv = {0}; - (void)new QApplication(argc, argv); + (void)new QtApplication(argc, argv); // When QApplication is constructed hhook = SetWindowsHookEx(WH_GETMESSAGE, FilterProc, 0, GetCurrentThreadId()); #elif !defined(QT_NO_GLIB) static int argc = 0; static char **argv = {0}; - (void)new QApplication(argc, argv); + (void)new QtApplication(argc, argv); #else PyErr_SetString(PyExc_RuntimeError, "Must construct a QApplication before a QPaintDevice\n"); return NULL; @@ -154,6 +179,7 @@ FreeCADGui_setupWithoutGUI(PyObject * /*self*/, PyObject *args) if (!Gui::Application::Instance) { static Gui::Application *app = new Gui::Application(false); + _isSetupWithoutGui = true; Q_UNUSED(app); } else { @@ -164,7 +190,8 @@ FreeCADGui_setupWithoutGUI(PyObject * /*self*/, PyObject *args) if (!SoDB::isInitialized()) { // init the Inventor subsystem SoDB::init(); - SIM::Coin3D::Quarter::Quarter::init(); + SoNodeKit::init(); + SoInteraction::init(); } if (!Gui::SoFCDB::isInitialized()) { Gui::SoFCDB::init(); From 8b32ea8dabcd65f61cdb6bc3597732290e96a970 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 20 Jun 2020 12:22:24 +0200 Subject: [PATCH 03/11] GuiPy: [skip ci] expose function to Python to replace Switch with Separator nodes --- src/Gui/Application.cpp | 44 +++++++++++++++++++++++++++++++++++++++++ src/Gui/SoFCDB.cpp | 5 +++++ src/Gui/SoFCDB.h | 1 + 3 files changed, 50 insertions(+) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index dbb6c88a3d..1ba99e4382 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -230,6 +230,47 @@ FreeCADGui_subgraphFromObject(PyObject * /*self*/, PyObject *args) return Py_None; } +static PyObject * +FreeCADGui_replaceSwitchNodes(PyObject * /*self*/, PyObject *args) +{ + PyObject* proxy; + if (!PyArg_ParseTuple(args, "O", &proxy)) + return nullptr; + + void* ptr = 0; + try { + Base::Interpreter().convertSWIGPointerObj("pivy.coin", "SoNode *", proxy, &ptr, 0); + SoNode* node = reinterpret_cast(ptr); + SoNode* replace = SoFCDB::replaceSwitches(node); + if (replace) { + replace->ref(); + + std::string prefix = "So"; + std::string type = replace->getTypeId().getName().getString(); + // doesn't start with the prefix 'So' + if (type.rfind("So", 0) != 0) { + type = prefix + type; + } + else if (type == "SoFCSelectionRoot") { + type = "SoSeparator"; + } + + type += " *"; + PyObject* proxy = 0; + proxy = Base::Interpreter().createSWIGPointerObj("pivy.coin", type.c_str(), (void*)replace, 1); + return Py::new_reference_to(Py::Object(proxy, true)); + } + else { + Py_INCREF(Py_None); + return Py_None; + } + } + catch (const Base::Exception& e) { + PyErr_SetString(PyExc_RuntimeError, e.what()); + return nullptr; + } +} + static PyObject * FreeCADGui_getSoDBVersion(PyObject * /*self*/, PyObject *args) { @@ -293,6 +334,9 @@ struct PyMethodDef FreeCADGui_methods[] = { {"subgraphFromObject",FreeCADGui_subgraphFromObject,METH_VARARGS, "subgraphFromObject(object) -> Node\n\n" "Return the Inventor subgraph to an object"}, + {"replaceSwitchNodes",FreeCADGui_replaceSwitchNodes,METH_VARARGS, + "replaceSwitchNodes(Node) -> Node\n\n" + "Replace Switch nodes with Separators"}, {"getSoDBVersion",FreeCADGui_getSoDBVersion,METH_VARARGS, "getSoDBVersion() -> String\n\n" "Return a text string containing the name\n" diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp index 3c9c7fd7be..e5477e9883 100644 --- a/src/Gui/SoFCDB.cpp +++ b/src/Gui/SoFCDB.cpp @@ -295,6 +295,11 @@ SoNode* replaceSwitchesInSceneGraph(SoNode* node) return node; } +SoNode* Gui::SoFCDB::replaceSwitches(SoNode* node) +{ + return replaceSwitchesInSceneGraph(node); +} + bool Gui::SoFCDB::writeToVRML(SoNode* node, const char* filename, bool binary) { SoNode* noSwitches = replaceSwitchesInSceneGraph(node); diff --git a/src/Gui/SoFCDB.h b/src/Gui/SoFCDB.h index 129f4e045d..932eab0572 100644 --- a/src/Gui/SoFCDB.h +++ b/src/Gui/SoFCDB.h @@ -40,6 +40,7 @@ public: static SbBool isInitialized(void); static void init(); static void finish(); + static SoNode* replaceSwitches(SoNode* node); /// helper to apply a SoWriteAction to a node and write it to a string static const std::string& writeNodesToString(SoNode * root); static bool writeToVRML(SoNode* node, const char* filename, bool binary); From e2efc1998291e417c1294159996def7990393b8d Mon Sep 17 00:00:00 2001 From: wandererfan Date: Thu, 18 Jun 2020 20:06:14 -0400 Subject: [PATCH 04/11] [TD]Detail default scale --- src/Mod/TechDraw/App/DrawViewDetail.cpp | 3 +-- src/Mod/TechDraw/Gui/TaskDetail.cpp | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewDetail.cpp b/src/Mod/TechDraw/App/DrawViewDetail.cpp index 929515d169..3ea133b289 100644 --- a/src/Mod/TechDraw/App/DrawViewDetail.cpp +++ b/src/Mod/TechDraw/App/DrawViewDetail.cpp @@ -117,6 +117,7 @@ DrawViewDetail::DrawViewDetail() //hide Properties not relevant to DVDetail Direction.setStatus(App::Property::ReadOnly,true); //Should be same as BaseView Rotation.setStatus(App::Property::ReadOnly,true); //same as BaseView + ScaleType.setValue("Custom"); //dvd uses scale from BaseView } DrawViewDetail::~DrawViewDetail() @@ -471,10 +472,8 @@ void DrawViewDetail::unsetupObject() if (base != nullptr) { base->requestPaint(); } - } - void DrawViewDetail::getParameters() { } diff --git a/src/Mod/TechDraw/Gui/TaskDetail.cpp b/src/Mod/TechDraw/Gui/TaskDetail.cpp index 90e2fb335a..b7a68669c7 100644 --- a/src/Mod/TechDraw/Gui/TaskDetail.cpp +++ b/src/Mod/TechDraw/Gui/TaskDetail.cpp @@ -472,6 +472,8 @@ void TaskDetail::createDetail() m_detailName.c_str(),m_baseName.c_str()); Gui::Command::doCommand(Command::Doc,"App.activeDocument().%s.XDirection = App.activeDocument().%s.XDirection", m_detailName.c_str(),m_baseName.c_str()); + Gui::Command::doCommand(Command::Doc,"App.activeDocument().%s.Scale = App.activeDocument().%s.Scale", + m_detailName.c_str(),m_baseName.c_str()); Gui::Command::doCommand(Command::Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", m_pageName.c_str(), m_detailName.c_str()); From d88d341d207c6cbe6f695c163912a02826f50679 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Sat, 20 Jun 2020 10:12:14 -0400 Subject: [PATCH 05/11] [TD]Cosmetic Edge endpoint inversion --- src/Mod/TechDraw/App/CosmeticEdgePyImp.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Mod/TechDraw/App/CosmeticEdgePyImp.cpp b/src/Mod/TechDraw/App/CosmeticEdgePyImp.cpp index 7860d67c17..c2d70dad5a 100644 --- a/src/Mod/TechDraw/App/CosmeticEdgePyImp.cpp +++ b/src/Mod/TechDraw/App/CosmeticEdgePyImp.cpp @@ -235,7 +235,6 @@ void CosmeticEdgePy::setStart(Py::Object arg) pNew = DrawUtil::invertY(pNew); Base::Vector3d pEnd = getCosmeticEdgePtr()->permaEnd; - pEnd = DrawUtil::invertY(pEnd); gp_Pnt gp1(pNew.x,pNew.y,pNew.z); gp_Pnt gp2(pEnd.x,pEnd.y,pEnd.z); TopoDS_Edge e = BRepBuilderAPI_MakeEdge(gp1, gp2); @@ -269,7 +268,6 @@ void CosmeticEdgePy::setEnd(Py::Object arg) pNew = DrawUtil::invertY(pNew); Base::Vector3d pStart = getCosmeticEdgePtr()->permaStart; - pStart = DrawUtil::invertY(pStart); gp_Pnt gp1(pNew.x,pNew.y,pNew.z); gp_Pnt gp2(pStart.x,pStart.y,pStart.z); TopoDS_Edge e = BRepBuilderAPI_MakeEdge(gp2, gp1); From f525fedd119d88e8ee936670fad62c30eb20d7ba Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 20 Jun 2020 14:27:04 -0500 Subject: [PATCH 06/11] Path: Slot operation fixes and improvements - Add `CutPattern` feature, defaulting to `ZigZag`. - Change inter-pass retractions to SafeHeight for multi-pass operations. - Fix division by zero instance for particular Perpendicular use case. - Synchronize property values with inputs in both the Property View and Tasks Editor windows. - Change `LayerMode` default value to `Multi-pass`. - Improve debug messaging. - Consolidate code and remove unused code. Path: fix exception thrown --- .../Gui/Resources/panels/PageOpSlotEdit.ui | 7 +- src/Mod/Path/PathScripts/PathSlot.py | 295 ++++++++++-------- src/Mod/Path/PathScripts/PathSlotGui.py | 157 +++++----- 3 files changed, 246 insertions(+), 213 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpSlotEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpSlotEdit.ui index a2466277ee..8d828b94de 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpSlotEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpSlotEdit.ui @@ -67,6 +67,9 @@ + + true + 0 @@ -77,7 +80,7 @@ <html><head/><body><p>Choose what point to use on the first selected feature.</p></body></html> - true + false @@ -159,7 +162,7 @@ <html><head/><body><p>Choose what point to use on the second selected feature.</p></body></html> - true + false diff --git a/src/Mod/Path/PathScripts/PathSlot.py b/src/Mod/Path/PathScripts/PathSlot.py index 8618e59ecd..fa9442dd41 100644 --- a/src/Mod/Path/PathScripts/PathSlot.py +++ b/src/Mod/Path/PathScripts/PathSlot.py @@ -46,8 +46,12 @@ Part = LazyLoader('Part', globals(), 'Part') if FreeCAD.GuiUp: import FreeCADGui -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +DEBUG = False +if DEBUG: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # Qt translation handling @@ -91,6 +95,7 @@ class ObjectSlot(PathOp.ObjectOp): # Set enumeration lists for enumeration properties if len(self.addNewProps) > 0: ENUMS = self.opPropertyEnumerations() + # ENUMS = self.getActiveEnumerations(obj) for n in ENUMS: if n in self.addNewProps: setattr(obj, n, ENUMS[n]) @@ -114,6 +119,8 @@ class ObjectSlot(PathOp.ObjectOp): QtCore.QT_TRANSLATE_NOOP("App::Property", "Enter custom start point for slot path.")), ("App::PropertyVectorDistance", "CustomPoint2", "Slot", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enter custom end point for slot path.")), + ("App::PropertyEnumeration", "CutPattern", "Slot", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the geometric clearing pattern to use for the operation.")), ("App::PropertyDistance", "ExtendPathStart", "Slot", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive extends the beginning of the path, negative shortens.")), ("App::PropertyDistance", "ExtendPathEnd", "Slot", @@ -138,6 +145,7 @@ class ObjectSlot(PathOp.ObjectOp): def opPropertyEnumerations(self): # Enumeration lists for App::PropertyEnumeration properties return { + 'CutPattern': ['Line', 'ZigZag'], 'LayerMode': ['Single-pass', 'Multi-pass'], 'PathOrientation': ['Start to End', 'Perpendicular'], 'Reference1': ['Center of Mass', 'Center of BoundBox', @@ -157,7 +165,8 @@ class ObjectSlot(PathOp.ObjectOp): 'CustomPoint2': FreeCAD.Vector(10.0, 10.0, 0.0), 'ExtendPathEnd': 0.0, 'Reference2': 'Center of Mass', - 'LayerMode': 'Single-pass', + 'LayerMode': 'Multi-pass', + 'CutPattern': 'ZigZag', 'PathOrientation': 'Start to End', 'ReverseDirection': False, @@ -167,27 +176,64 @@ class ObjectSlot(PathOp.ObjectOp): return defaults - def setEditorProperties(self, obj): - # Used to hide inputs in properties list - A = B = 2 + def getActiveEnumerations(self, obj): + """getActiveEnumerations(obj) ... + Method returns dictionary of property enumerations based on + active conditions in the operation.""" + ENUMS = self.opPropertyEnumerations() if hasattr(obj, 'Base'): - enums2 = self.opPropertyEnumerations()['Reference2'] if obj.Base: (base, subsList) = obj.Base[0] subCnt = len(subsList) if subCnt == 1: # Adjust available enumerations - obj.Reference1 = self._getReference1Enums(subsList[0], True) - A = 0 + ENUMS['Reference1'] = self._makeReference1Enumerations(subsList[0], True) elif subCnt == 2: # Adjust available enumerations - obj.Reference1 = self._getReference1Enums(subsList[0]) - obj.Reference2 = self._getReference2Enums(subsList[1]) + ENUMS['Reference1'] = self._makeReference1Enumerations(subsList[0]) + ENUMS['Reference2'] = self._makeReference2Enumerations(subsList[1]) + return ENUMS + + def updateEnumerations(self, obj): + """updateEnumerations(obj) ... + Method updates property enumerations based on active conditions + in the operation. Returns the updated enumerations dictionary. + Existing property values must be stored, and then restored after + the assignment of updated enumerations.""" + PathLog.debug('updateEnumerations()') + # Save existing values + pre_Ref1 = obj.Reference1 + pre_Ref2 = obj.Reference2 + + # Update enumerations + ENUMS = self.getActiveEnumerations(obj) + obj.Reference1 = ENUMS['Reference1'] + obj.Reference2 = ENUMS['Reference2'] + + # Restore pre-existing values if available with active enumerations. + # If not, set to first element in active enumeration list. + if pre_Ref1 in ENUMS['Reference1']: + obj.Reference1 = pre_Ref1 + else: + obj.Reference1 = ENUMS['Reference1'][0] + if pre_Ref2 in ENUMS['Reference2']: + obj.Reference2 = pre_Ref2 + else: + obj.Reference2 = ENUMS['Reference2'][0] + + return ENUMS + + def setEditorProperties(self, obj): + # Used to hide inputs in properties list + A = B = 2 + if hasattr(obj, 'Base'): + if obj.Base: + (base, subsList) = obj.Base[0] + subCnt = len(subsList) + if subCnt == 1: + A = 0 + elif subCnt == 2: A = B = 0 - else: - ENUMS = self.opPropertyEnumerations() - obj.Reference1 = ENUMS['Reference1'] - obj.Reference2 = ENUMS['Reference2'] obj.setEditorMode('Reference1', A) obj.setEditorMode('Reference2', B) @@ -196,6 +242,7 @@ class ObjectSlot(PathOp.ObjectOp): if hasattr(self, 'propertiesReady'): if self.propertiesReady: if prop in ['Base']: + self.updateEnumerations(obj) self.setEditorProperties(obj) def opOnDocumentRestored(self, obj): @@ -209,15 +256,15 @@ class ObjectSlot(PathOp.ObjectOp): obj.setEditorMode('ShowTempObjects', mode) # Repopulate enumerations in case of changes - ENUMS = self.opPropertyEnumerations() + ENUMS = self.updateEnumerations(obj) for n in ENUMS: restore = False if hasattr(obj, n): val = obj.getPropertyByName(n) restore = True - setattr(obj, n, ENUMS[n]) + setattr(obj, n, ENUMS[n]) # set the enumerations list if restore: - setattr(obj, n, val) + setattr(obj, n, val) # restore the value self.setEditorProperties(obj) @@ -320,6 +367,8 @@ class ObjectSlot(PathOp.ObjectOp): self.tmpGrp = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup', 'tmpDebugGrp') tmpGrpNm = self.tmpGrp.Name + # self.updateEnumerations(obj) + # Identify parent Job JOB = PathUtils.findParentJob(obj) self.JOB = JOB @@ -418,11 +467,14 @@ class ObjectSlot(PathOp.ObjectOp): lenSL = len(subsList) featureCnt = lenSL if lenSL == 1: + PathLog.debug('Reference 1: {}'.format(obj.Reference1)) sub1 = subsList[0] shape_1 = getattr(base.Shape, sub1) self.shape1 = shape_1 pnts = self._processSingle(obj, shape_1, sub1) else: + PathLog.debug('Reference 1: {}'.format(obj.Reference1)) + PathLog.debug('Reference 2: {}'.format(obj.Reference2)) sub1 = subsList[0] sub2 = subsList[1] shape_1 = getattr(base.Shape, sub1) @@ -467,7 +519,7 @@ class ObjectSlot(PathOp.ObjectOp): (p1, p2) = pnts if self.isDebug: - PathLog.debug('p1, p2: {}, {}'.format(p1, p2)) + PathLog.debug('Path Points are:\np1 = {}\np2 = {}'.format(p1, p2)) if p1.sub(p2).Length != 0 and self.showTempObjects: O = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp_Path') O.Shape = Part.makeLine(p1, p2) @@ -493,7 +545,7 @@ class ObjectSlot(PathOp.ObjectOp): It returns the slot gcode for the operation.""" CMDS = list() - def layerPass(p1, p2, depth): + def linePass(p1, p2, depth): cmds = list() # cmds.append(Path.Command('N (Tool type: {})'.format(toolType), {})) cmds.append(Path.Command('G0', {'X': p1.x, 'Y': p1.y, 'F': self.horizRapid})) @@ -502,16 +554,25 @@ class ObjectSlot(PathOp.ObjectOp): return cmds # CMDS.append(Path.Command('N (Tool type: {})'.format(toolType), {})) - # CMDS.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) if obj.LayerMode == 'Single-pass': - CMDS.extend(layerPass(p1, p2, obj.FinalDepth.Value)) + CMDS.extend(linePass(p1, p2, obj.FinalDepth.Value)) CMDS.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) else: - prvDep = obj.StartDepth.Value - for dep in self.depthParams: - CMDS.extend(layerPass(p1, p2, dep)) - CMDS.append(Path.Command('G0', {'Z': prvDep, 'F': self.vertRapid})) - prvDep = dep + if obj.CutPattern == 'Line': + for dep in self.depthParams: + CMDS.extend(linePass(p1, p2, dep)) + CMDS.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + elif obj.CutPattern == 'ZigZag': + CMDS.append(Path.Command('G0', {'X': p1.x, 'Y': p1.y, 'F': self.horizRapid})) + i = 0 + for dep in self.depthParams: + if i % 2.0 == 0: # even + CMDS.append(Path.Command('G1', {'Z': dep, 'F': self.vertFeed})) + CMDS.append(Path.Command('G1', {'X': p2.x, 'Y': p2.y, 'F': self.horizFeed})) + else: # odd + CMDS.append(Path.Command('G1', {'Z': dep, 'F': self.vertFeed})) + CMDS.append(Path.Command('G1', {'X': p1.x, 'Y': p1.y, 'F': self.horizFeed})) + i += 1 CMDS.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) return CMDS @@ -528,7 +589,7 @@ class ObjectSlot(PathOp.ObjectOp): pnts = False norm = shape_1.normalAt(0.0, 0.0) - PathLog.debug('Face.normalAt(): {}'.format(norm)) + PathLog.debug('{}.normalAt(): {}'.format(sub1, norm)) if norm.z == 1 or norm.z == -1: pnts = self._processSingleHorizFace(obj, shape_1) elif norm.z == 0: @@ -571,6 +632,7 @@ class ObjectSlot(PathOp.ObjectOp): def _processSingleHorizFace(self, obj, shape): """Determine slot path endpoints from a single horizontally oriented face.""" + PathLog.debug('_processSingleHorizFace()') lineTypes = ['Part::GeomLine'] def getRadians(self, E): @@ -603,8 +665,6 @@ class ObjectSlot(PathOp.ObjectOp): pairs = list() eCnt = len(shape.Edges) lstE = eCnt - 1 - I = [i for i in range(0, eCnt)] - I.append(0) for i in range(0, eCnt): if i < lstE: ni = i + 1 @@ -629,6 +689,11 @@ class ObjectSlot(PathOp.ObjectOp): if pairCnt > 1: pairs.sort(key=lambda tup: tup[0].Length, reverse=True) + if self.isDebug: + PathLog.debug(' -pairCnt: {}'.format(pairCnt)) + for (a, b) in pairs: + PathLog.debug(' -pair: {}, {}'.format(round(a.Length, 4), round(b.Length,4))) + if pairCnt == 0: msg = translate('PathSlot', 'No parallel edges identified.') @@ -638,9 +703,9 @@ class ObjectSlot(PathOp.ObjectOp): same = pairs[0] else: if obj.Reference1 == 'Long Edge': - same = pairs[0] - elif obj.Reference1 == 'Short Edge': same = pairs[1] + elif obj.Reference1 == 'Short Edge': + same = pairs[0] else: msg = 'Reference1 ' msg += translate('PathSlot', @@ -653,6 +718,7 @@ class ObjectSlot(PathOp.ObjectOp): def _processSingleComplexFace(self, obj, shape): """Determine slot path endpoints from a single complex face.""" + PathLog.debug('_processSingleComplexFace()') PNTS = list() def zVal(V): @@ -667,6 +733,7 @@ class ObjectSlot(PathOp.ObjectOp): def _processSingleVertFace(self, obj, shape): """Determine slot path endpoints from a single vertically oriented face with no single bottom edge.""" + PathLog.debug('_processSingleVertFace()') eCnt = len(shape.Edges) V0 = shape.Edges[0].Vertexes[0] V1 = shape.Edges[eCnt - 1].Vertexes[1] @@ -691,6 +758,7 @@ class ObjectSlot(PathOp.ObjectOp): # Methods for processing double geometry def _processDouble(self, obj, shape_1, sub1, shape_2, sub2): + PathLog.debug('_processDouble()') """This is the control method for slots based on a two Base Geometry features.""" cmds = False @@ -898,47 +966,12 @@ class ObjectSlot(PathOp.ObjectOp): n2 = p2 return (n1, n2) - def _getEndMidPoints(self, same): - # Find mid-points between ends of equal, oppossing edges - e0va = same[0].Vertexes[0] - e0vb = same[0].Vertexes[1] - e1va = same[1].Vertexes[0] - e1vb = same[1].Vertexes[1] - - if False: - midX1 = (e0va.X + e0vb.X) / 2.0 - midY1 = (e0va.Y + e0vb.Y) / 2.0 - midX2 = (e1va.X + e1vb.X) / 2.0 - midY2 = (e1va.Y + e1vb.Y) / 2.0 - m1 = FreeCAD.Vector(midX1, midY1, e0va.Z) - m2 = FreeCAD.Vector(midX2, midY2, e0va.Z) - - p1 = FreeCAD.Vector(e0va.X, e0va.Y, e0va.Z) - p2 = FreeCAD.Vector(e0vb.X, e0vb.Y, e0vb.Z) - p3 = FreeCAD.Vector(e1va.X, e1va.Y, e1va.Z) - p4 = FreeCAD.Vector(e1vb.X, e1vb.Y, e1vb.Z) - - L0 = Part.makeLine(p1, p2) - L1 = Part.makeLine(p3, p4) - comL0 = L0.CenterOfMass - comL1 = L1.CenterOfMass - m1 = FreeCAD.Vector(comL0.x, comL0.y, 0.0) - m2 = FreeCAD.Vector(comL1.x, comL1.y, 0.0) - - return (m1, m2) - def _getOppMidPoints(self, same): # Find mid-points between ends of equal, oppossing edges - v1 = same[0].Vertexes[0] - v2 = same[0].Vertexes[1] - a1 = same[1].Vertexes[0] - a2 = same[1].Vertexes[1] - midX1 = (v1.X + a2.X) / 2.0 - midY1 = (v1.Y + a2.Y) / 2.0 - midX2 = (v2.X + a1.X) / 2.0 - midY2 = (v2.Y + a1.Y) / 2.0 - p1 = FreeCAD.Vector(midX1, midY1, v1.Z) - p2 = FreeCAD.Vector(midX2, midY2, v1.Z) + com1 = same[0].CenterOfMass + com2 = same[1].CenterOfMass + p1 = FreeCAD.Vector(com1.x, com1.y, 0.0) + p2 = FreeCAD.Vector(com2.x, com2.y, 0.0) return (p1, p2) def _isParallel(self, dYdX1, dYdX2): @@ -1152,19 +1185,31 @@ class ObjectSlot(PathOp.ObjectOp): return ('Wire', wires[0]) return False - def _getReference1Enums(self, sub, single=False): - # Adjust available enumerations - enums1 = self.opPropertyEnumerations()['Reference1'] - for ri in removeIndexesFromReference_1(sub, single): - enums1.pop(ri) - return enums1 + def _makeReference1Enumerations(self, sub, single=False): + """Customize Reference1 enumerations based on feature type.""" + PathLog.debug('_makeReference1Enumerations()') + cat = sub[:4] + if single: + if cat == 'Face': + return ['Long Edge', 'Short Edge'] + elif cat == 'Edge': + return ['Long Edge'] + elif cat == 'Vert': + return ['Vertex'] + elif cat == 'Vert': + return ['Vertex'] - def _getReference2Enums(self, sub): - # Adjust available enumerations - enums2 = self.opPropertyEnumerations()['Reference2'] - for ri in removeIndexesFromReference_2(sub): - enums2.pop(ri) - return enums2 + return ['Center of Mass', 'Center of BoundBox', + 'Lowest Point', 'Highest Point'] + + def _makeReference2Enumerations(self, sub): + """Customize Reference2 enumerations based on feature type.""" + PathLog.debug('_makeReference2Enumerations()') + cat = sub[:4] + if cat == 'Vert': + return ['Vertex'] + return ['Center of Mass', 'Center of BoundBox', + 'Lowest Point', 'Highest Point'] def _lineCollisionCheck(self, obj, p1, p2): """Make simple circle with diameter of tool, at start point. @@ -1176,44 +1221,51 @@ class ObjectSlot(PathOp.ObjectOp): def getPerp(p1, p2, dist): toEnd = p2.sub(p1) - factor = dist / toEnd.Length perp = FreeCAD.Vector(-1 * toEnd.y, toEnd.x, 0.0) + if perp.x == 0 and perp.y == 0: + return perp perp.normalize() perp.multiply(dist) return perp + # Make first cylinder ce1 = Part.Wire(Part.makeCircle(rad, p1).Edges) - ce2 = Part.Wire(Part.makeCircle(rad, p2).Edges) C1 = Part.Face(ce1) - C2 = Part.Face(ce2) - zTrans = obj.FinalDepth.Value - C1.BoundBox.ZMin C1.translate(FreeCAD.Vector(0.0, 0.0, zTrans)) - zTrans = obj.FinalDepth.Value - C2.BoundBox.ZMin - C2.translate(FreeCAD.Vector(0.0, 0.0, zTrans)) - extFwd = obj.StartDepth.Value - obj.FinalDepth.Value extVect = FreeCAD.Vector(0.0, 0.0, extFwd) startShp = C1.extrude(extVect) - endShp = C2.extrude(extVect) - perp = getPerp(p1, p2, rad) - v1 = p1.add(perp) - v2 = p1.sub(perp) - v3 = p2.sub(perp) - v4 = p2.add(perp) - e1 = Part.makeLine(v1, v2) - e2 = Part.makeLine(v2, v3) - e3 = Part.makeLine(v3, v4) - e4 = Part.makeLine(v4, v1) - edges = Part.__sortEdges__([e1, e2, e3, e4]) - rectFace = Part.Face(Part.Wire(edges)) - zTrans = obj.FinalDepth.Value - rectFace.BoundBox.ZMin - rectFace.translate(FreeCAD.Vector(0.0, 0.0, zTrans)) - boxShp = rectFace.extrude(extVect) + if p2.sub(p1).Length > 0: + # Make second cylinder + ce2 = Part.Wire(Part.makeCircle(rad, p2).Edges) + C2 = Part.Face(ce2) + zTrans = obj.FinalDepth.Value - C2.BoundBox.ZMin + C2.translate(FreeCAD.Vector(0.0, 0.0, zTrans)) + endShp = C2.extrude(extVect) - part1 = startShp.fuse(boxShp) - pathTravel = part1.fuse(endShp) + # Make extruded rectangle to connect cylinders + perp = getPerp(p1, p2, rad) + v1 = p1.add(perp) + v2 = p1.sub(perp) + v3 = p2.sub(perp) + v4 = p2.add(perp) + e1 = Part.makeLine(v1, v2) + e2 = Part.makeLine(v2, v3) + e3 = Part.makeLine(v3, v4) + e4 = Part.makeLine(v4, v1) + edges = Part.__sortEdges__([e1, e2, e3, e4]) + rectFace = Part.Face(Part.Wire(edges)) + zTrans = obj.FinalDepth.Value - rectFace.BoundBox.ZMin + rectFace.translate(FreeCAD.Vector(0.0, 0.0, zTrans)) + boxShp = rectFace.extrude(extVect) + + # Fuse two cylinders and box together + part1 = startShp.fuse(boxShp) + pathTravel = part1.fuse(endShp) + else: + pathTravel = startShp if self.showTempObjects: O = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp_PathTravel') @@ -1233,35 +1285,6 @@ class ObjectSlot(PathOp.ObjectOp): # Eclass -# Determine applicable enumerations -def removeIndexesFromReference_1(sub, single=False): - """Determine which enumerations to remove for Reference1 input - based upon the feature type(category).""" - cat = sub[:4] - remIdxs = [6, 5, 4] - if cat == 'Face': - if single: - remIdxs = [6, 3, 2, 1, 0] - elif cat == 'Edge': - if single: - remIdxs = [6, 5, 3, 2, 1, 0] - elif cat == 'Vert': - remIdxs = [5, 4, 3, 2, 1, 0] - return remIdxs - - -def removeIndexesFromReference_2(sub): - """Determine which enumerations to remove for Reference2 input - based upon the feature type(category).""" - cat = sub[:4] - remIdxs = [4] - # Customize Reference combobox options - if cat == 'Vert': - remIdxs = [3, 2, 1, 0] - return remIdxs - - - def SetupProperties(): ''' SetupProperties() ... Return list of properties required for operation.''' return [tup[1] for tup in ObjectSlot.opPropertyDefinitions(False)] diff --git a/src/Mod/Path/PathScripts/PathSlotGui.py b/src/Mod/Path/PathScripts/PathSlotGui.py index 4b1d25e2c7..7180c8dc43 100644 --- a/src/Mod/Path/PathScripts/PathSlotGui.py +++ b/src/Mod/Path/PathScripts/PathSlotGui.py @@ -37,33 +37,75 @@ __doc__ = "Slot operation page controller and command implementation." __contributors__ = "" +DEBUG = False + +def debugMsg(msg): + global DEBUG + if DEBUG: + FreeCAD.Console.PrintMessage('PathSlotGui:: ' + msg + '\n') + + class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''Page controller class for the Slot operation.''' + def getForm(self): + '''getForm() ... returns UI''' + debugMsg('getForm()') + return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSlotEdit.ui") + def initPage(self, obj): + '''initPage(obj) ... Is called after getForm() to initiate the task panel.''' + debugMsg('initPage()') # pylint: disable=attribute-defined-outside-init self.CATS = [None, None] + self.propEnums = PathSlot.ObjectSlot.opPropertyEnumerations(False) + self.ENUMS = dict() self.setTitle("Slot - " + obj.Label) # retrieve property enumerations - self.propEnums = PathSlot.ObjectSlot.opPropertyEnumerations(False) # Requirements due to Gui::QuantitySpinBox class use in UI panel self.geo1Extension = PathGui.QuantitySpinBox(self.form.geo1Extension, obj, 'ExtendPathStart') self.geo2Extension = PathGui.QuantitySpinBox(self.form.geo2Extension, obj, 'ExtendPathEnd') - # self.updateVisibility() - def getForm(self): - '''getForm() ... returns UI''' - # FreeCAD.Console.PrintMessage('getForm()\n') - return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSlotEdit.ui") + def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' + debugMsg('setFields()') + debugMsg('... calling updateVisibility()') + self.updateVisibility() + + self.updateQuantitySpinBoxes() + + self.setupToolController(obj, self.form.toolController) + self.setupCoolant(obj, self.form.coolantController) + + enums = self.propEnums['Reference1'] + if 'Reference1' in self.ENUMS: + enums = self.ENUMS['Reference1'] + debugMsg(' -enums1: {}'.format(enums)) + idx = enums.index(obj.Reference1) + self.form.geo1Reference.setCurrentIndex(idx) + + enums = self.propEnums['Reference2'] + if 'Reference2' in self.ENUMS: + enums = self.ENUMS['Reference2'] + debugMsg(' -enums2: {}'.format(enums)) + idx = enums.index(obj.Reference2) + self.form.geo2Reference.setCurrentIndex(idx) + + self.selectInComboBox(obj.LayerMode, self.form.layerMode) + self.selectInComboBox(obj.PathOrientation, self.form.pathOrientation) + + if obj.ReverseDirection: + self.form.reverseDirection.setCheckState(QtCore.Qt.Checked) + else: + self.form.reverseDirection.setCheckState(QtCore.Qt.Unchecked) def updateQuantitySpinBoxes(self): - # FreeCAD.Console.PrintMessage('updateQuantitySpinBoxes()\n') self.geo1Extension.updateSpinBox() self.geo2Extension.updateSpinBox() def getFields(self, obj): '''getFields(obj) ... transfers values from UI to obj's proprties''' - # FreeCAD.Console.PrintMessage('getFields()\n') + debugMsg('getFields()') self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) @@ -79,36 +121,11 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): val = self.propEnums['PathOrientation'][self.form.pathOrientation.currentIndex()] obj.PathOrientation = val - if hasattr(self.form, 'reverseDirection'): - obj.ReverseDirection = self.form.reverseDirection.isChecked() - - def setFields(self, obj): - '''setFields(obj) ... transfers obj's property values to UI''' - # FreeCAD.Console.PrintMessage('setFields()\n') - self.updateQuantitySpinBoxes() - - self.setupToolController(obj, self.form.toolController) - self.setupCoolant(obj, self.form.coolantController) - - idx = self.propEnums['Reference1'].index(obj.Reference1) - self.form.geo1Reference.setCurrentIndex(idx) - idx = self.propEnums['Reference2'].index(obj.Reference2) - self.form.geo2Reference.setCurrentIndex(idx) - - self.selectInComboBox(obj.LayerMode, self.form.layerMode) - self.selectInComboBox(obj.PathOrientation, self.form.pathOrientation) - - if obj.ReverseDirection: - self.form.reverseDirection.setCheckState(QtCore.Qt.Checked) - else: - self.form.reverseDirection.setCheckState(QtCore.Qt.Unchecked) - - # FreeCAD.Console.PrintMessage('... calling updateVisibility()\n') - self.updateVisibility() + obj.ReverseDirection = self.form.reverseDirection.isChecked() def getSignalsForUpdate(self, obj): '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' - # FreeCAD.Console.PrintMessage('getSignalsForUpdate()\n') + debugMsg('getSignalsForUpdate()') signals = [] signals.append(self.form.toolController.currentIndexChanged) signals.append(self.form.coolantController.currentIndexChanged) @@ -123,7 +140,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def updateVisibility(self, sentObj=None): '''updateVisibility(sentObj=None)... Updates visibility of Tasks panel objects.''' - # FreeCAD.Console.PrintMessage('updateVisibility()\n') + # debugMsg('updateVisibility()') hideFeatures = True if hasattr(self.obj, 'Base'): if self.obj.Base: @@ -136,8 +153,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): subCnt = len(sublist) if subCnt == 1: + debugMsg(' -subCnt == 1') # Save value, then reset choices - self.resetRef1Choices() n1 = sublist[0] s1 = getattr(base.Shape, n1) # Show Reference1 and cusomize options within @@ -152,8 +169,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): if self.CATS[1]: self.CATS[1] = None elif subCnt == 2: - self.resetRef1Choices() - self.resetRef2Choices() + debugMsg(' -subCnt == 2') n1 = sublist[0] n2 = sublist[1] s1 = getattr(base.Shape, n1) @@ -171,64 +187,55 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): else: self.form.pathOrientation_label.hide() self.form.pathOrientation.hide() + if hideFeatures: + # reset values + self.CATS = [None, None] + self.selectInComboBox('Start to End', self.form.pathOrientation) + # hide inputs and show message self.form.featureReferences.hide() self.form.customPoints.show() - """ - 'Reference1': ['Center of Mass', 'Center of BoundBox', - 'Lowest Point', 'Highest Point', 'Long Edge', - 'Short Edge', 'Vertex'], - 'Reference2': ['Center of Mass', 'Center of BoundBox', - 'Lowest Point', 'Highest Point', 'Vertex'] - """ - def customizeReference_1(self, sub, single=False): + debugMsg('customizeReference_1()') # Customize Reference1 combobox options # by removing unavailable choices cat = sub[:4] if cat != self.CATS[0]: self.CATS[0] = cat - cBox = self.form.geo1Reference - cBox.blockSignals(True) - for ri in PathSlot.removeIndexesFromReference_1(sub, single): - cBox.removeItem(ri) - cBox.blockSignals(False) + slot = PathSlot.ObjectSlot + enums = slot._makeReference1Enumerations(slot, sub, single) + self.ENUMS['Reference1'] = enums + debugMsg('Ref1: {}'.format(enums)) + self._updateComboBox(self.form.geo1Reference, enums) + # self.form.geo1Reference.setCurrentIndex(0) + # self.form.geo1Reference.setCurrentText(enums[0]) def customizeReference_2(self, sub): + debugMsg('customizeReference_2()') # Customize Reference2 combobox options # by removing unavailable choices cat = sub[:4] if cat != self.CATS[1]: self.CATS[1] = cat - cBox = self.form.geo2Reference - cBox.blockSignals(True) - for ri in PathSlot.removeIndexesFromReference_2(sub): - cBox.removeItem(ri) - cBox.blockSignals(False) - cBox.setCurrentIndex(0) - - def resetRef1Choices(self): - # Reset Reference1 choices - ref1 = self.form.geo1Reference - ref1.blockSignals(True) - ref1.clear() # Empty the combobox - ref1.addItems(self.propEnums['Reference1']) - ref1.blockSignals(False) - - def resetRef2Choices(self): - # Reset Reference2 choices - ref2 = self.form.geo2Reference - ref2.blockSignals(True) - ref2.clear() # Empty the combobox - ref2.addItems(self.propEnums['Reference2']) - ref2.blockSignals(False) + slot = PathSlot.ObjectSlot + enums = slot._makeReference2Enumerations(slot, sub) + self.ENUMS['Reference2'] = enums + debugMsg('Ref2: {}'.format(enums)) + self._updateComboBox(self.form.geo2Reference, enums) + # self.form.geo2Reference.setCurrentIndex(0) + # self.form.geo2Reference.setCurrentText(enums[0]) def registerSignalHandlers(self, obj): - # FreeCAD.Console.PrintMessage('registerSignalHandlers()\n') + # debugMsg('registerSignalHandlers()') # self.form.pathOrientation.currentIndexChanged.connect(self.updateVisibility) pass + def _updateComboBox(self, cBox, enums): + cBox.blockSignals(True) + cBox.clear() + cBox.addItems(enums) + cBox.blockSignals(False) Command = PathOpGui.SetupOperation('Slot', PathSlot.Create, From 4b8cf63c14124d7b97ff1921761e0c9e82f52c4c Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 21 Jun 2020 00:07:21 +0200 Subject: [PATCH 07/11] [skip ci] improve possibility to create a QApplication in a thread --- src/App/Application.cpp | 2 +- src/Base/Console.cpp | 7 +++++-- src/Main/FreeCADGuiPy.cpp | 3 +++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 6b83908bda..3f32c1780e 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -1889,7 +1889,7 @@ void Application::initConfig(int argc, char ** argv) Branding brand; QString binDir = QString::fromUtf8((mConfig["AppHomePath"] + "bin").c_str()); QFileInfo fi(binDir, QString::fromLatin1("branding.xml")); - if (brand.readFile(fi.absoluteFilePath())) { + if (fi.exists() && brand.readFile(fi.absoluteFilePath())) { Branding::XmlConfig cfg = brand.getUserDefines(); for (Branding::XmlConfig::iterator it = cfg.begin(); it != cfg.end(); ++it) { App::Application::Config()[it.key()] = it.value(); diff --git a/src/Base/Console.cpp b/src/Base/Console.cpp index 31a1b90d81..bd7fcfde09 100644 --- a/src/Base/Console.cpp +++ b/src/Base/Console.cpp @@ -126,8 +126,6 @@ ConsoleSingleton::ConsoleSingleton(void) ,_defaultLogLevel(FC_LOGLEVEL_MSG) #endif { - // make sure this object is part of the main thread - ConsoleOutput::getInstance(); } ConsoleSingleton::~ConsoleSingleton() @@ -233,6 +231,11 @@ bool ConsoleSingleton::IsMsgTypeEnabled(const char* sObs, FreeCAD_ConsoleMsgType void ConsoleSingleton::SetConnectionMode(ConnectionMode mode) { connectionMode = mode; + + // make sure this method gets called from the main thread + if (connectionMode == Queued) { + ConsoleOutput::getInstance(); + } } /** Prints a Message diff --git a/src/Main/FreeCADGuiPy.cpp b/src/Main/FreeCADGuiPy.cpp index 3d7b5488a0..a1d2079743 100644 --- a/src/Main/FreeCADGuiPy.cpp +++ b/src/Main/FreeCADGuiPy.cpp @@ -109,6 +109,9 @@ FreeCADGui_showMainWindow(PyObject * /*self*/, PyObject *args) #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); #endif + // This only works well if the QApplication is the very first created instance + // of a QObject. Otherwise the application lives in a different thread than the + // main thread which will cause hazardous behaviour. QtApplication app(argc, argv); if (setupMainWindow()) { app.exec(); From 3971c85130344b082d765f594ebc0962024f5c24 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 21 Jun 2020 01:39:39 +0200 Subject: [PATCH 08/11] [skip ci] make Jupyter notebook integration working again --- src/Main/FreeCADGuiPy.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Main/FreeCADGuiPy.cpp b/src/Main/FreeCADGuiPy.cpp index a1d2079743..32f82207a6 100644 --- a/src/Main/FreeCADGuiPy.cpp +++ b/src/Main/FreeCADGuiPy.cpp @@ -120,17 +120,20 @@ FreeCADGui_showMainWindow(PyObject * /*self*/, PyObject *args) t.detach(); } else { + // In order to get Jupiter notebook integration working we must create a direct instance + // of QApplication. Not even a sub-class can be used because otherwise PySide2 wraps it + // with a QtCore.QCoreApplication which will raise an exception in ipykernel #if defined(Q_OS_WIN) static int argc = 0; static char **argv = {0}; - (void)new QtApplication(argc, argv); + (void)new QApplication(argc, argv); // When QApplication is constructed hhook = SetWindowsHookEx(WH_GETMESSAGE, FilterProc, 0, GetCurrentThreadId()); #elif !defined(QT_NO_GLIB) static int argc = 0; static char **argv = {0}; - (void)new QtApplication(argc, argv); + (void)new QApplication(argc, argv); #else PyErr_SetString(PyExc_RuntimeError, "Must construct a QApplication before a QPaintDevice\n"); return NULL; From eb6a8e5339ea6902925bed5dbba54ca71d798d0f Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 21 Jun 2020 05:18:47 +0200 Subject: [PATCH 09/11] [TD] add spacing to ProjGroup dialog Add the setting to auto-distribute projections to the dialog --- src/Mod/TechDraw/Gui/TaskProjGroup.cpp | 47 +- src/Mod/TechDraw/Gui/TaskProjGroup.h | 6 + src/Mod/TechDraw/Gui/TaskProjGroup.ui | 1426 +++++++++++++----------- 3 files changed, 824 insertions(+), 655 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskProjGroup.cpp b/src/Mod/TechDraw/Gui/TaskProjGroup.cpp index 7afd578b0c..bcc9cebc9a 100644 --- a/src/Mod/TechDraw/Gui/TaskProjGroup.cpp +++ b/src/Mod/TechDraw/Gui/TaskProjGroup.cpp @@ -45,16 +45,16 @@ #include #include +#include +#include #include #include #include -#include -#include - +#include "ViewProviderPage.h" #include "ViewProviderProjGroup.h" #include "ViewProviderProjGroupItem.h" -#include "ViewProviderPage.h" + #include "TaskProjGroup.h" #include @@ -86,6 +86,13 @@ TaskProjGroup::TaskProjGroup(TechDraw::DrawProjGroup* featView, bool mode) : ui->sbScaleDen->setEnabled(false); } + ui->cbAutoDistribute->setChecked(multiView->AutoDistribute.getValue()); + // disable if no AutoDistribute + ui->sbXSpacing->setEnabled(multiView->AutoDistribute.getValue()); + ui->sbYSpacing->setEnabled(multiView->AutoDistribute.getValue()); + ui->sbXSpacing->setValue(multiView->spacingX.getValue()); + ui->sbYSpacing->setValue(multiView->spacingY.getValue()); + // Initially toggle view checkboxes if needed setupViewCheckboxes(true); @@ -112,6 +119,13 @@ TaskProjGroup::TaskProjGroup(TechDraw::DrawProjGroup* featView, bool mode) : // connect(ui->projection, SIGNAL(currentIndexChanged(int)), this, SLOT(projectionTypeChanged(int))); connect(ui->projection, SIGNAL(currentIndexChanged(QString)), this, SLOT(projectionTypeChanged(QString))); + // Spacing + connect(ui->cbAutoDistribute, SIGNAL(clicked(bool)), this, SLOT(AutoDistributeClicked(bool))); + connect(ui->sbXSpacing, SIGNAL(valueChanged(double)), this, SLOT(spacingChanged(void))); + connect(ui->sbYSpacing, SIGNAL(valueChanged(double)), this, SLOT(spacingChanged(void))); + ui->sbXSpacing->setUnit(Base::Unit::Length); + ui->sbYSpacing->setUnit(Base::Unit::Length); + m_page = multiView->findParentPage(); Gui::Document* activeGui = Gui::Application::Instance->getDocument(m_page->getDocument()); Gui::ViewProvider* vp = activeGui->getViewProvider(m_page); @@ -135,6 +149,9 @@ void TaskProjGroup::saveGroupState() m_saveProjType = multiView->ProjectionType.getValueAsString(); m_saveScaleType = multiView->ScaleType.getValueAsString(); m_saveScale = multiView->Scale.getValue(); + m_saveAutoDistribute = multiView->AutoDistribute.getValue(); + m_saveSpacingX = multiView->spacingX.getValue(); + m_saveSpacingY = multiView->spacingY.getValue(); DrawProjGroupItem* anchor = multiView->getAnchor(); m_saveDirection = anchor->Direction.getValue(); } @@ -154,6 +171,9 @@ void TaskProjGroup::restoreGroupState() multiView->ProjectionType.setValue(m_saveProjType.c_str()); multiView->ScaleType.setValue(m_saveScaleType.c_str()); multiView->Scale.setValue(m_saveScale); + multiView->AutoDistribute.setValue(m_saveAutoDistribute); + multiView->spacingX.setValue(m_saveSpacingX); + multiView->spacingY.setValue(m_saveSpacingY); multiView->purgeProjections(); for(auto & sv : m_saveViewNames) { if (sv != "Front") { @@ -269,6 +289,25 @@ void TaskProjGroup::scaleTypeChanged(int index) } } +void TaskProjGroup::AutoDistributeClicked(bool b) +{ + if (blockUpdate) { + return; + } + multiView->AutoDistribute.setValue(b); + multiView->recomputeFeature(); +} + +void TaskProjGroup::spacingChanged(void) +{ + if (blockUpdate) { + return; + } + multiView->spacingX.setValue(ui->sbXSpacing->value().getValue()); + multiView->spacingY.setValue(ui->sbYSpacing->value().getValue()); + multiView->recomputeFeature(); +} + std::pair TaskProjGroup::nearestFraction(const double val, const long int maxDenom) const { /* diff --git a/src/Mod/TechDraw/Gui/TaskProjGroup.h b/src/Mod/TechDraw/Gui/TaskProjGroup.h index c03e80648b..0f50bfdd74 100644 --- a/src/Mod/TechDraw/Gui/TaskProjGroup.h +++ b/src/Mod/TechDraw/Gui/TaskProjGroup.h @@ -85,6 +85,9 @@ protected Q_SLOTS: /* void projectionTypeChanged(int index);*/ void projectionTypeChanged(QString qText); void scaleTypeChanged(int index); + void AutoDistributeClicked(bool b); + /// Updates item spacing + void spacingChanged(void); void scaleManuallyChanged(int i); protected: @@ -122,6 +125,9 @@ private: std::string m_saveProjType; std::string m_saveScaleType; double m_saveScale; + bool m_saveAutoDistribute; + double m_saveSpacingX; + double m_saveSpacingY; Base::Vector3d m_saveDirection; std::vector m_saveViewNames; }; diff --git a/src/Mod/TechDraw/Gui/TaskProjGroup.ui b/src/Mod/TechDraw/Gui/TaskProjGroup.ui index 3be133997f..3f4f21e265 100644 --- a/src/Mod/TechDraw/Gui/TaskProjGroup.ui +++ b/src/Mod/TechDraw/Gui/TaskProjGroup.ui @@ -6,8 +6,8 @@ 0 0 - 371 - 511 + 250 + 477 @@ -25,664 +25,788 @@ Projection Group - + - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - QLayout::SetDefaultConstraint + + + + + Projection + + + + + + + First or Third Angle + + + + First Angle - - - - - - Projection - - - - - - - First or Third Angle - - - - First Angle - - - - - Third Angle - - - - - Page - - - - - - - - - - - - Scale - - - - - - - Scale Page/Auto/Custom - - - - Page - - - - - Automatic - - - - - Custom - - - - - - - - - - - - Custom Scale - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Scale Numerator - - - 1 - - - 1 - - - - - - - : - - - - - - - Scale Denominator - - - 1 - - - 999 - - - 1 - - - - - - - - - Qt::Horizontal - - - - - - - - - - 0 - 0 - - - - Adjust Primary Direction - - - true - - - Qt::AlignCenter - - - 0 - - - - - - - - - - - false - - - - 11 - 50 - false - false - - - - Qt::NoFocus - - - Current primary view direction - - - Qt::AlignCenter - - - true - - - - - - - Rotate right - - - - - - - :/icons/arrow-right.svg - - - - - 24 - 24 - - - - - - - - - 0 - 0 - - - - Rotate up - - - - - - - :/icons/arrow-up.svg - - - - - 24 - 24 - - - - - - - - Rotate left - - - - - - - :/icons/arrow-left.svg - - - - - 24 - 24 - - - - - - - - Rotate down - - - - - - - :/icons/arrow-down.svg - - - - - 24 - 24 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Horizontal - - - - - - - - - Secondary Projections - - - Qt::AlignCenter - - - - - - - - - - - true - - - Bottom - - - QCheckBox::indicator { -width: 24px; -height: 24px; -} - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - Primary - - - QCheckBox::indicator { -width: 24px; -height: 24px; -} - - - - - - - true - - - - - - - Right - - - QCheckBox::indicator { -width: 24px; -height: 24px; -} - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Left - - - QCheckBox::indicator { -width: 24px; -height: 24px; -} - - - - - - - - - - - LeftFrontBottom - - - QCheckBox::indicator { -width: 24px; -height: 24px; -} - - - - - - - - - - - Top - - - QCheckBox::indicator { -width: 24px; -height: 24px; -} - - - - - - - - - - - RightFrontBottom - - - QCheckBox::indicator { -width: 24px; -height: 24px; -} - - - - - - - - - - - RightFrontTop - - - QCheckBox::indicator { -width: 24px; -height: 24px; -} - - - - - - - - - - - Rear - - - QCheckBox::indicator { -width: 24px; -height: 24px; -} - - - - - - - - - - - LeftFrontTop - - - QCheckBox::indicator { -width: 24px; -height: 24px; -} - - - - - - - - 24 - 24 - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Spin CW - - - - - - - :/icons/arrow-cw.svg - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Spin CCW - - - - - - - :/icons/arrow-ccw.svg - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Qt::Vertical + + + + Third Angle - - - 20 - 40 - + + + + Page - - - + + + + + + + + + + + Scale + + + + + + + Scale Page/Auto/Custom + + + + Page + + + + + Automatic + + + + + Custom + + + + + + + + + + + + Custom Scale + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Scale Numerator + + + 1 + + + 1 + + + + + + + : + + + + + + + Scale Denominator + + + 1 + + + 999 + + + 1 + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Adjust Primary Direction + + + true + + + Qt::AlignCenter + + + 0 + + + + + + + + + + + false + + + + 11 + 50 + false + false + + + + Qt::NoFocus + + + Current primary view direction + + + Qt::AlignCenter + + + true + + + + + + + Rotate right + + + + + + + :/icons/arrow-right.svg + + + + + 24 + 24 + + + + + + + + + 0 + 0 + + + + Rotate up + + + + + + + :/icons/arrow-up.svg + + + + + 24 + 24 + + + + + + + + Rotate left + + + + + + + :/icons/arrow-left.svg + + + + + 24 + 24 + + + + + + + + Rotate down + + + + + + + :/icons/arrow-down.svg + + + + + 24 + 24 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + + + Secondary Projections + + + Qt::AlignCenter + + + + + + + + + + + true + + + Bottom + + + QCheckBox::indicator { +width: 24px; +height: 24px; +} + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Primary + + + QCheckBox::indicator { +width: 24px; +height: 24px; +} + + + + + + + true + + + + + + + Right + + + QCheckBox::indicator { +width: 24px; +height: 24px; +} + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Left + + + QCheckBox::indicator { +width: 24px; +height: 24px; +} + + + + + + + + + + + LeftFrontBottom + + + QCheckBox::indicator { +width: 24px; +height: 24px; +} + + + + + + + + + + + Top + + + QCheckBox::indicator { +width: 24px; +height: 24px; +} + + + + + + + + + + + RightFrontBottom + + + QCheckBox::indicator { +width: 24px; +height: 24px; +} + + + + + + + + + + + RightFrontTop + + + QCheckBox::indicator { +width: 24px; +height: 24px; +} + + + + + + + + + + + Rear + + + QCheckBox::indicator { +width: 24px; +height: 24px; +} + + + + + + + + + + + LeftFrontTop + + + QCheckBox::indicator { +width: 24px; +height: 24px; +} + + + + + + + + 24 + 24 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Spin CW + + + + + + + :/icons/arrow-cw.svg + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Spin CCW + + + + + + + :/icons/arrow-ccw.svg + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Distributes projections automatically +using the given X/Y Spacing + + + Auto Distribute + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 150 + 22 + + + + Horizontal space between center of projections + + + false + + + + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + X Spacing + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Y Spacing + + + + + + + + 0 + 0 + + + + + 150 + 22 + + + + Vertical space between center of projections + + + false + + + + + + 0.000000000000000 + + + + + + + + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+
- + + + cbAutoDistribute + clicked(bool) + sbXSpacing + setEnabled(bool) + + + 60 + 407 + + + 164 + 431 + + + + + cbAutoDistribute + clicked(bool) + sbYSpacing + setEnabled(bool) + + + 60 + 407 + + + 164 + 459 + + + + From ed429e8a2c57fede26ddaa2ba0f167b87e4987d8 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 21 Jun 2020 05:22:32 +0200 Subject: [PATCH 10/11] disable keyboardTracking for the scale to avoid unnecessary recomputes --- src/Mod/TechDraw/Gui/TaskProjGroup.ui | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Mod/TechDraw/Gui/TaskProjGroup.ui b/src/Mod/TechDraw/Gui/TaskProjGroup.ui index 3f4f21e265..ef8201cebc 100644 --- a/src/Mod/TechDraw/Gui/TaskProjGroup.ui +++ b/src/Mod/TechDraw/Gui/TaskProjGroup.ui @@ -119,6 +119,9 @@ Scale Numerator + + false + 1 @@ -139,6 +142,9 @@ Scale Denominator + + false + 1 From edc21e933995c0e087598ca8a13bffc6d7f3b80a Mon Sep 17 00:00:00 2001 From: donovaly Date: Sun, 21 Jun 2020 12:52:36 +0200 Subject: [PATCH 11/11] TaskProjGroup.ui: correct a typo --- src/Mod/TechDraw/Gui/TaskProjGroup.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/TechDraw/Gui/TaskProjGroup.ui b/src/Mod/TechDraw/Gui/TaskProjGroup.ui index ef8201cebc..62973a9e24 100644 --- a/src/Mod/TechDraw/Gui/TaskProjGroup.ui +++ b/src/Mod/TechDraw/Gui/TaskProjGroup.ui @@ -688,7 +688,7 @@ using the given X/Y Spacing - Horizontal space between center of projections + Horizontal space between border of projections false @@ -754,7 +754,7 @@ using the given X/Y Spacing - Vertical space between center of projections + Vertical space between border of projections false