From 1025ef4da34676bb6f679c6c5fb0f6a7ed8c6a56 Mon Sep 17 00:00:00 2001 From: WandererFan Date: Mon, 13 Apr 2020 22:31:18 -0400 Subject: [PATCH 01/33] [TD]Piecewise Sectioning Algo --- src/Mod/TechDraw/App/DrawViewSection.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewSection.cpp b/src/Mod/TechDraw/App/DrawViewSection.cpp index d3cb579bb6..0d76ef8ec9 100644 --- a/src/Mod/TechDraw/App/DrawViewSection.cpp +++ b/src/Mod/TechDraw/App/DrawViewSection.cpp @@ -347,7 +347,6 @@ App::DocumentObjectExecReturn *DrawViewSection::execute(void) } } - dvp->requestPaint(); //to refresh section line return DrawView::execute(); } @@ -382,13 +381,26 @@ void DrawViewSection::sectionExec(TopoDS_Shape baseShape) BRepBuilderAPI_Copy BuilderCopy(baseShape); TopoDS_Shape myShape = BuilderCopy.Shape(); - BRepAlgoAPI_Cut mkCut(myShape, prism); - if (!mkCut.IsDone()) { - Base::Console().Warning("DVS: Section cut has failed in %s\n",getNameInDocument()); - return; + BRep_Builder builder; + TopoDS_Compound pieces; + builder.MakeCompound(pieces); + TopExp_Explorer expl(myShape, TopAbs_SOLID); + int indb = 0; + int outdb = 0; + for (; expl.More(); expl.Next()) { + indb++; + const TopoDS_Solid& s = TopoDS::Solid(expl.Current()); + BRepAlgoAPI_Cut mkCut(s, prism); + if (!mkCut.IsDone()) { + Base::Console().Warning("DVS: Section cut has failed in %s\n",getNameInDocument()); + continue; + } + TopoDS_Shape cut = mkCut.Shape(); + builder.Add(pieces, cut); + outdb++; } - TopoDS_Shape rawShape = mkCut.Shape(); + TopoDS_Shape rawShape = pieces; if (debugSection()) { BRepTools::Write(myShape, "DVSCopy.brep"); //debug BRepTools::Write(aProjFace, "DVSFace.brep"); //debug From 8a7ebe6fadb8409bd95778f5c3110853ed46d18b Mon Sep 17 00:00:00 2001 From: WandererFan Date: Fri, 17 Apr 2020 21:53:03 -0400 Subject: [PATCH 02/33] [TD]Piecewise Detail Algo --- src/Mod/TechDraw/App/DrawViewDetail.cpp | 63 +++++++++++++++---------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewDetail.cpp b/src/Mod/TechDraw/App/DrawViewDetail.cpp index 53c02f5894..8a491fea4a 100644 --- a/src/Mod/TechDraw/App/DrawViewDetail.cpp +++ b/src/Mod/TechDraw/App/DrawViewDetail.cpp @@ -255,9 +255,9 @@ void DrawViewDetail::detailExec(TopoDS_Shape shape, double scale = getScale(); BRepBuilderAPI_Copy BuilderCopy(shape); - TopoDS_Shape copyShape = BuilderCopy.Shape(); + TopoDS_Shape myShape = BuilderCopy.Shape(); - gp_Pnt gpCenter = TechDraw::findCentroid(copyShape, + gp_Pnt gpCenter = TechDraw::findCentroid(myShape, dirDetail); Base::Vector3d shapeCenter = Base::Vector3d(gpCenter.X(),gpCenter.Y(),gpCenter.Z()); m_saveCentroid = shapeCenter; //centroid of original shape @@ -265,7 +265,7 @@ void DrawViewDetail::detailExec(TopoDS_Shape shape, if (dvs != nullptr) { //section cutShape should already be on origin } else { - copyShape = TechDraw::moveShape(copyShape, //centre shape on origin + myShape = TechDraw::moveShape(myShape, //centre shape on origin -shapeCenter); } @@ -280,7 +280,7 @@ void DrawViewDetail::detailExec(TopoDS_Shape shape, Bnd_Box bbxSource; bbxSource.SetGap(0.0); - BRepBndLib::Add(copyShape, bbxSource); + BRepBndLib::Add(myShape, bbxSource); double diag = sqrt(bbxSource.SquareExtent()); Base::Vector3d toolPlaneOrigin = anchorOffset3d + dirDetail * diag * -1.0; //center tool about anchor @@ -301,33 +301,47 @@ void DrawViewDetail::detailExec(TopoDS_Shape shape, gp_Vec extrudeDir(extrudeVec.x,extrudeVec.y,extrudeVec.z); TopoDS_Shape tool = BRepPrimAPI_MakePrism(aProjFace, extrudeDir, false, true).Shape(); - BRepAlgoAPI_Common mkCommon(copyShape,tool); - if (!mkCommon.IsDone()) { - Base::Console().Warning("DVD::execute - %s - detail cut operation failed (1)\n", getNameInDocument()); - return; - } - if (mkCommon.Shape().IsNull()) { - Base::Console().Warning("DVD::execute - %s - detail cut operation failed (2)\n", getNameInDocument()); - return; - } - //Did we get a solid? - TopExp_Explorer xp; - xp.Init(mkCommon.Shape(),TopAbs_SOLID); - if (!(xp.More() == Standard_True)) { - Base::Console().Warning("DVD::execute - mkCommon.Shape is not a solid!\n"); + BRep_Builder builder; + TopoDS_Compound pieces; + builder.MakeCompound(pieces); + TopExp_Explorer expl(myShape, TopAbs_SOLID); + int indb = 0; + int outdb = 0; + for (; expl.More(); expl.Next()) { + indb++; + const TopoDS_Solid& s = TopoDS::Solid(expl.Current()); + + BRepAlgoAPI_Common mkCommon(s,tool); + if (!mkCommon.IsDone()) { +// Base::Console().Warning("DVD::execute - %s - detail cut operation failed (1)\n", getNameInDocument()); + continue; + } + if (mkCommon.Shape().IsNull()) { +// Base::Console().Warning("DVD::execute - %s - detail cut operation failed (2)\n", getNameInDocument()); + continue; + } + //this might be overkill for piecewise algo + //Did we get at least 1 solid? + TopExp_Explorer xp; + xp.Init(mkCommon.Shape(),TopAbs_SOLID); + if (!(xp.More() == Standard_True)) { +// Base::Console().Warning("DVD::execute - mkCommon.Shape is not a solid!\n"); + continue; + } + builder.Add(pieces, mkCommon.Shape()); + outdb++; } - TopoDS_Shape detail = mkCommon.Shape(); if (debugDetail()) { BRepTools::Write(tool, "DVDTool.brep"); //debug - BRepTools::Write(copyShape, "DVDCopy.brep"); //debug - BRepTools::Write(detail, "DVDCommon.brep"); //debug + BRepTools::Write(myShape, "DVDCopy.brep"); //debug + BRepTools::Write(pieces, "DVDCommon.brep"); //debug } Bnd_Box testBox; testBox.SetGap(0.0); - BRepBndLib::Add(detail, testBox); + BRepBndLib::Add(pieces, testBox); if (testBox.IsVoid()) { TechDraw::GeometryObject* go = getGeometryObject(); if (go != nullptr) { @@ -343,7 +357,7 @@ void DrawViewDetail::detailExec(TopoDS_Shape shape, // TopoDS_Compound Comp; // builder.MakeCompound(Comp); // builder.Add(Comp, tool); -// builder.Add(Comp, copyShape); +// builder.Add(Comp, myShape); gp_Pnt inputCenter; try { @@ -358,7 +372,8 @@ void DrawViewDetail::detailExec(TopoDS_Shape shape, gp_Ax2 viewAxis = dvp->getProjectionCS(stdOrg); //sb same CS as base view. //center shape on origin - TopoDS_Shape centeredShape = TechDraw::moveShape(detail, +// TopoDS_Shape centeredShape = TechDraw::moveShape(detail, + TopoDS_Shape centeredShape = TechDraw::moveShape(pieces, centroid * -1.0); TopoDS_Shape scaledShape = TechDraw::scaleShape(centeredShape, From a020dc2afcb878fa8293e459fec1b62cbe441058 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sun, 19 Apr 2020 21:26:55 +0200 Subject: [PATCH 03/33] FEM: meshtools, fix element names in face search --- src/Mod/Fem/femmesh/meshtools.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/Fem/femmesh/meshtools.py b/src/Mod/Fem/femmesh/meshtools.py index 8c2241d7f8..e8bc641764 100644 --- a/src/Mod/Fem/femmesh/meshtools.py +++ b/src/Mod/Fem/femmesh/meshtools.py @@ -1441,7 +1441,7 @@ def build_mesh_faces_of_volume_elements( else: FreeCAD.Console.PrintError( "Error in build_mesh_faces_of_volume_elements(): " - "hexa20: face not found! {}\n" + "tetra10: face not found! {}\n" .format(face_node_indexs) ) elif vol_node_ct == 4: @@ -1459,7 +1459,7 @@ def build_mesh_faces_of_volume_elements( else: FreeCAD.Console.PrintError( "Error in build_mesh_faces_of_volume_elements(): " - "hexa20: face not found! {}\n" + "tetra4: face not found! {}\n" .format(face_node_indexs) ) elif vol_node_ct == 20: @@ -1503,7 +1503,7 @@ def build_mesh_faces_of_volume_elements( else: FreeCAD.Console.PrintError( "Error in build_mesh_faces_of_volume_elements(): " - "hexa20: face not found! {}\n" + "hexa8: face not found! {}\n" .format(face_node_indexs) ) elif vol_node_ct == 15: @@ -1543,7 +1543,7 @@ def build_mesh_faces_of_volume_elements( else: FreeCAD.Console.PrintError( "Error in build_mesh_faces_of_volume_elements(): " - "pent6: face not found! {}\n" + "penta6: face not found! {}\n" .format(face_node_indexs) ) else: From 9dc688ff3b86b2c5f5fe86d6e42f333e1c57988b Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sun, 19 Apr 2020 21:36:26 +0200 Subject: [PATCH 04/33] FEM: result task panel, avoid zero float division --- src/Mod/Fem/femguiobjects/_ViewProviderFemResultMechanical.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/Fem/femguiobjects/_ViewProviderFemResultMechanical.py b/src/Mod/Fem/femguiobjects/_ViewProviderFemResultMechanical.py index 5c1551fc91..b4e25b14be 100644 --- a/src/Mod/Fem/femguiobjects/_ViewProviderFemResultMechanical.py +++ b/src/Mod/Fem/femguiobjects/_ViewProviderFemResultMechanical.py @@ -703,6 +703,8 @@ def get_displacement_scale_factor(res_obj): z_span = abs(p_z_max - p_z_min) span = max(x_span, y_span, z_span) max_disp = max(x_max, y_max, z_max) + if max_disp == 0.0: + return 0.0 # avoid float division by zero # FIXME - add max_allowed_disp to Preferences max_allowed_disp = 0.01 * span scale = max_allowed_disp / max_disp From 45aef7b028a54c5420db49ff3284fc7377475e90 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sun, 19 Apr 2020 21:56:35 +0200 Subject: [PATCH 05/33] FEM: meshtools, init empty node numbers in face search --- src/Mod/Fem/femmesh/meshtools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/Fem/femmesh/meshtools.py b/src/Mod/Fem/femmesh/meshtools.py index e8bc641764..c3f1cd0a96 100644 --- a/src/Mod/Fem/femmesh/meshtools.py +++ b/src/Mod/Fem/femmesh/meshtools.py @@ -1426,6 +1426,7 @@ def build_mesh_faces_of_volume_elements( FreeCAD.Console.PrintLog("VolElement: {}\n".format(veID)) vol_node_ct = len(femelement_table[veID]) face_node_indexs = sorted(face_nodenumber_table[veID]) + node_numbers = () if vol_node_ct == 10: FreeCAD.Console.PrintLog(" --> tetra10 --> tria6 face\n") # node order of face in tetra10 volume element From 1eef7064f8ad1d250e40fbd3d1d522dcca602d48 Mon Sep 17 00:00:00 2001 From: Adam Spontarelli Date: Sat, 28 Mar 2020 12:57:36 -0400 Subject: [PATCH 06/33] Initial addition of fcsprocket feature. This is a PartDesign tool that allows for the simple creation of ANSI standard roller chain sprockets. --- src/Mod/PartDesign/CMakeLists.txt | 19 +- .../PartDesign/Gui/Resources/PartDesign.qrc | 1 + .../Resources/icons/PartDesign_Sprocket.svg | 492 ++++++++++++++++++ src/Mod/PartDesign/Gui/Workbench.cpp | 7 +- src/Mod/PartDesign/InitGui.py | 1 + src/Mod/PartDesign/SprocketFeature.py | 232 +++++++++ src/Mod/PartDesign/SprocketFeature.ui | 232 +++++++++ src/Mod/PartDesign/fcsprocket/README.md | 24 + src/Mod/PartDesign/fcsprocket/__init__.py | 1 + src/Mod/PartDesign/fcsprocket/fcsprocket.py | 105 ++++ .../PartDesign/fcsprocket/fcsprocketdialog.py | 66 +++ src/Mod/PartDesign/fcsprocket/sprocket.py | 144 +++++ 12 files changed, 1320 insertions(+), 4 deletions(-) create mode 100644 src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Sprocket.svg create mode 100644 src/Mod/PartDesign/SprocketFeature.py create mode 100644 src/Mod/PartDesign/SprocketFeature.ui create mode 100644 src/Mod/PartDesign/fcsprocket/README.md create mode 100644 src/Mod/PartDesign/fcsprocket/__init__.py create mode 100644 src/Mod/PartDesign/fcsprocket/fcsprocket.py create mode 100644 src/Mod/PartDesign/fcsprocket/fcsprocketdialog.py create mode 100644 src/Mod/PartDesign/fcsprocket/sprocket.py diff --git a/src/Mod/PartDesign/CMakeLists.txt b/src/Mod/PartDesign/CMakeLists.txt index 177550408a..435ce86d35 100644 --- a/src/Mod/PartDesign/CMakeLists.txt +++ b/src/Mod/PartDesign/CMakeLists.txt @@ -16,6 +16,8 @@ if(BUILD_GUI) TestPartDesignGui.py InvoluteGearFeature.py InvoluteGearFeature.ui + SprocketFeature.py + SprocketFeature.ui ) endif(BUILD_GUI) @@ -60,6 +62,13 @@ set(PartDesign_GearScripts fcgear/svggear.py ) +set(PartDesign_SprocketScripts + fcsprocket/__init__.py + fcsprocket/fcsprocket.py + fcsprocket/fcsprocketdialog.py + fcsprocket/sprocket.py +) + set(PartDesign_WizardShaft WizardShaft/__init__.py WizardShaft/WizardShaft.svg @@ -76,6 +85,7 @@ add_custom_target(PartDesignScripts ALL SOURCES ${PartDesign_OtherScripts} ${PartDesign_TestScripts} ${PartDesign_GearScripts} + ${PartDesign_SprocketScripts} ) fc_target_copy_resource(PartDesignScripts @@ -85,6 +95,7 @@ fc_target_copy_resource(PartDesignScripts ${PartDesign_OtherScripts} ${PartDesign_TestScripts} ${PartDesign_GearScripts} + ${PartDesign_SprocketScripts} ) INSTALL( @@ -113,7 +124,13 @@ INSTALL( ${PartDesign_GearScripts} DESTINATION Mod/PartDesign/fcgear - +) + +INSTALL( + FILES + ${PartDesign_SprocketScripts} + DESTINATION + Mod/PartDesign/fcsprocket ) if(BUILD_FEM) diff --git a/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc b/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc index e8db9a825e..34550529ba 100644 --- a/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc +++ b/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc @@ -37,6 +37,7 @@ icons/PartDesign_Revolution.svg icons/PartDesign_Scaled.svg icons/PartDesign_ShapeBinder.svg + icons/PartDesign_Sprocket.svg icons/PartDesign_SubShapeBinder.svg icons/PartDesign_Subtractive_Box.svg icons/PartDesign_Subtractive_Cone.svg diff --git a/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Sprocket.svg b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Sprocket.svg new file mode 100644 index 0000000000..15bc199d19 --- /dev/null +++ b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Sprocket.svg @@ -0,0 +1,492 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp index 350c14dbe3..b8489d36fe 100644 --- a/src/Mod/PartDesign/Gui/Workbench.cpp +++ b/src/Mod/PartDesign/Gui/Workbench.cpp @@ -509,9 +509,10 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "PartDesign_Boolean" << "Separator" //<< "PartDesign_Hole" - << "PartDesign_InvoluteGear" - << "Separator" - << "PartDesign_Migrate"; + << "PartDesign_Migrate" + << "PartDesign_Sprocket" + << "PartDesign_InvoluteGear"; + // For 0.13 a couple of python packages like numpy, matplotlib and others // are not deployed with the installer on Windows. Thus, the WizardShaft is diff --git a/src/Mod/PartDesign/InitGui.py b/src/Mod/PartDesign/InitGui.py index f25bd79358..c935ffb033 100644 --- a/src/Mod/PartDesign/InitGui.py +++ b/src/Mod/PartDesign/InitGui.py @@ -51,6 +51,7 @@ class PartDesignWorkbench ( Workbench ): import PartDesign try: from PartDesign import InvoluteGearFeature + from PartDesign import SprocketFeature except ImportError: print("Involute gear module cannot be loaded") #try: diff --git a/src/Mod/PartDesign/SprocketFeature.py b/src/Mod/PartDesign/SprocketFeature.py new file mode 100644 index 0000000000..d9824c49cd --- /dev/null +++ b/src/Mod/PartDesign/SprocketFeature.py @@ -0,0 +1,232 @@ +#*************************************************************************** +#* Copyright (c) 2020 Adam Spontarelli * +#* * +#* 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, Part +from fcsprocket import fcsprocket +from fcsprocket import sprocket + +if FreeCAD.GuiUp: + import FreeCADGui + from PySide import QtCore, QtGui + from FreeCADGui import PySideUic as uic + +__title__="PartDesign SprocketObject management" +__author__ = "Adam Spontarelli" +__url__ = "http://www.freecadweb.org" + + +def makeSprocket(name): + '''makeSprocket(name): makes a Sprocket''' + obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",name) + _Sprocket(obj) + if FreeCAD.GuiUp: + _ViewProviderSprocket(obj.ViewObject) + #FreeCAD.ActiveDocument.recompute() + if FreeCAD.GuiUp: + body=FreeCADGui.ActiveDocument.ActiveView.getActiveObject("pdbody") + part=FreeCADGui.ActiveDocument.ActiveView.getActiveObject("part") + if body: + body.Group=body.Group+[obj] + elif part: + part.Group=part.Group+[obj] + return obj + +class _CommandSprocket: + "the Fem Sprocket command definition" + def GetResources(self): + return {'Pixmap' : 'PartDesign_Sprocket', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PartDesign_Sprocket","Sprocket..."), + 'Accel': "", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PartDesign_Sprocket","Creates or edit the sprocket definition.")} + + def Activated(self): + + FreeCAD.ActiveDocument.openTransaction("Create Sprocket") + FreeCADGui.addModule("SprocketFeature") + FreeCADGui.doCommand("SprocketFeature.makeSprocket('Sprocket')") + FreeCADGui.doCommand("Gui.activeDocument().setEdit(App.ActiveDocument.ActiveObject.Name,0)") + + def IsActive(self): + if FreeCAD.ActiveDocument: + return True + else: + return False + + +class _Sprocket: + "The Sprocket object" + def __init__(self,obj): + self.Type = "Sprocket" + obj.addProperty("App::PropertyInteger","NumberOfTeeth","Sprocket","Number of gear teeth") + obj.addProperty("App::PropertyLength","Pitch","Sprocket","Chain Pitch") + obj.addProperty("App::PropertyLength","RollerDiameter","Sprocket","Roller Diameter") + obj.addProperty("App::PropertyString","ANSISize","Sprocket","ANSI Size") + + obj.NumberOfTeeth = 50 + obj.Pitch = "0.375 in" + obj.RollerDiameter = "0.20 in" + obj.ANSISize = "35" + + obj.Proxy = self + + + def execute(self,obj): + w = fcsprocket.FCWireBuilder() + sprocket.CreateSprocket(w, obj.Pitch.Value, obj.NumberOfTeeth, obj.RollerDiameter.Value) + + sprocketw = Part.Wire([o.toShape() for o in w.wire]) + obj.Shape = sprocketw + obj.positionBySupport(); + return + + +class _ViewProviderSprocket: + "A View Provider for the Sprocket object" + + def __init__(self,vobj): + vobj.Proxy = self + + def getIcon(self): + return ":/icons/PartDesign_Sprocket.svg" + + def attach(self, vobj): + self.ViewObject = vobj + self.Object = vobj.Object + + + def setEdit(self,vobj,mode): + taskd = _SprocketTaskPanel(self.Object,mode) + taskd.obj = vobj.Object + taskd.update() + FreeCADGui.Control.showDialog(taskd) + return True + + def unsetEdit(self,vobj,mode): + FreeCADGui.Control.closeDialog() + return + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + +class _SprocketTaskPanel: + '''The editmode TaskPanel for Sprocket objects''' + def __init__(self,obj,mode): + self.obj = obj + + self.form=FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/PartDesign/SprocketFeature.ui") + self.form.setWindowIcon(QtGui.QIcon(":/icons/PartDesign_Sprocket.svg")) + + QtCore.QObject.connect(self.form.Quantity_Pitch, QtCore.SIGNAL("valueChanged(double)"), self.pitchChanged) + QtCore.QObject.connect(self.form.Quantity_RollerDiameter, QtCore.SIGNAL("valueChanged(double)"), self.rollerDiameterChanged) + QtCore.QObject.connect(self.form.spinBox_NumberOfTeeth, QtCore.SIGNAL("valueChanged(int)"), self.numTeethChanged) + QtCore.QObject.connect(self.form.comboBox_ANSISize, QtCore.SIGNAL("currentTextChanged(const QString)"), self.ANSISizeChanged) + + self.update() + + if mode == 0: # fresh created + self.obj.Proxy.execute(self.obj) # calculate once + FreeCAD.Gui.SendMsgToActiveView("ViewFit") + + def transferTo(self): + "Transfer from the dialog to the object" + self.obj.NumberOfTeeth = self.form.spinBox_NumberOfTeeth.value() + self.obj.Pitch = self.form.Quantity_Pitch.text() + self.obj.RollerDiameter = self.form.Quantity_RollerDiameter.text() + self.obj.ANSISize = self.form.comboBox_ANSISize.currentText() + + def transferFrom(self): + "Transfer from the object to the dialog" + self.form.spinBox_NumberOfTeeth.setValue(self.obj.NumberOfTeeth) + self.form.Quantity_Pitch.setText(self.obj.Pitch.UserString) + self.form.Quantity_RollerDiameter.setText(self.obj.RollerDiameter.UserString) + self.form.comboBox_ANSISize.setCurrentText(self.obj.ANSISize) + + def pitchChanged(self, value): + self.obj.Pitch = value + self.obj.Proxy.execute(self.obj) + FreeCAD.Gui.SendMsgToActiveView("ViewFit") + + def ANSISizeChanged(self, size): + """ + ANSI B29.1-2011 standard roller chain sizes in USCS units (inches) + {size: [Pitch, Roller Diameter]} + """ + ANSIRollerTable = {"25": [0.250, 0.130], + "35": [0.375, 0.200], + "41": [0.500, 0.306], + "40": [0.500, 0.312], + "50": [0.625, 0.400], + "60": [0.750, 0.469], + "80": [1.000, 0.625], + "100":[1.250, 0.750], + "120":[1.500, 0.875], + "140":[1.750, 1.000], + "160":[2.000, 1.125], + "180":[2.250, 1.460], + "200":[2.500, 1.562], + "240":[3.000, 1.875]} + + self.obj.Pitch = str(ANSIRollerTable[size][0]) + " in" + self.obj.RollerDiameter = str(ANSIRollerTable[size][1]) + " in" + self.form.Quantity_Pitch.setText(self.obj.Pitch.UserString) + self.form.Quantity_RollerDiameter.setText(self.obj.RollerDiameter.UserString) + + self.obj.Proxy.execute(self.obj) + FreeCAD.Gui.SendMsgToActiveView("ViewFit") + + def rollerDiameterChanged(self, value): + self.obj.RollerDiameter = value + self.obj.Proxy.execute(self.obj) + + def numTeethChanged(self, value): + self.obj.NumberOfTeeth = value + self.obj.Proxy.execute(self.obj) + FreeCAD.Gui.SendMsgToActiveView("ViewFit") + + def getStandardButtons(self): + return int(QtGui.QDialogButtonBox.Ok) | int(QtGui.QDialogButtonBox.Cancel)| int(QtGui.QDialogButtonBox.Apply) + + def clicked(self,button): + if button == QtGui.QDialogButtonBox.Apply: + self.transferTo() + self.obj.Proxy.execute(self.obj) + + def update(self): + 'fills the widgets' + self.transferFrom() + + def accept(self): + self.transferTo() + FreeCAD.ActiveDocument.recompute() + FreeCADGui.ActiveDocument.resetEdit() + + def reject(self): + FreeCADGui.ActiveDocument.resetEdit() + FreeCAD.ActiveDocument.abortTransaction() + + + +if FreeCAD.GuiUp: + FreeCADGui.addCommand('PartDesign_Sprocket',_CommandSprocket()) diff --git a/src/Mod/PartDesign/SprocketFeature.ui b/src/Mod/PartDesign/SprocketFeature.ui new file mode 100644 index 0000000000..c1960a8837 --- /dev/null +++ b/src/Mod/PartDesign/SprocketFeature.ui @@ -0,0 +1,232 @@ + + + SprocketParameter + + + + 0 + 0 + 195 + 142 + + + + Sprocket parameter + + + + + + + Number of teeth: + + + + + + + 3 + + + 9999 + + + 50 + + + + + + + + ANSI Size: + + + + + + + + + 25 + + + + + 35 + + + + + 41 + + + + + 40 + + + + + 50 + + + + + 60 + + + + + 80 + + + + + 100 + + + + + 120 + + + + + 140 + + + + + 160 + + + + + 180 + + + + + 200 + + + + + 240 + + + + + + + + + + Chain Pitch: + + + + + + + + + + 0 + 0 + + + + + 80 + 20 + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + in + + + 3 + + + 2000.000000000000000 + + + 0.01 + + + + 0.001 + + + 0.375 + + + + + + + + + Roller Diameter: + + + + + + + + 0 + 0 + + + + + 80 + 20 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + in + + + 3 + + + 50.000000000000000 + + + 0.01 + + + + 0.01 + + + 0.20 + + + + + + + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
+ + +
diff --git a/src/Mod/PartDesign/fcsprocket/README.md b/src/Mod/PartDesign/fcsprocket/README.md new file mode 100644 index 0000000000..3759e676ce --- /dev/null +++ b/src/Mod/PartDesign/fcsprocket/README.md @@ -0,0 +1,24 @@ +================================================ + FCSprocket: a Sprocket Generator for FreeCAD +================================================ + +This is a simple sprocket generation tool. Sprockets are used in combination +with roller chain to transmit power. The tooth profiles are drawn according +to ANSI standards from the methods outlined in: + + Standard handbook of chains : chains for power transmission and material + handling. Boca Raton: Taylor & Francis, 2006. Print. + + AND + + Oberg, Erik, et al. Machinery's handbook : a reference book for the + mechanical engineer, designer, manufacturing engineer, draftsman, + toolmaker, and machinist. New York: Industrial Press, 2016. Print. + + +This code is based on the work of David Douard and his implementation of the +gear generator (fcgear) found in FreeCAD. + + +Copyright 2020 Adam Spontarelli . +Distributed under the LGPL licence. diff --git a/src/Mod/PartDesign/fcsprocket/__init__.py b/src/Mod/PartDesign/fcsprocket/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/Mod/PartDesign/fcsprocket/__init__.py @@ -0,0 +1 @@ + diff --git a/src/Mod/PartDesign/fcsprocket/fcsprocket.py b/src/Mod/PartDesign/fcsprocket/fcsprocket.py new file mode 100644 index 0000000000..b27309c470 --- /dev/null +++ b/src/Mod/PartDesign/fcsprocket/fcsprocket.py @@ -0,0 +1,105 @@ +# (c) 2020 Adam Spontarelli +# +# 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. +# +# FCGear 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 FCGear; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + +from math import cos, sin, pi, acos, asin, atan, sqrt + +import FreeCAD, Part +from FreeCAD import Base, Console +from . import sprocket +rotate = sprocket.rotate + +def makeSprocket(P, N, Dr): + if FreeCAD.ActiveDocument is None: + FreeCAD.newDocument("Sprocket") + doc = FreeCAD.ActiveDocument + w = FCWireBuilder() + sprocket.CreateSprocket(w, P, N, Dr) + sprocketw = Part.Wire([o.toShape() for o in w.wire]) + sprocket = doc.addObject("Part::Feature", "Sprocket") + sprocket.Shape = sprocketw + return sprocket + +class FCWireBuilder(object): + """A helper class to prepare a Part.Wire object""" + def __init__(self): + self.pos = None + self.theta = 0.0 + self.wire = [] + + def move(self, p): + """set current position""" + self.pos = Base.Vector(*p) + + def line(self, p): + """Add a segment between self.pos and p""" + p = rotate(p, self.theta) + end = Base.Vector(*p) + self.wire.append(Part.LineSegment(self.pos, end)) + self.pos = end + + def arc(self, p, r, sweep): + """"Add an arc from self.pos to p which radius is r + sweep (0 or 1) determine the orientation of the arc + """ + p = rotate(p, self.theta) + end = Base.Vector(*p) + mid = Base.Vector(*(midpoints(p, self.pos, r)[sweep])) + self.wire.append(Part.Arc(self.pos, mid, end)) + self.pos = end + + def curve(self, *points): + """Add a Bezier curve from self.pos to points[-1] + every other points are the control points of the Bezier curve (which + will thus be of degree len(points) ) + """ + points = [Base.Vector(*rotate(p, self.theta)) for p in points] + bz = Part.BezierCurve() + bz.setPoles([self.pos] + points) + self.wire.append(bz) + self.pos = points[-1] + + def close(self): + pass + +def midpoints(p1, p2, r): + """A very ugly function that returns the midpoint of a p1 and p2 + on the circle which radius is r and which pass through p1 and + p2 + + Return the 2 possible solutions + """ + vx, vy = p2[0]-p1[0], p2[1]-p1[1] + b = (vx**2 + vy**2)**.5 + v = (vx/b, vy/b) + cosA = b**2 / (2*b*r) + A = acos(cosA) + + vx, vy = rotate(v, A) + c1 = (p1[0]+r*vx, p1[1]+r*vy) + m1x, m1y = ((p1[0]+p2[0])/2 - c1[0], (p1[1]+p2[1])/2 - c1[1]) + dm1 = (m1x**2+m1y**2)**.5 + m1x, m1y = (c1[0] + r*m1x/dm1, c1[1] + r*m1y/dm1) + m1 = (m1x, m1y) + + vx, vy = rotate(v, -A) + c2 = (p1[0]+r*vx, p1[1]+r*vy) + m2x, m2y = ((p1[0]+p2[0])/2 - c2[0], (p1[1]+p2[1])/2 - c2[1]) + dm2 = (m2x**2+m2y**2)**.5 + m2x, m2y = (c2[0] + r*m2x/dm2, c2[1] + r*m2y/dm2) + m2 = (m2x, m2y) + + return m1, m2 diff --git a/src/Mod/PartDesign/fcsprocket/fcsprocketdialog.py b/src/Mod/PartDesign/fcsprocket/fcsprocketdialog.py new file mode 100644 index 0000000000..7360a7235d --- /dev/null +++ b/src/Mod/PartDesign/fcsprocket/fcsprocketdialog.py @@ -0,0 +1,66 @@ +# (c) 2020 Adam Spontarelli +# +# 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. +# +# FCGear 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 FCGear; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + +from PySide import QtGui as qt +import fcsprocket +import FreeCAD, FreeCADGui + +class SprocketCreationFrame(qt.QFrame): + def __init__(self, parent=None): + super(SprocketCreationFrame, self).__init__(parent) + self.P = qt.QSpinBox(value=0.375) + self.N = qt.QDoubleSpinBox(value=45) + self.Dr = qt.QDoubleSpinBox(value=0.20) + + l = qt.QFormLayout(self) + l.setFieldGrowthPolicy(l.ExpandingFieldsGrow) + l.addRow('Number of teeth:', self.N) + l.addRow('Chain Pitch (in):', self.P) + l.addRow('Roller Diameter (in):', self.Dr) + + +class SprocketDialog(qt.QDialog): + def __init__(self, parent=None): + super(SprocketDialog, self).__init__(parent) + self.gc = SprocketCreationFrame() + + btns = qt.QDialogButtonBox.Ok | qt.QDialogButtonBox.Cancel + buttonBox = qt.QDialogButtonBox(btns, + accepted=self.accept, + rejected=self.reject) + l = qt.QVBoxLayout(self) + l.addWidget(self.gc) + l.addWidget(buttonBox) + self.setWindowTitle('Sprocket creation dialog') + + def accept(self): + if FreeCAD.ActiveDocument is None: + FreeCAD.newDocument("Sprocket") + + gear = fcgear.makeSprocket(self.gc.m.value(), + self.gc.Z.value(), + self.gc.angle.value(), + not self.gc.split.currentIndex()) + FreeCADGui.SendMsgToActiveView("ViewFit") + return super(SprocketDialog, self).accept() + + +if __name__ == '__main__': + a = qt.QApplication([]) + w = SprocketDialog() + w.show() + a.exec_() diff --git a/src/Mod/PartDesign/fcsprocket/sprocket.py b/src/Mod/PartDesign/fcsprocket/sprocket.py new file mode 100644 index 0000000000..a35bb48e70 --- /dev/null +++ b/src/Mod/PartDesign/fcsprocket/sprocket.py @@ -0,0 +1,144 @@ +# (c) 2020 Adam Spontarelli +# +# 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. +# +# FCGear 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 FCGear; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + +from math import cos, sin, tan, sqrt, radians, acos, atan, asin, degrees +import math + +import sys +if sys.version_info.major >= 3: + xrange = range + + +def CreateSprocket(w, P, N, Dr): + """ + Create a sprocket + + w is the wirebuilder object (in which the sprocket will be constructed) + P is the chain pitch + N is the number of teeth + Dr is the roller diameter + + Remaining variables can be found in Standard Handbook of Chains + """ + Ds = 1.005 * Dr + (0.003 * 25.4) + R = Ds / 2 + alpha = 35 + 60/N + beta = 18 - 56 / N + M = 0.8 * Dr * cos(radians(35) + radians(60/N)) + T = 0.8 * Dr * sin(radians(35) + radians(60/N)) + E = 1.3025 * Dr + (0.0015 * 25.4) + W = 1.4 * Dr * cos(radians(180/N)) + V = 1.4 * Dr * sin(radians(180/N)) + F = Dr * (0.8 * cos(radians(18) - radians(56)/N) + 1.4 * + cos(radians(17) - radians(64) / N) - 1.3025) - (0.0015 * 25.4) + PD = P / (sin(radians(180)/N)) + H = sqrt(F**2 - (1.4 * Dr - P/2)**2) + OD = P * (0.6 + 1/tan(radians(180/N))) + + # The sprocket tooth gullet consists of four segments + x0 = 0 + y0 = PD/2 - R + + # ---- Segment 1 ----- + alpha = 35 + 60/N + x1 = -R * cos(radians(alpha)) + y1 = PD/2 - R * sin(radians(alpha)) + arc_end = [x1, y1] + + # ---- Segment 2 ----- + alpha = 35 + 60/N + beta = 18 - 56 / N + x2 = M - E * cos(radians(alpha-beta)) + y2 = T - E * sin(radians(alpha-beta)) + PD/2 + + # # ---- Segment 3 ----- + y2o = y2 - PD/2 + hyp = sqrt((-W-x2)**2 + (-V-y2o)**2) + AP = sqrt(hyp**2 - F**2) + gamma = atan((y2o + V)/(x2 + W)) + alpha = asin(AP / hyp) + beta = 180 - (90 - degrees(alpha)) - (90 - degrees(gamma)) + x3o = AP * sin(radians(beta)) + y3o = AP * cos(radians(beta)) + x3 = x2 - x3o + y3 = y2 + y3o + + # ---- Segment 4 ----- + alpha = 180/N + m = -1/tan(radians(alpha)) + yf = PD/2 - V + A = 1 + m**2 + B = 2*m*yf - 2*W + C = W**2 + yf**2 - F**2 + # x4a = (-B - sqrt(B**2 - 4 * A * C)) / (2*A) + x4b = (-B + sqrt(B**2 - 4 * A * C)) / (2*A) + x4 = -x4b + y4 = m * x4 + + p0 = [x0,y0] + p1 = [x1,y1] + p2 = [x2,y2] + p3 = [x3,y3] + p4 = [x4,y4] + p5 = [-x1,y1] + p6 = [-x2,y2] + p7 = [-x3,y3] + p8 = [-x4,y4] + + w.move(p4) # vectors are lists [x,y] + w.arc(p3, F, 0) + w.line(p2) + w.arc(p1, E, 1) + w.arc(p0, R, 1) + + # ---- Mirror ----- + w.arc(p5, R, 1) + w.arc(p6, E, 1) + w.line(p7) + w.arc(p8, F, 0) + + # ---- Polar Array ---- + alpha = -radians(360/N) + for n in range(1,N): + # falling gullet slope + w.arc(rotate(p3, alpha*n), F, 0) + w.line(rotate(p2, alpha*n)) + w.arc(rotate(p1, alpha*n), E, 1) + w.arc(rotate(p0, alpha*n), R, 1) + + # rising gullet slope + w.arc(rotate(p5, alpha*n), R, 1) + w.line(rotate(p6, alpha*n)) + w.arc(rotate(p7, alpha*n), E, 0) + w.arc(rotate(p8, alpha*n), F, 0) + + w.close() + + return w + + +def rotate(pt, rads): + """ + rotate pt by rads radians about origin + """ + sinA = sin(rads) + cosA = cos(rads) + return (pt[0] * cosA - pt[1] * sinA, + pt[0] * sinA + pt[1] * cosA) + + + From 1123e271a9c7abbb8c5e6232af122a355d5f103f Mon Sep 17 00:00:00 2001 From: Adam Spontarelli Date: Mon, 30 Mar 2020 16:23:55 -0400 Subject: [PATCH 07/33] Converted class names from private to public and corrected docstring formatting, per feedback from pull request --- src/Mod/PartDesign/SprocketFeature.py | 46 +++++++++++++-------- src/Mod/PartDesign/fcsprocket/fcsprocket.py | 2 +- src/Mod/PartDesign/fcsprocket/sprocket.py | 5 --- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/Mod/PartDesign/SprocketFeature.py b/src/Mod/PartDesign/SprocketFeature.py index d9824c49cd..5fa8f7fd31 100644 --- a/src/Mod/PartDesign/SprocketFeature.py +++ b/src/Mod/PartDesign/SprocketFeature.py @@ -34,11 +34,13 @@ __url__ = "http://www.freecadweb.org" def makeSprocket(name): - '''makeSprocket(name): makes a Sprocket''' + """ + makeSprocket(name): makes a Sprocket + """ obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",name) - _Sprocket(obj) + Sprocket(obj) if FreeCAD.GuiUp: - _ViewProviderSprocket(obj.ViewObject) + ViewProviderSprocket(obj.ViewObject) #FreeCAD.ActiveDocument.recompute() if FreeCAD.GuiUp: body=FreeCADGui.ActiveDocument.ActiveView.getActiveObject("pdbody") @@ -49,8 +51,10 @@ def makeSprocket(name): part.Group=part.Group+[obj] return obj -class _CommandSprocket: - "the Fem Sprocket command definition" +class CommandSprocket: + """ + the Fem Sprocket command definition + """ def GetResources(self): return {'Pixmap' : 'PartDesign_Sprocket', 'MenuText': QtCore.QT_TRANSLATE_NOOP("PartDesign_Sprocket","Sprocket..."), @@ -71,8 +75,10 @@ class _CommandSprocket: return False -class _Sprocket: - "The Sprocket object" +class Sprocket: + """ + The Sprocket object + """ def __init__(self,obj): self.Type = "Sprocket" obj.addProperty("App::PropertyInteger","NumberOfTeeth","Sprocket","Number of gear teeth") @@ -98,8 +104,10 @@ class _Sprocket: return -class _ViewProviderSprocket: - "A View Provider for the Sprocket object" +class ViewProviderSprocket: + """ + A View Provider for the Sprocket object + """ def __init__(self,vobj): vobj.Proxy = self @@ -111,9 +119,8 @@ class _ViewProviderSprocket: self.ViewObject = vobj self.Object = vobj.Object - def setEdit(self,vobj,mode): - taskd = _SprocketTaskPanel(self.Object,mode) + taskd = SprocketTaskPanel(self.Object,mode) taskd.obj = vobj.Object taskd.update() FreeCADGui.Control.showDialog(taskd) @@ -130,8 +137,10 @@ class _ViewProviderSprocket: return None -class _SprocketTaskPanel: - '''The editmode TaskPanel for Sprocket objects''' +class SprocketTaskPanel: + """ + The editmode TaskPanel for Sprocket objects + """ def __init__(self,obj,mode): self.obj = obj @@ -150,14 +159,18 @@ class _SprocketTaskPanel: FreeCAD.Gui.SendMsgToActiveView("ViewFit") def transferTo(self): - "Transfer from the dialog to the object" + """ + Transfer from the dialog to the object + """ self.obj.NumberOfTeeth = self.form.spinBox_NumberOfTeeth.value() self.obj.Pitch = self.form.Quantity_Pitch.text() self.obj.RollerDiameter = self.form.Quantity_RollerDiameter.text() self.obj.ANSISize = self.form.comboBox_ANSISize.currentText() def transferFrom(self): - "Transfer from the object to the dialog" + """ + Transfer from the object to the dialog + """ self.form.spinBox_NumberOfTeeth.setValue(self.obj.NumberOfTeeth) self.form.Quantity_Pitch.setText(self.obj.Pitch.UserString) self.form.Quantity_RollerDiameter.setText(self.obj.RollerDiameter.UserString) @@ -214,7 +227,6 @@ class _SprocketTaskPanel: self.obj.Proxy.execute(self.obj) def update(self): - 'fills the widgets' self.transferFrom() def accept(self): @@ -229,4 +241,4 @@ class _SprocketTaskPanel: if FreeCAD.GuiUp: - FreeCADGui.addCommand('PartDesign_Sprocket',_CommandSprocket()) + FreeCADGui.addCommand('PartDesign_Sprocket', CommandSprocket()) diff --git a/src/Mod/PartDesign/fcsprocket/fcsprocket.py b/src/Mod/PartDesign/fcsprocket/fcsprocket.py index b27309c470..533c5d47c5 100644 --- a/src/Mod/PartDesign/fcsprocket/fcsprocket.py +++ b/src/Mod/PartDesign/fcsprocket/fcsprocket.py @@ -1,4 +1,4 @@ -# (c) 2020 Adam Spontarelli +# (c) 2014 David Douard # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License (LGPL) diff --git a/src/Mod/PartDesign/fcsprocket/sprocket.py b/src/Mod/PartDesign/fcsprocket/sprocket.py index a35bb48e70..9c1bc1c6b2 100644 --- a/src/Mod/PartDesign/fcsprocket/sprocket.py +++ b/src/Mod/PartDesign/fcsprocket/sprocket.py @@ -16,13 +16,8 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 from math import cos, sin, tan, sqrt, radians, acos, atan, asin, degrees -import math -import sys -if sys.version_info.major >= 3: - xrange = range - def CreateSprocket(w, P, N, Dr): """ Create a sprocket From add624353da123fbb3aba8f25c888fd54a0187df Mon Sep 17 00:00:00 2001 From: Adam Spontarelli Date: Mon, 30 Mar 2020 10:30:47 -0400 Subject: [PATCH 08/33] Converted class names from private to public, per feedback from pull request --- src/Mod/PartDesign/SprocketFeature.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Mod/PartDesign/SprocketFeature.py b/src/Mod/PartDesign/SprocketFeature.py index 5fa8f7fd31..0cfa0c9adc 100644 --- a/src/Mod/PartDesign/SprocketFeature.py +++ b/src/Mod/PartDesign/SprocketFeature.py @@ -52,9 +52,11 @@ def makeSprocket(name): return obj class CommandSprocket: + """ the Fem Sprocket command definition """ + def GetResources(self): return {'Pixmap' : 'PartDesign_Sprocket', 'MenuText': QtCore.QT_TRANSLATE_NOOP("PartDesign_Sprocket","Sprocket..."), @@ -79,6 +81,7 @@ class Sprocket: """ The Sprocket object """ + def __init__(self,obj): self.Type = "Sprocket" obj.addProperty("App::PropertyInteger","NumberOfTeeth","Sprocket","Number of gear teeth") @@ -138,9 +141,13 @@ class ViewProviderSprocket: class SprocketTaskPanel: +<<<<<<< HEAD """ The editmode TaskPanel for Sprocket objects """ +======= + '''The editmode TaskPanel for Sprocket objects''' +>>>>>>> 07b2401c1... Converted class names from private to public, per feedback from pull request def __init__(self,obj,mode): self.obj = obj From 55e537d79dd25ec2300cc073225c16774f576ef2 Mon Sep 17 00:00:00 2001 From: carlopav Date: Sat, 21 Mar 2020 10:09:34 +0100 Subject: [PATCH 09/33] [Draft] Improved Snapper Toolbar Behaviour Changed snap toolbar behaviour: - create a list of available snaps (Gui.Snapper.snaps) - make it consistent with Snap Gui Commands (in gui_snaps module) - create a list of active snaps (Gui.Snapper.active_snaps) - refactor the isEnabled() method to allow it to check if the given snap is in Gui.Snapper.active_snaps and not if the snap toolbar button isChecked() - updated and reordered the new list of gui snap commands in draftutils.init_tools and used it as a base to refactor the creation of draft toolbar - updated all the draft snap gui tools to make them control the toolbar buttons directly . . . --- src/Mod/Draft/DraftGui.py | 3 +- src/Mod/Draft/draftguitools/gui_snapper.py | 438 +++++++++++---------- src/Mod/Draft/draftguitools/gui_snaps.py | 20 +- src/Mod/Draft/draftutils/init_tools.py | 18 +- 4 files changed, 259 insertions(+), 220 deletions(-) diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index 4c12910ac3..8076310f39 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -1893,8 +1893,7 @@ class DraftToolBar: return str(a) def togglesnap(self): - if hasattr(FreeCADGui,"Snapper"): - FreeCADGui.Snapper.toggle() + FreeCADGui.doCommand('FreeCADGui.runCommand("Draft_Snap_Lock")') def togglenearsnap(self): if hasattr(FreeCADGui,"Snapper"): diff --git a/src/Mod/Draft/draftguitools/gui_snapper.py b/src/Mod/Draft/draftguitools/gui_snapper.py index 37a4a9e211..542acf76d8 100644 --- a/src/Mod/Draft/draftguitools/gui_snapper.py +++ b/src/Mod/Draft/draftguitools/gui_snapper.py @@ -39,19 +39,19 @@ import math from pivy import coin from PySide import QtCore, QtGui -import FreeCAD -import FreeCADGui +import FreeCAD as App +import FreeCADGui as Gui import Draft import DraftVecUtils from FreeCAD import Vector import draftguitools.gui_trackers as trackers +from draftutils.init_tools import get_draft_snap_commands from draftutils.messages import _msg, _wrn __title__ = "FreeCAD Draft Snap tools" __author__ = "Yorik van Havre" __url__ = "https://www.freecadweb.org" - class Snapper: """Classes to manage snapping in Draft and Arch. @@ -117,6 +117,27 @@ class Snapper: self.callbackMove = None self.snapObjectIndex = 0 + # snap keys, it's important tha they are in this order for + # saving in preferences and for properly restore the toolbar + self.snaps = ['Lock', # 0 + 'Near', # 1 former "passive" snap + 'Extension', # 2 + 'Parallel', # 3 + 'Grid', # 4 + "Endpoint", # 5 + 'Midpoint', # 6 + 'Perpendicular', # 7 + 'Angle', # 8 + 'Center', # 9 + 'Ortho', # 10 + 'Intersection', # 11 + 'Special', # 12 + 'Dimensions', # 13 + 'WorkingPlane' # 14 + ] + + self.init_active_snaps() + # the snapmarker has "dot","circle" and "square" available styles if self.snapStyle: self.mk = coll.OrderedDict([('passive', 'empty'), @@ -159,6 +180,19 @@ class Snapper: ('intersection', ':/icons/Snap_Intersection.svg'), ('special', ':/icons/Snap_Special.svg')]) + def init_active_snaps(self): + """ + set self.active_snaps according to user prefs + """ + self.active_snaps = [] + param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") + snap_modes = param.GetString("snapModes") + i = 0 + for snap in snap_modes: + if bool(int(snap)): + self.active_snaps.append(self.snaps[i]) + i += 1 + def cstr(self, lastpoint, constrain, point): """Return constraints if needed.""" if constrain or self.mask: @@ -197,8 +231,8 @@ class Snapper: if not hasattr(self, "toolbar"): self.makeSnapToolBar() - mw = FreeCADGui.getMainWindow() - bt = mw.findChild(QtGui.QToolBar, "Draft Snap") + mw = Gui.getMainWindow() + bt = mw.findChild(QtGui.QToolBar,"Draft Snap") if not bt: mw.addToolBar(self.toolbar) else: @@ -310,7 +344,7 @@ class Snapper: subname = self.snapInfo['SubName'] obj = parent.getSubObject(subname, retType=1) else: - obj = FreeCAD.ActiveDocument.getObject(self.snapInfo['Object']) + obj = App.ActiveDocument.getObject(self.snapInfo['Object']) parent = obj subname = self.snapInfo['Component'] if not obj: @@ -434,9 +468,9 @@ class Snapper: # calculating the nearest snap point shortest = 1000000000000000000 - origin = Vector(self.snapInfo['x'], - self.snapInfo['y'], - self.snapInfo['z']) + origin = App.Vector(self.snapInfo['x'], + self.snapInfo['y'], + self.snapInfo['z']) winner = None fp = point for snap in snaps: @@ -454,7 +488,7 @@ class Snapper: if self.radius: dv = point.sub(winner[2]) if (dv.Length > self.radius): - if (not oldActive) and self.isEnabled("passive"): + if (not oldActive) and self.isEnabled("Near"): winner = self.snapToVertex(self.snapInfo) # setting the cursors @@ -482,8 +516,8 @@ class Snapper: def toWP(self, point): """Project the given point on the working plane, if needed.""" if self.isEnabled("WorkingPlane"): - if hasattr(FreeCAD, "DraftWorkingPlane"): - return FreeCAD.DraftWorkingPlane.projectPoint(point) + if hasattr(App, "DraftWorkingPlane"): + return App.DraftWorkingPlane.projectPoint(point) return point def getApparentPoint(self, x, y): @@ -491,14 +525,14 @@ class Snapper: view = Draft.get3DView() pt = view.getPoint(x, y) if self.mask != "z": - if hasattr(FreeCAD, "DraftWorkingPlane"): + if hasattr(App,"DraftWorkingPlane"): if view.getCameraType() == "Perspective": camera = view.getCameraNode() p = camera.getField("position").getValue() - dv = pt.sub(Vector(p[0], p[1], p[2])) + dv = pt.sub(App.Vector(p[0], p[1], p[2])) else: dv = view.getViewDirection() - return FreeCAD.DraftWorkingPlane.projectPoint(pt, dv) + return App.DraftWorkingPlane.projectPoint(pt, dv) return pt def snapToDim(self, obj): @@ -526,7 +560,7 @@ class Snapper: self.extLine.on() self.setCursor(tsnap[1]) return tsnap[2], eline - if self.isEnabled("extension"): + if self.isEnabled("Extension"): tsnap = self.snapToExtOrtho(last, constrain, eline) if tsnap: if (tsnap[0].sub(point)).Length < self.radius: @@ -553,10 +587,10 @@ class Snapper: self.setCursor(tsnap[1]) return tsnap[2], eline - for o in (self.lastObj[1], self.lastObj[0]): - if o and (self.isEnabled('extension') - or self.isEnabled('parallel')): - ob = FreeCAD.ActiveDocument.getObject(o) + for o in [self.lastObj[1], self.lastObj[0]]: + if o and (self.isEnabled('Extension') + or self.isEnabled('Parallel')): + ob = App.ActiveDocument.getObject(o) if ob: if ob.isDerivedFrom("Part::Feature"): edges = ob.Shape.Edges @@ -572,7 +606,7 @@ class Snapper: np = self.getPerpendicular(e,point) if not DraftGeomUtils.isPtOnEdge(np,e): if (np.sub(point)).Length < self.radius: - if self.isEnabled('extension'): + if self.isEnabled('Extension'): if np != e.Vertexes[0].Point: p0 = e.Vertexes[0].Point if self.tracker and not self.selectMode: @@ -603,7 +637,7 @@ class Snapper: self.lastExtensions[0] = ne return np,ne else: - if self.isEnabled('parallel'): + if self.isEnabled('Parallel'): if last: ve = DraftGeomUtils.vec(e) if not DraftVecUtils.isNull(ve): @@ -620,7 +654,7 @@ class Snapper: def snapToCrossExtensions(self, point): """Snap to the intersection of the last 2 extension lines.""" - if self.isEnabled('extension'): + if self.isEnabled('Extension'): if len(self.lastExtensions) == 2: np = DraftGeomUtils.findIntersection(self.lastExtensions[0], self.lastExtensions[1], True, True) if np: @@ -648,19 +682,19 @@ class Snapper: return p return None - def snapToPolar(self, point, last): + def snapToPolar(self,point,last): """Snap to polar lines from the given point.""" - if self.isEnabled('ortho') and (not self.mask): + if self.isEnabled('Ortho') and (not self.mask): if last: vecs = [] - if hasattr(FreeCAD, "DraftWorkingPlane"): - ax = [FreeCAD.DraftWorkingPlane.u, - FreeCAD.DraftWorkingPlane.v, - FreeCAD.DraftWorkingPlane.axis] + if hasattr(App,"DraftWorkingPlane"): + ax = [App.DraftWorkingPlane.u, + App.DraftWorkingPlane.v, + App.DraftWorkingPlane.axis] else: - ax = [FreeCAD.Vector(1, 0, 0), - FreeCAD.Vector(0, 1, 0), - FreeCAD.Vector(0, 0, 1)] + ax = [App.Vector(1, 0, 0), + App.Vector(0, 1, 0), + App.Vector(0, 0, 1)] for a in self.polarAngles: if a == 90: vecs.extend([ax[0], ax[0].negative()]) @@ -691,7 +725,7 @@ class Snapper: """Return a grid snap point if available.""" if self.grid: if self.grid.Visible: - if self.isEnabled("grid"): + if self.isEnabled("Grid"): np = self.grid.getClosestNode(point) if np: dv = point.sub(np) @@ -707,7 +741,7 @@ class Snapper: def snapToEndpoints(self, shape): """Return a list of endpoints snap locations.""" snaps = [] - if self.isEnabled("endpoint"): + if self.isEnabled("Endpoint"): if hasattr(shape, "Vertexes"): for v in shape.Vertexes: snaps.append([v.Point, 'endpoint', self.toWP(v.Point)]) @@ -725,7 +759,7 @@ class Snapper: def snapToMidpoint(self, shape): """Return a list of midpoints snap locations.""" snaps = [] - if self.isEnabled("midpoint"): + if self.isEnabled("Midpoint"): if isinstance(shape, Part.Edge): mp = DraftGeomUtils.findMidpoint(shape) if mp: @@ -735,7 +769,7 @@ class Snapper: def snapToPerpendicular(self, shape, last): """Return a list of perpendicular snap locations.""" snaps = [] - if self.isEnabled("perpendicular"): + if self.isEnabled("Perpendicular"): if last: if isinstance(shape, Part.Edge): if DraftGeomUtils.geomType(shape) == "Line": @@ -758,7 +792,7 @@ class Snapper: def snapToOrtho(self, shape, last, constrain): """Return a list of ortho snap locations.""" snaps = [] - if self.isEnabled("ortho"): + if self.isEnabled("Ortho"): if constrain: if isinstance(shape, Part.Edge): if last: @@ -774,7 +808,7 @@ class Snapper: def snapToExtOrtho(self, last, constrain, eline): """Return an ortho X extension snap location.""" - if self.isEnabled("extension") and self.isEnabled("ortho"): + if self.isEnabled("Extension") and self.isEnabled("Ortho"): if constrain and last and self.constraintAxis and self.extLine: tmpEdge1 = Part.LineSegment(last, last.add(self.constraintAxis)).toShape() tmpEdge2 = Part.LineSegment(self.extLine.p1(), self.extLine.p2()).toShape() @@ -800,15 +834,15 @@ class Snapper: """ if not self.holdPoints: return None - if hasattr(FreeCAD, "DraftWorkingPlane"): - u = FreeCAD.DraftWorkingPlane.u - v = FreeCAD.DraftWorkingPlane.v + if hasattr(App, "DraftWorkingPlane"): + u = App.DraftWorkingPlane.u + v = App.DraftWorkingPlane.v else: - u = FreeCAD.Vector(1, 0, 0) - v = FreeCAD.Vector(0, 1, 0) + u = App.Vector(1, 0, 0) + v = App.Vector(0, 1, 0) if len(self.holdPoints) > 1: # first try mid points - if self.isEnabled("midpoint"): + if self.isEnabled("Midpoint"): l = list(self.holdPoints) for p1, p2 in itertools.combinations(l, 2): p3 = p1.add((p2.sub(p1)).multiply(0.5)) @@ -845,7 +879,7 @@ class Snapper: def snapToExtPerpendicular(self, last): """Return a perpendicular X extension snap location.""" - if self.isEnabled("extension") and self.isEnabled("perpendicular"): + if self.isEnabled("Extension") and self.isEnabled("Perpendicular"): if last and self.extLine: if self.extLine.p1() != self.extLine.p2(): tmpEdge = Part.LineSegment(self.extLine.p1(), self.extLine.p2()).toShape() @@ -856,7 +890,7 @@ class Snapper: def snapToElines(self, e1, e2): """Return a snap at the infinite intersection of the given edges.""" snaps = [] - if self.isEnabled("intersection") and self.isEnabled("extension"): + if self.isEnabled("Intersection") and self.isEnabled("Extension"): if e1 and e2: # get the intersection points pts = DraftGeomUtils.findIntersection(e1, e2, True, True) @@ -868,7 +902,7 @@ class Snapper: def snapToAngles(self, shape): """Return a list of angle snap locations.""" snaps = [] - if self.isEnabled("angle"): + if self.isEnabled("Angle"): rad = shape.Curve.Radius pos = shape.Curve.Center for i in (0, 30, 45, 60, 90, @@ -885,7 +919,7 @@ class Snapper: def snapToCenter(self, shape): """Return a list of center snap locations.""" snaps = [] - if self.isEnabled("center"): + if self.isEnabled("Center"): pos = shape.Curve.Center c = self.toWP(pos) if hasattr(shape.Curve, "Radius"): @@ -906,7 +940,7 @@ class Snapper: def snapToFace(self, shape): """Return a face center snap location.""" snaps = [] - if self.isEnabled("center"): + if self.isEnabled("Center"): pos = shape.CenterOfMass c = self.toWP(pos) snaps.append([pos, 'center', c]) @@ -915,10 +949,10 @@ class Snapper: def snapToIntersection(self, shape): """Return a list of intersection snap locations.""" snaps = [] - if self.isEnabled("intersection"): + if self.isEnabled("Intersection"): # get the stored objects to calculate intersections if self.lastObj[0]: - obj = FreeCAD.ActiveDocument.getObject(self.lastObj[0]) + obj = App.ActiveDocument.getObject(self.lastObj[0]) if obj: if obj.isDerivedFrom("Part::Feature") or (Draft.getType(obj) == "Axis"): if (not self.maxEdges) or (len(obj.Shape.Edges) <= self.maxEdges): @@ -947,7 +981,7 @@ class Snapper: def snapToPolygon(self, obj): """Return a list of polygon center snap locations.""" snaps = [] - if self.isEnabled("center"): + if self.isEnabled("Center"): c = obj.Placement.Base for edge in obj.Shape.Edges: p1 = edge.Vertexes[0].Point @@ -958,29 +992,16 @@ class Snapper: snaps.append([v2, 'center', self.toWP(c)]) return snaps - def snapToVertex(self, info, active=False): - """Return a vertex snap location.""" - p = Vector(info['x'], info['y'], info['z']) - if active: - if self.isEnabled("passive"): - return [p, 'endpoint', self.toWP(p)] - else: - return [] - elif self.isEnabled("passive"): - return [p, 'passive', p] - else: - return [] def snapToSpecials(self, obj, lastpoint=None, eline=None): """Return special snap locations, if any.""" snaps = [] - if self.isEnabled("special"): + if self.isEnabled("Special"): if (Draft.getType(obj) == "Wall"): # special snapping for wall: snap to its base shape if it is linear if obj.Base: if not obj.Base.Shape.Solids: - for v in obj.Base.Shape.Vertexes: snaps.append([v.Point, 'special', self.toWP(v.Point)]) elif (Draft.getType(obj) == "Structure"): @@ -1040,13 +1061,13 @@ class Snapper: def setCursor(self, mode=None): """Set or reset the cursor to the given mode or resets.""" if self.selectMode: - mw = FreeCADGui.getMainWindow() + mw = Gui.getMainWindow() for w in mw.findChild(QtGui.QMdiArea).findChildren(QtGui.QWidget): if w.metaObject().className() == "SIM::Coin3D::Quarter::QuarterWidget": w.unsetCursor() self.cursorMode = None elif not mode: - mw = FreeCADGui.getMainWindow() + mw = Gui.getMainWindow() for w in mw.findChild(QtGui.QMdiArea).findChildren(QtGui.QWidget): if w.metaObject().className() == "SIM::Coin3D::Quarter::QuarterWidget": w.unsetCursor() @@ -1064,7 +1085,7 @@ class Snapper: qp.drawPixmap(QtCore.QPoint(16, 8), tp) qp.end() cur = QtGui.QCursor(newicon, 8, 8) - mw = FreeCADGui.getMainWindow() + mw = Gui.getMainWindow() for w in mw.findChild(QtGui.QMdiArea).findChildren(QtGui.QWidget): if w.metaObject().className() == "SIM::Coin3D::Quarter::QuarterWidget": w.setCursor(cur) @@ -1121,7 +1142,7 @@ class Snapper: """Keep the current angle.""" if delta: self.mask = delta - elif isinstance(self.mask, FreeCAD.Vector): + elif isinstance(self.mask, App.Vector): self.mask = None elif self.trackLine: if self.trackLine.Visible: @@ -1138,15 +1159,15 @@ class Snapper: used as basepoint. """ # without the Draft module fully loaded, no axes system!" - if not hasattr(FreeCAD, "DraftWorkingPlane"): + if not hasattr(App, "DraftWorkingPlane"): return point - point = Vector(point) + point = App.Vector(point) # setup trackers if needed if not self.constrainLine: if self.snapStyle: - self.constrainLine = trackers.lineTracker(scolor=FreeCADGui.draftToolBar.getDefaultColor("snap")) + self.constrainLine = trackers.lineTracker(scolor=Gui.draftToolBar.getDefaultColor("snap")) else: self.constrainLine = trackers.lineTracker(dotted=True) @@ -1162,23 +1183,23 @@ class Snapper: if self.mask: self.affinity = self.mask if not self.affinity: - self.affinity = FreeCAD.DraftWorkingPlane.getClosestAxis(delta) - if isinstance(axis, FreeCAD.Vector): + self.affinity = App.DraftWorkingPlane.getClosestAxis(delta) + if isinstance(axis, App.Vector): self.constraintAxis = axis elif axis == "x": - self.constraintAxis = FreeCAD.DraftWorkingPlane.u + self.constraintAxis = App.DraftWorkingPlane.u elif axis == "y": - self.constraintAxis = FreeCAD.DraftWorkingPlane.v + self.constraintAxis = App.DraftWorkingPlane.v elif axis == "z": - self.constraintAxis = FreeCAD.DraftWorkingPlane.axis + self.constraintAxis = App.DraftWorkingPlane.axis else: if self.affinity == "x": - self.constraintAxis = FreeCAD.DraftWorkingPlane.u + self.constraintAxis = App.DraftWorkingPlane.u elif self.affinity == "y": - self.constraintAxis = FreeCAD.DraftWorkingPlane.v + self.constraintAxis = App.DraftWorkingPlane.v elif self.affinity == "z": - self.constraintAxis = FreeCAD.DraftWorkingPlane.axis - elif isinstance(self.affinity, FreeCAD.Vector): + self.constraintAxis = App.DraftWorkingPlane.axis + elif isinstance(self.affinity, App.Vector): self.constraintAxis = self.affinity else: self.constraintAxis = None @@ -1229,7 +1250,7 @@ class Snapper: if point: print "got a 3D point: ",point - FreeCADGui.Snapper.getPoint(callback=cb) + Gui.Snapper.getPoint(callback=cb) If the callback function accepts more than one argument, it will also receive the last snapped object. Finally, a qt widget @@ -1243,7 +1264,7 @@ class Snapper: self.pt = None self.lastSnappedObject = None self.holdPoints = [] - self.ui = FreeCADGui.draftToolBar + self.ui = Gui.draftToolBar self.view = Draft.get3DView() # remove any previous leftover callbacks @@ -1259,20 +1280,20 @@ class Snapper: mousepos = event.getPosition() ctrl = event.wasCtrlDown() shift = event.wasShiftDown() - self.pt = FreeCADGui.Snapper.snap(mousepos, lastpoint=last, - active=ctrl, constrain=shift) - if hasattr(FreeCAD, "DraftWorkingPlane"): + self.pt = Gui.Snapper.snap(mousepos, lastpoint=last, + active=ctrl, constrain=shift) + if hasattr(App, "DraftWorkingPlane"): self.ui.displayPoint(self.pt, last, - plane=FreeCAD.DraftWorkingPlane, - mask=FreeCADGui.Snapper.affinity) + plane = App.DraftWorkingPlane, + mask = App.Snapper.affinity) if movecallback: movecallback(self.pt, self.snapInfo) def getcoords(point, relative=False): """Get the global coordinates from a point.""" self.pt = point - if relative and last and hasattr(FreeCAD, "DraftWorkingPlane"): - v = FreeCAD.DraftWorkingPlane.getGlobalCoords(point) + if relative and last and hasattr(App, "DraftWorkingPlane"): + v = App.DraftWorkingPlane.getGlobalCoords(point) self.pt = last.add(v) accept() @@ -1289,8 +1310,8 @@ class Snapper: self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.callbackMove) self.callbackClick = None self.callbackMove = None - obj = FreeCADGui.Snapper.lastSnappedObject - FreeCADGui.Snapper.off() + obj = Gui.Snapper.lastSnappedObject + Gui.Snapper.off() self.ui.offUi() if callback: if len(inspect.getargspec(callback).args) > 1: @@ -1306,7 +1327,7 @@ class Snapper: self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.callbackMove) self.callbackClick = None self.callbackMove = None - FreeCADGui.Snapper.off() + Gui.Snapper.off() self.ui.offUi() if callback: if len(inspect.getargspec(callback).args) > 1: @@ -1332,121 +1353,90 @@ class Snapper: def makeSnapToolBar(self): """Build the Snap toolbar.""" - mw = FreeCADGui.getMainWindow() + mw = Gui.getMainWindow() self.toolbar = QtGui.QToolBar(mw) mw.addToolBar(QtCore.Qt.TopToolBarArea, self.toolbar) self.toolbar.setObjectName("Draft Snap") self.toolbar.setWindowTitle(QtCore.QCoreApplication.translate("Workbench", "Draft Snap")) - self.toolbarButtons = [] - # grid button - self.gridbutton = QtGui.QAction(mw) - self.gridbutton.setIcon(QtGui.QIcon.fromTheme("Draft_Grid", QtGui.QIcon(":/icons/Draft_Grid.svg"))) - self.gridbutton.setText(QtCore.QCoreApplication.translate("Draft_ToggleGrid", "Grid")) - self.gridbutton.setToolTip(QtCore.QCoreApplication.translate("Draft_ToggleGrid", "Toggles the Draft grid On/Off")) - self.gridbutton.setObjectName("GridButton") - self.gridbutton.setWhatsThis("Draft_ToggleGrid") - QtCore.QObject.connect(self.gridbutton, QtCore.SIGNAL("triggered()"), self.toggleGrid) - self.toolbar.addAction(self.gridbutton) + snap_gui_commands = get_draft_snap_commands() + self.init_draft_snap_buttons(snap_gui_commands, self.toolbar, "_Button") + self.restore_snap_buttons_state(self.toolbar,"_Button") - # master button - self.masterbutton = QtGui.QAction(mw) - self.masterbutton.setIcon(QtGui.QIcon.fromTheme("Snap_Lock", QtGui.QIcon(":/icons/Snap_Lock.svg"))) - self.masterbutton.setText(QtCore.QCoreApplication.translate("Draft_Snap_Lock", "Lock")) - self.masterbutton.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_Lock", "Toggle On/Off")) - self.masterbutton.setObjectName("SnapButtonMain") - self.masterbutton.setWhatsThis("Draft_ToggleSnap") - self.masterbutton.setCheckable(True) - self.masterbutton.setChecked(True) - QtCore.QObject.connect(self.masterbutton, - QtCore.SIGNAL("toggled(bool)"), self.toggle) - self.toolbar.addAction(self.masterbutton) - for c,i in self.cursors.items(): - if i: - b = QtGui.QAction(mw) - b.setIcon(QtGui.QIcon.fromTheme(i.replace(':/icons/', '').replace('.svg', ''), QtGui.QIcon(i))) - if c == "passive": - b.setText(QtCore.QCoreApplication.translate("Draft_Snap_Near", "Nearest")) - b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_Near", "Nearest")) - else: - b.setText(QtCore.QCoreApplication.translate("Draft_Snap_"+c.capitalize(), c.capitalize())) - b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_"+c.capitalize(), c.capitalize())) - b.setObjectName("SnapButton" + c) - b.setWhatsThis("Draft_" + c.capitalize()) - b.setCheckable(True) - b.setChecked(True) - self.toolbar.addAction(b) - self.toolbarButtons.append(b) - QtCore.QObject.connect(b, QtCore.SIGNAL("toggled(bool)"), - self.saveSnapModes) + if not Draft.getParam("showSnapBar",True): + self.toolbar.hide() - # adding non-snap button - for n in ("Dimensions", "WorkingPlane"): - b = QtGui.QAction(mw) - b.setIcon(QtGui.QIcon.fromTheme("Snap_" + n, QtGui.QIcon(":/icons/Snap_"+n+".svg"))) - b.setText(QtCore.QCoreApplication.translate("Draft_Snap_" + n,n)) - b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_" + n,n)) - b.setObjectName("SnapButton" + n) - b.setWhatsThis("Draft_" + n) + def init_draft_snap_buttons(self, commands, context, button_suffix): + """ + Init Draft Snap toolbar buttons. + + Parameters: + commands Snap command list, + use: get_draft_snap_commands(): + context The toolbar or action group the buttons have + to be added to + button_suffix The suffix that have to be applied to the command name + to define the button name + """ + for gc in commands: + # setup toolbar buttons + command = 'Gui.runCommand("' + gc + '")' + b = QtGui.QAction(context) + b.setIcon(QtGui.QIcon(':/icons/' + gc[6:] + '.svg')) + b.setText(QtCore.QCoreApplication.translate("Draft_Snap", "Snap " + gc[11:])) + b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap", "Snap " + gc[11:])) + b.setObjectName(gc + button_suffix) + b.setWhatsThis("Draft_"+gc[11:].capitalize()) b.setCheckable(True) b.setChecked(True) - self.toolbar.addAction(b) - QtCore.QObject.connect(b, QtCore.SIGNAL("toggled(bool)"), - self.saveSnapModes) - self.toolbarButtons.append(b) + context.addAction(b) + QtCore.QObject.connect(b, + QtCore.SIGNAL("triggered()"), + lambda f=Gui.doCommand, + arg=command:f(arg)) - # set status tip where needed - for b in self.toolbar.actions(): + for b in context.actions(): if len(b.statusTip()) == 0: b.setStatusTip(b.toolTip()) - # restoring states - t = Draft.getParam("snapModes", "111111111101111") - if t: - c = 0 - for b in [self.masterbutton] + self.toolbarButtons: - if len(t) > c: - state = bool(int(t[c])) - b.setChecked(state) + def restore_snap_buttons_state(self, toolbar, button_suffix): + """ + Restore toolbar button's checked state according to + "snapModes" saved in preferences + """ + # set status tip where needed + param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") + snap_modes = param.GetString("snapModes") + + for b in toolbar.actions(): + if len(b.statusTip()) == 0: + b.setStatusTip(b.toolTip()) + + # restore toolbar buttons state + if snap_modes: + for a in toolbar.findChildren(QtGui.QAction): + snap = a.objectName()[11:].replace(button_suffix,"") + if snap in Gui.Snapper.snaps: + i = Gui.Snapper.snaps.index(snap) + state = bool(int(snap_modes[i])) + a.setChecked(state) if state: - b.setToolTip(b.toolTip() + " (ON)") + a.setToolTip(a.toolTip()+" (ON)") else: - b.setToolTip(b.toolTip() + " (OFF)") - c += 1 - if not Draft.getParam("showSnapBar", True): - self.toolbar.hide() + a.setToolTip(a.toolTip()+" (OFF)") + + def get_snap_toolbar(self): + """retuns snap toolbar object""" + mw = Gui.getMainWindow() + if mw: + toolbar = mw.findChild(QtGui.QToolBar,"Draft Snap") + if toolbar: + return toolbar + return None def toggleGrid(self): - """Run Draft_ToggleGrid.""" - FreeCADGui.runCommand("Draft_ToggleGrid") - - def saveSnapModes(self): - """Save the snap modes for next sessions.""" - t = '' - for b in [self.masterbutton] + self.toolbarButtons: - t += str(int(b.isChecked())) - if b.isChecked(): - b.setToolTip(b.toolTip().replace("OFF", "ON")) - else: - b.setToolTip(b.toolTip().replace("ON", "OFF")) - Draft.setParam("snapModes", t) - - def toggle(self, checked=None): - """Toggle the snap mode.""" - if hasattr(self, "toolbarButtons"): - if checked is None: - self.masterbutton.toggle() - elif checked: - if hasattr(self, "savedButtonStates"): - for i in range(len(self.toolbarButtons)): - self.toolbarButtons[i].setEnabled(True) - self.toolbarButtons[i].setChecked(self.savedButtonStates[i]) - else: - self.savedButtonStates = [] - for i in range(len(self.toolbarButtons)): - self.savedButtonStates.append(self.toolbarButtons[i].isChecked()) - self.toolbarButtons[i].setEnabled(False) - self.saveSnapModes() + "toggle FreeCAD Draft Grid" + Gui.runCommand("Draft_ToggleGrid") def showradius(self): """Show the snap radius indicator.""" @@ -1456,32 +1446,66 @@ class Snapper: self.radiusTracker.update(self.radius) self.radiusTracker.on() - def isEnabled(self, but): - """Return true if the given button is turned on.""" - for b in self.toolbarButtons: - if str(b.objectName()) == "SnapButton" + but: - return (b.isEnabled() and b.isChecked()) - return False + def isEnabled(self, snap): + "Returns true if the given snap is on" + if "Lock" in self.active_snaps and snap in self.active_snaps: + return True + else: + return False + + def toggle_snap(self, snap, set_to = None): + "Sets the given snap on/off according to the given parameter" + if set_to: # set mode + if set_to is True: + if not snap in self.active_snaps: + self.active_snaps.append(snap) + status = True + elif set_to is False: + if snap in self.active_snaps: + self.active_snaps.remove(snap) + status = False + else: # toggle mode, default + if not snap in self.active_snaps: + self.active_snaps.append(snap) + status = True + elif snap in self.active_snaps: + self.active_snaps.remove(snap) + status = False + self.save_snap_state() + return status + + def save_snap_state(self): + """ + save snap state to user preferences to be restored in next session + """ + param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") + snap_modes = "" + for snap in self.snaps: + if snap in self.active_snaps: + snap_modes += "1" + else: + snap_modes += "0" + param.SetString("snapModes",snap_modes) def show(self): """Show the toolbar and the grid.""" if not hasattr(self, "toolbar"): self.makeSnapToolBar() - mw = FreeCADGui.getMainWindow() - bt = mw.findChild(QtGui.QToolBar, "Draft Snap") + bt = self.get_snap_toolbar() if not bt: + mw = FreeCADGui.getMainWindow() mw.addToolBar(self.toolbar) self.toolbar.setParent(mw) self.toolbar.show() self.toolbar.toggleViewAction().setVisible(True) - if FreeCADGui.ActiveDocument: + if Gui.ActiveDocument: self.setTrackers() - if not FreeCAD.ActiveDocument.Objects: - if FreeCADGui.ActiveDocument.ActiveView: - if FreeCADGui.ActiveDocument.ActiveView.getCameraType() == 'Orthographic': - c = FreeCADGui.ActiveDocument.ActiveView.getCameraNode() + if not App.ActiveDocument.Objects: + if Gui.ActiveDocument.ActiveView: + if Gui.ActiveDocument.ActiveView.getCameraType() == 'Orthographic': + c = Gui.ActiveDocument.ActiveView.getCameraNode() if c.orientation.getValue().getValue() == (0.0, 0.0, 0.0, 1.0): - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") + p = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") h = p.GetInt("defaultCameraHeight",0) if h: c.height.setValue(h) @@ -1523,7 +1547,7 @@ class Snapper: self.tracker = trackers.snapTracker() self.trackLine = trackers.lineTracker() if self.snapStyle: - c = FreeCADGui.draftToolBar.getDefaultColor("snap") + c = Gui.draftToolBar.getDefaultColor("snap") self.extLine = trackers.lineTracker(scolor=c) self.extLine2 = trackers.lineTracker(scolor=c) else: diff --git a/src/Mod/Draft/draftguitools/gui_snaps.py b/src/Mod/Draft/draftguitools/gui_snaps.py index 36ca85ffa0..75eb999335 100644 --- a/src/Mod/Draft/draftguitools/gui_snaps.py +++ b/src/Mod/Draft/draftguitools/gui_snaps.py @@ -28,12 +28,23 @@ # \brief Provide the Draft_Snap commands used by the snapping mechanism # in Draft. -from PySide.QtCore import QT_TRANSLATE_NOOP - +import draftguitools.gui_base as gui_base import FreeCADGui as Gui import draftguitools.gui_base as gui_base from draftutils.translate import _tr +from PySide.QtCore import QT_TRANSLATE_NOOP +from draftutils.translate import _tr + + +class Draft_Snap_Lock(gui_base.GuiCommandSimplest): + """GuiCommand for the Draft_Snap_Lock tool. + + Activate or deactivate all snap methods at once. + """ + + def __init__(self): + super().__init__(name=_tr("Main toggle snap")) class Draft_Snap_Lock(gui_base.GuiCommandSimplest): """GuiCommand for the Draft_Snap_Lock tool. @@ -63,6 +74,11 @@ class Draft_Snap_Lock(gui_base.GuiCommandSimplest): if hasattr(Gui.Snapper, "masterbutton"): Gui.Snapper.masterbutton.toggle() + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "masterbutton"): + Gui.Snapper.masterbutton.toggle() + +Gui.addCommand('Draft_Snap_Lock', Draft_Snap_Lock()) Gui.addCommand('Draft_Snap_Lock', Draft_Snap_Lock()) diff --git a/src/Mod/Draft/draftutils/init_tools.py b/src/Mod/Draft/draftutils/init_tools.py index 49b85cbcf6..7335ae2ccd 100644 --- a/src/Mod/Draft/draftutils/init_tools.py +++ b/src/Mod/Draft/draftutils/init_tools.py @@ -115,15 +115,15 @@ def get_draft_utility_commands(): def get_draft_snap_commands(): """Return the snapping commands list.""" - return ['Draft_Snap_Lock', 'Draft_Snap_Midpoint', - 'Draft_Snap_Perpendicular', - 'Draft_Snap_Grid', 'Draft_Snap_Intersection', - 'Draft_Snap_Parallel', - 'Draft_Snap_Endpoint', 'Draft_Snap_Angle', - 'Draft_Snap_Center', - 'Draft_Snap_Extension', 'Draft_Snap_Near', - 'Draft_Snap_Ortho', 'Draft_Snap_Special', - 'Draft_Snap_Dimensions', 'Draft_Snap_WorkingPlane'] + return ['Draft_Snap_Lock', + 'Draft_Snap_Endpoint', 'Draft_Snap_Midpoint', + 'Draft_Snap_Center', 'Draft_Snap_Angle', + 'Draft_Snap_Intersection', 'Draft_Snap_Perpendicular', + 'Draft_Snap_Extension', 'Draft_Snap_Parallel', + 'Draft_Snap_Special', 'Draft_Snap_Near', + 'Draft_Snap_Ortho', 'Draft_Snap_Grid', + 'Draft_Snap_WorkingPlane', 'Draft_Snap_Dimensions', + ] def init_draft_toolbars(workbench): From a23224da88d24c24a50896351cceeac3ac31c1f7 Mon Sep 17 00:00:00 2001 From: carlopav Date: Sat, 21 Mar 2020 01:25:32 +0100 Subject: [PATCH 10/33] [Draft] Added Draft Snap Statusbar . --- src/Mod/Draft/InitGui.py | 2 +- .../Draft/draftutils/init_draft_statusbar.py | 209 +++++++++++++++--- 2 files changed, 178 insertions(+), 33 deletions(-) diff --git a/src/Mod/Draft/InitGui.py b/src/Mod/Draft/InitGui.py index 286da11671..4572cc5b69 100644 --- a/src/Mod/Draft/InitGui.py +++ b/src/Mod/Draft/InitGui.py @@ -140,7 +140,7 @@ class DraftWorkbench(FreeCADGui.Workbench): if hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper.hide() import draftutils.init_draft_statusbar as dsb - dsb.hide_draft_statusbar() + dsb.hide_draft_statusbar() FreeCAD.Console.PrintLog("Draft workbench deactivated.\n") def ContextMenu(self, recipient): diff --git a/src/Mod/Draft/draftutils/init_draft_statusbar.py b/src/Mod/Draft/draftutils/init_draft_statusbar.py index 1d55d611fd..515ecda1b2 100644 --- a/src/Mod/Draft/draftutils/init_draft_statusbar.py +++ b/src/Mod/Draft/draftutils/init_draft_statusbar.py @@ -32,9 +32,9 @@ This module provide the code for the Draft Statusbar, activated by initGui import FreeCAD as App import FreeCADGui as Gui -from PySide import QtGui +from PySide import QtCore, QtGui from PySide.QtCore import QT_TRANSLATE_NOOP - +from draftutils.init_tools import get_draft_snap_commands #---------------------------------------------------------------------------- # SCALE WIDGET FUNCTIONS @@ -124,7 +124,8 @@ def label_to_scale(label): return scale except: err = QT_TRANSLATE_NOOP("draft", - "Unable to convert input into a scale factor") + "Unable to convert input into a " + "scale factor") App.Console.PrintWarning(err) return None @@ -137,25 +138,34 @@ def _set_scale(action): mw = Gui.getMainWindow() sb = mw.statusBar() - statuswidget = sb.findChild(QtGui.QToolBar,"draft_status_widget") + scale_widget = sb.findChild(QtGui.QToolBar,"draft_status_scale_widget") if action.text() == QT_TRANSLATE_NOOP("draft","custom"): dialog_text = QT_TRANSLATE_NOOP("draft", - "Set custom annotation scale in format x:x, x=x" + "Set custom annotation scale in " + "format x:x, x=x" ) - custom_scale = QtGui.QInputDialog.getText(None, "Set custom scale", dialog_text) + custom_scale = QtGui.QInputDialog.getText(None, "Set custom scale", + dialog_text) if custom_scale[1]: print(custom_scale[0]) scale = label_to_scale(custom_scale[0]) if scale is None: return param.SetFloat("DraftAnnotationScale", scale) cs = scale_to_label(scale) - statuswidget.scaleLabel.setText(cs) + scale_widget.scaleLabel.setText(cs) else: text_scale = action.text() - statuswidget.scaleLabel.setText(text_scale) + scale_widget.scaleLabel.setText(text_scale) scale = label_to_scale(text_scale) param.SetFloat("DraftAnnotationScale", scale) +#---------------------------------------------------------------------------- +# SNAP WIDGET FUNCTIONS +#---------------------------------------------------------------------------- + +def toggle_ortho(): + Gui.runCommand('Draft_Snap_Ortho',0) + #---------------------------------------------------------------------------- # MAIN DRAFT STATUSBAR FUNCTIONS #---------------------------------------------------------------------------- @@ -164,21 +174,131 @@ def init_draft_statusbar(sb): """ this function initializes draft statusbar """ + param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") + + # SNAP WIDGET - init ---------------------------------------------------- - statuswidget = QtGui.QToolBar() - statuswidget.setObjectName("draft_status_widget") + snap_widget = QtGui.QToolBar() + snap_widget.setObjectName("draft_snap_widget") + snap_widget.setIconSize(QtCore.QSize(16,16)) + + # GRID BUTTON - init + gridbutton = QtGui.QPushButton(snap_widget) + gridbutton.setIcon(QtGui.QIcon.fromTheme("Draft", + QtGui.QIcon(":/icons/" + "Draft_Grid.svg"))) + gridbutton.setToolTip(QT_TRANSLATE_NOOP("Draft", + "Toggles Grid On/Off")) + gridbutton.setObjectName("Grid_Statusbutton") + gridbutton.setWhatsThis("Draft_ToggleGrid") + gridbutton.setFlat(True) + QtCore.QObject.connect(gridbutton,QtCore.SIGNAL("clicked()"), + lambda f=Gui.doCommand, + arg='Gui.runCommand("Draft_ToggleGrid")':f(arg)) + snap_widget.addWidget(gridbutton) + + # SNAP BUTTON - init + snappref = param.GetString("snapModes","111111111101111")[0] + snapbutton = QtGui.QPushButton(snap_widget) + snapbutton.setIcon(QtGui.QIcon.fromTheme("Draft", + QtGui.QIcon(":/icons/" + "Snap_Lock.svg"))) + snapbutton.setObjectName("Snap_Statusbutton") + snapbutton.setWhatsThis("Draft_ToggleLockSnap") + snapbutton.setToolTip(QT_TRANSLATE_NOOP("Draft", + "Object snapping")) + snapbutton.setCheckable(True) + snapbutton.setChecked(bool(int(snappref))) + snapbutton.setFlat(True) + + snaps_menu = QtGui.QMenu(snapbutton) + snaps_menu.setObjectName("draft_statusbar_snap_toolbar") + + snap_gui_commands = get_draft_snap_commands() + if 'Draft_Snap_Ortho' in snap_gui_commands: + snap_gui_commands.remove('Draft_Snap_Ortho') + if 'Draft_Snap_WorkingPlane' in snap_gui_commands: + snap_gui_commands.remove('Draft_Snap_WorkingPlane') + if 'Draft_Snap_Dimensions' in snap_gui_commands: + snap_gui_commands.remove('Draft_Snap_Dimensions') + Gui.Snapper.init_draft_snap_buttons(snap_gui_commands,snaps_menu, "_Statusbutton") + Gui.Snapper.restore_snap_buttons_state(snaps_menu, "_Statusbutton") + + snapbutton.setMenu(snaps_menu) + snap_widget.addWidget(snapbutton) + + + # DIMENSION BUTTON - init + dimpref = param.GetString("snapModes","111111111101111")[13] + dimbutton = QtGui.QPushButton(snap_widget) + dimbutton.setIcon(QtGui.QIcon.fromTheme("Draft", + QtGui.QIcon(":/icons/" + "Snap_Dimensions.svg"))) + dimbutton.setToolTip(QT_TRANSLATE_NOOP("Draft", + "Toggles Visual Aid Dimensions On/Off")) + dimbutton.setObjectName("Draft_Snap_Dimensions_Statusbutton") + dimbutton.setWhatsThis("Draft_ToggleDimensions") + dimbutton.setFlat(True) + dimbutton.setCheckable(True) + dimbutton.setChecked(bool(int(dimpref))) + QtCore.QObject.connect(dimbutton,QtCore.SIGNAL("clicked()"), + lambda f=Gui.doCommand, + arg='Gui.runCommand("Draft_Snap_Dimensions")':f(arg)) + snap_widget.addWidget(dimbutton) + + # ORTHO BUTTON - init + ortopref = param.GetString("snapModes","111111111101111")[10] + orthobutton = QtGui.QPushButton(snap_widget) + orthobutton.setIcon(QtGui.QIcon.fromTheme("Draft", + QtGui.QIcon(":/icons/" + "Snap_Ortho.svg"))) + orthobutton.setObjectName("Draft_Snap_Ortho"+"_Statusbutton") + orthobutton.setWhatsThis("Draft_ToggleOrtho") + orthobutton.setToolTip(QT_TRANSLATE_NOOP("Draft", + "Toggles Ortho On/Off")) + orthobutton.setFlat(True) + orthobutton.setCheckable(True) + orthobutton.setChecked(bool(int(ortopref))) + QtCore.QObject.connect(orthobutton,QtCore.SIGNAL("clicked()"), + lambda f=Gui.doCommand, + arg='Gui.runCommand("Draft_Snap_Ortho")':f(arg)) + snap_widget.addWidget(orthobutton) + + # WORKINGPLANE BUTTON - init + wppref = param.GetString("snapModes","111111111101111")[14] + wpbutton = QtGui.QPushButton(snap_widget) + wpbutton.setIcon(QtGui.QIcon.fromTheme("Draft", + QtGui.QIcon(":/icons/" + "Snap_WorkingPlane.svg"))) + wpbutton.setObjectName("Draft_Snap_WorkingPlane_Statusbutton") + wpbutton.setWhatsThis("Draft_ToggleWorkingPlaneSnap") + wpbutton.setToolTip(QT_TRANSLATE_NOOP("Draft", + "Toggles Constrain to Working Plane On/Off")) + wpbutton.setFlat(True) + wpbutton.setCheckable(True) + wpbutton.setChecked(bool(int(wppref))) + QtCore.QObject.connect(wpbutton,QtCore.SIGNAL("clicked()"), + lambda f=Gui.doCommand, + arg='Gui.runCommand("Draft_Snap_WorkingPlane")':f(arg)) + snap_widget.addWidget(wpbutton) + + # add snap widget to the statusbar + sb.insertPermanentWidget(2, snap_widget) + snap_widget.show() + - # SCALE TOOL ------------------------------------------------------------- + # SCALE WIDGET ---------------------------------------------------------- + scale_widget = QtGui.QToolBar() + scale_widget.setObjectName("draft_status_scale_widget") # get scales list according to system units draft_scales = get_scales() # get draft annotation scale - param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") draft_annotation_scale = param.GetFloat("DraftAnnotationScale", 1.0) # initializes scale widget - statuswidget.draft_scales = draft_scales + scale_widget.draft_scales = draft_scales scaleLabel = QtGui.QPushButton("Scale") scaleLabel.setObjectName("ScaleLabel") scaleLabel.setFlat(True) @@ -194,12 +314,12 @@ def init_draft_statusbar(sb): scaleLabel.setText(scale_label) tooltip = "Set the scale used by draft annotation tools" scaleLabel.setToolTip(QT_TRANSLATE_NOOP("draft",tooltip)) - statuswidget.addWidget(scaleLabel) - statuswidget.scaleLabel = scaleLabel + scale_widget.addWidget(scaleLabel) + scale_widget.scaleLabel = scaleLabel - # ADD TOOLS TO STATUS BAR ------------------------------------------------ - sb.addPermanentWidget(statuswidget) - statuswidget.show() + # add scale widget to the statusbar + sb.insertPermanentWidget(3, scale_widget) + scale_widget.show() def show_draft_statusbar(): """ @@ -208,25 +328,50 @@ def show_draft_statusbar(): mw = Gui.getMainWindow() if mw: sb = mw.statusBar() - statuswidget = sb.findChild(QtGui.QToolBar,"draft_status_widget") - if statuswidget: - statuswidget.show() + + scale_widget = sb.findChild(QtGui.QToolBar, + "draft_status_scale_widget") + if scale_widget: + scale_widget.show() else: init_draft_statusbar(sb) + + snap_widget = sb.findChild(QtGui.QToolBar,"draft_snap_widget") + if snap_widget: + snap_widget.show() + else: + init_draft_statusbar(sb) + def hide_draft_statusbar(): """ hides draft statusbar if present """ mw = Gui.getMainWindow() - if mw: - sb = mw.statusBar() - statuswidget = sb.findChild(QtGui.QToolBar,"draft_status_widget") - if statuswidget: - statuswidget.hide() - else: - # when switching workbenches, the toolbar sometimes "jumps" - # out of the status bar to any other dock area... - statuswidget = mw.findChild(QtGui.QToolBar,"draft_status_widget") - if statuswidget: - statuswidget.hide() \ No newline at end of file + if not mw: + return + sb = mw.statusBar() + + # hide scale widget + scale_widget = sb.findChild(QtGui.QToolBar, + "draft_status_scale_widget") + if scale_widget: + scale_widget.hide() + else: + # when switching workbenches, the toolbar sometimes "jumps" + # out of the status bar to any other dock area... + scale_widget = mw.findChild(QtGui.QToolBar, + "draft_status_scale_widget") + if scale_widget: + scale_widget.hide() + + # hide snap widget + snap_widget = sb.findChild(QtGui.QToolBar,"draft_snap_widget") + if snap_widget: + snap_widget.hide() + else: + # when switching workbenches, the toolbar sometimes "jumps" + # out of the status bar to any other dock area... + snap_widget = mw.findChild(QtGui.QToolBar,"draft_snap_widget") + if snap_widget: + snap_widget.hide() \ No newline at end of file From 480216b25af85ff039d0a8e370dec6c45c655a13 Mon Sep 17 00:00:00 2001 From: carlopav Date: Sat, 21 Mar 2020 09:31:24 +0100 Subject: [PATCH 11/33] [Draft] rough implementation of new preference dialog for interface --- .../ui/preferences-draftinterface.ui | 1122 +++++++++++++++++ 1 file changed, 1122 insertions(+) create mode 100644 src/Mod/Draft/Resources/ui/preferences-draftinterface.ui diff --git a/src/Mod/Draft/Resources/ui/preferences-draftinterface.ui b/src/Mod/Draft/Resources/ui/preferences-draftinterface.ui new file mode 100644 index 0000000000..1d2e2522dc --- /dev/null +++ b/src/Mod/Draft/Resources/ui/preferences-draftinterface.ui @@ -0,0 +1,1122 @@ + + + Gui::Dialog::DlgSettingsDraft + + + + 0 + 0 + 510 + 397 + + + + General settings + + + + 6 + + + 9 + + + + + In-Command Shortcuts + + + + + + + + + + Relative + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + R + + + 1 + + + + + + false + + + inCommandShortcutRelative + + + Mod/Draft + + + + + + + + + + + + + Continue + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + T + + + 1 + + + + + + false + + + inCommandShortcutContinue + + + Mod/Draft + + + + + + + + + + + + + Close + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + O + + + 1 + + + + + + false + + + inCommandShortcutClose + + + Mod/Draft + + + + + + + + + + + + + + + Copy + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + P + + + 1 + + + + + + false + + + inCommandShortcutCopy + + + Mod/Draft + + + + + + + + + + + Subelement Mode + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + D + + + 1 + + + + + + false + + + inCommandShortcutSubelementMode + + + Mod/Draft + + + + + + + + + + + Fill + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + L + + + 1 + + + + + + false + + + inCommandShortcutFill + + + Mod/Draft + + + + + + + + + + + + + + + Exit + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + A + + + 1 + + + + + + false + + + inCommandShortcutExit + + + Mod/Draft + + + + + + + + + + + Select Edge + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + E + + + 1 + + + + + + false + + + inCommandShortcutSelectEdge + + + Mod/Draft + + + + + + + + + + + Add Hold + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + Q + + + 1 + + + + + + false + + + inCommandShortcutAddHold + + + Mod/Draft + + + + + + + + + + + + + + + Length + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + H + + + 1 + + + + + + false + + + inCommandShortcutLength + + + Mod/Draft + + + + + + + + + + + Wipe + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + W + + + 1 + + + + + + false + + + inCommandShortcutWipe + + + Mod/Draft + + + + + + + + + + + Set WP + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + U + + + 1 + + + + + + false + + + inCommandShortcutSetWP + + + Mod/Draft + + + + + + + + + + + + + + + Cycle Snap + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + ` + + + 1 + + + + + + false + + + inCommandShortcutCycleSnap + + + Mod/Draft + + + + + + + + + + + + + + + + + + + + + Snap + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + S + + + 1 + + + + + + false + + + inCommandShortcutSnap + + + Mod/Draft + + + + + + + + + + + Increase Radius + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + [ + + + 1 + + + + + + false + + + inCommandShortcutIncreaseRadius + + + Mod/Draft + + + + + + + + + + + Decrease Radius + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + ] + + + 1 + + + + + + false + + + inCommandShortcutDecreaseRadius + + + Mod/Draft + + + + + + + + + + + + + + + Restrict X + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + X + + + 1 + + + + + + false + + + inCommandShortcutRestrictX + + + Mod/Draft + + + + + + + + + + + Restrict Y + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + Y + + + 1 + + + + + + false + + + inCommandShortcutRestrictY + + + Mod/Draft + + + + + + + + + + + Restrict Z + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + Z + + + 1 + + + + + + false + + + RestrictZ + + + Mod/Draft + + + + + + + + + + + + + + + 0 + 0 + + + + Draft Statusbar + + + true + + + + + + + + + + Normally, after copying objects, the copies get selected. If this option is checked, the base objects will be selected instead. + + + Draft snap widget + + + selectBaseObjects + + + Mod/Draft + + + + + + + + + + + When this is checked, the Draft tools will create Part primitives instead of Draft objects, when available. + + + Annotation scale widget + + + UsePartPrimitives + + + Mod/Draft + + + + + + + + + + + + + + + + + Mouse navigation mode + + + true + + + focusOnLength + + + Mod/Draft + + + + + + + + + If this is checked, objects will appear as filled by default. Otherwise, they will appear as wireframe + + + 3D View size + + + true + + + fillmode + + + Mod/Draft + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + qPixmapFromMimeSource + + + Gui::PrefCheckBox + QCheckBox +
Gui/PrefWidgets.h
+
+ + Gui::PrefLineEdit + QLineEdit +
Gui/PrefWidgets.h
+
+
+ + +
From be75f4febc5fd2d145485350077538f12e15d69a Mon Sep 17 00:00:00 2001 From: carlopav Date: Wed, 25 Mar 2020 09:00:19 +0100 Subject: [PATCH 12/33] [Draft] Statusbar widgets, preferences to disable draft statusba . . . . --- src/Mod/Draft/InitGui.py | 1 + src/Mod/Draft/Resources/Draft.qrc | 1 + .../Draft/Resources/ui/preferences-draft.ui | 1205 ++--------- .../ui/preferences-draftinterface.ui | 1885 ++++++++--------- .../Draft/draftutils/init_draft_statusbar.py | 32 +- 5 files changed, 1018 insertions(+), 2106 deletions(-) diff --git a/src/Mod/Draft/InitGui.py b/src/Mod/Draft/InitGui.py index 4572cc5b69..66330e4c3a 100644 --- a/src/Mod/Draft/InitGui.py +++ b/src/Mod/Draft/InitGui.py @@ -116,6 +116,7 @@ class DraftWorkbench(FreeCADGui.Workbench): if hasattr(FreeCADGui, "draftToolBar"): if not hasattr(FreeCADGui.draftToolBar, "loadedPreferences"): FreeCADGui.addPreferencePage(":/ui/preferences-draft.ui", QT_TRANSLATE_NOOP("Draft", "Draft")) + FreeCADGui.addPreferencePage(":/ui/preferences-draftinterface.ui", QT_TRANSLATE_NOOP("Draft", "Draft")) FreeCADGui.addPreferencePage(":/ui/preferences-draftsnap.ui", QT_TRANSLATE_NOOP("Draft", "Draft")) FreeCADGui.addPreferencePage(":/ui/preferences-draftvisual.ui", QT_TRANSLATE_NOOP("Draft", "Draft")) FreeCADGui.addPreferencePage(":/ui/preferences-drafttexts.ui", QT_TRANSLATE_NOOP("Draft", "Draft")) diff --git a/src/Mod/Draft/Resources/Draft.qrc b/src/Mod/Draft/Resources/Draft.qrc index 5467200ade..c4096b88f3 100644 --- a/src/Mod/Draft/Resources/Draft.qrc +++ b/src/Mod/Draft/Resources/Draft.qrc @@ -153,6 +153,7 @@ translations/Draft_zh-CN.qm translations/Draft_zh-TW.qm ui/preferences-draft.ui + ui/preferences-draftinterface.ui ui/preferences-draftsnap.ui ui/preferences-drafttexts.ui ui/preferences-draftvisual.ui diff --git a/src/Mod/Draft/Resources/ui/preferences-draft.ui b/src/Mod/Draft/Resources/ui/preferences-draft.ui index 3a41695f63..005858b37e 100644 --- a/src/Mod/Draft/Resources/ui/preferences-draft.ui +++ b/src/Mod/Draft/Resources/ui/preferences-draft.ui @@ -6,8 +6,8 @@ 0 0 - 584 - 881 + 500 + 560 @@ -17,16 +17,7 @@ 6 - - 9 - - - 9 - - - 9 - - + 9 @@ -41,41 +32,6 @@ General Draft Settings - - - - If this is checked, copy mode will be kept across command, otherwise commands will always start in no-copy mode - - - Global copy mode - - - false - - - copymode - - - Mod/Draft - - - - - - - Normally, after copying objects, the copies get selected. If this option is checked, the base objects will be selected instead. - - - Select base objects after copying - - - selectBaseObjects - - - Mod/Draft - - - @@ -233,68 +189,149 @@ Values with differences below this value will be treated as same. This value wil - + - When this is checked, the Draft tools will create Part primitives instead of Draft objects, when available. + If this option is checked, the layers drop-down list will also show groups, allowing you to automatically add objects to groups too. - Use Part Primitives when available - - - UsePartPrimitives - - - Mod/Draft - - - - - - - If this is checked, objects will appear as filled by default. Otherwise, they will appear as wireframe - - - Fill objects with faces whenever possible + Show groups in layers list drop-down button - true + false - fillmode + AutogroupAddGroups Mod/Draft + + + + + + + Draft tools options + + + + 9 + - - - When drawing lines, set focus on Length instead of X coordinate + + + 0 - - focusOnLength - - - Mod/Draft - - - - - - - If this option is set, when creating Draft objects on top of an existing face of another object, the "Support" property of the Draft object will be set to the base object. This was the standard behaviour before FreeCAD 0.19 - - - Set the Support property when possible - - - useSupport - - - Mod/Draft - - + + + + When drawing lines, set focus on Length instead of X coordinate. +This allows to point the direction and type the distance. + + + Set focus on Length instead of X coordinate + + + focusOnLength + + + Mod/Draft + + + + + + + If this option is set, when creating Draft objects on top of an existing face of another object, the "Support" property of the Draft object will be set to the base object. This was the standard behaviour before FreeCAD 0.19 + + + Set the Support property when possible + + + useSupport + + + Mod/Draft + + + + + + + If this is checked, objects will appear as filled by default. +Otherwise, they will appear as wireframe + + + Fill objects with faces whenever possible + + + true + + + fillmode + + + Mod/Draft + + + + + + + Normally, after copying objects, the copies get selected. +If this option is checked, the base objects will be selected instead. + + + Select base objects after copying + + + selectBaseObjects + + + Mod/Draft + + + + + + + If this is checked, copy mode will be kept across command, +otherwise commands will always start in no-copy mode + + + Global copy mode + + + false + + + copymode + + + Mod/Draft + + + + + + + Force Draft Tools to create Part primitives instead of Draft objects. +Note that this is not fully supported, and many object will be not editable with Draft Modifiers. + + + Use Part Primitives when available + + + UsePartPrimitives + + + Mod/Draft + + + + @@ -320,25 +357,6 @@ Values with differences below this value will be treated as same. This value wil - - - - If this option is checked, the layers drop-down list will also show groups, allowing you to automatically add objects to groups too. - - - Show groups in layers list drop-down button - - - false - - - AutogroupAddGroups - - - Mod/Draft - - - @@ -408,7 +426,7 @@ Values with differences below this value will be treated as same. This value wil This is the default color for objects being drawn while in construction mode. - + 44 125 @@ -428,965 +446,6 @@ Values with differences below this value will be treated as same. This value wil - - - - In-Command Shortcuts - - - - - - - - - - Relative - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - R - - - 1 - - - - - - false - - - inCommandShortcutRelative - - - Mod/Draft - - - - - - - - - - - - - Continue - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - T - - - 1 - - - - - - false - - - inCommandShortcutContinue - - - Mod/Draft - - - - - - - - - - - - - Close - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - O - - - 1 - - - - - - false - - - inCommandShortcutClose - - - Mod/Draft - - - - - - - - - - - - - - - Copy - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - P - - - 1 - - - - - - false - - - inCommandShortcutCopy - - - Mod/Draft - - - - - - - - - - - Subelement Mode - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - D - - - 1 - - - - - - false - - - inCommandShortcutSubelementMode - - - Mod/Draft - - - - - - - - - - - Fill - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - L - - - 1 - - - - - - false - - - inCommandShortcutFill - - - Mod/Draft - - - - - - - - - - - - - - - Exit - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - A - - - 1 - - - - - - false - - - inCommandShortcutExit - - - Mod/Draft - - - - - - - - - - - Select Edge - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - E - - - 1 - - - - - - false - - - inCommandShortcutSelectEdge - - - Mod/Draft - - - - - - - - - - - Add Hold - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - Q - - - 1 - - - - - - false - - - inCommandShortcutAddHold - - - Mod/Draft - - - - - - - - - - - - - - - Length - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - H - - - 1 - - - - - - false - - - inCommandShortcutLength - - - Mod/Draft - - - - - - - - - - - Wipe - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - W - - - 1 - - - - - - false - - - inCommandShortcutWipe - - - Mod/Draft - - - - - - - - - - - Set WP - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - U - - - 1 - - - - - - false - - - inCommandShortcutSetWP - - - Mod/Draft - - - - - - - - - - - - - - - Cycle Snap - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - ` - - - 1 - - - - - - false - - - inCommandShortcutCycleSnap - - - Mod/Draft - - - - - - - - - - - - - - - - - - - - - Snap - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - S - - - 1 - - - - - - false - - - inCommandShortcutSnap - - - Mod/Draft - - - - - - - - - - - Increase Radius - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - [ - - - 1 - - - - - - false - - - inCommandShortcutIncreaseRadius - - - Mod/Draft - - - - - - - - - - - Decrease Radius - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - ] - - - 1 - - - - - - false - - - inCommandShortcutDecreaseRadius - - - Mod/Draft - - - - - - - - - - - - - - - Restrict X - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - X - - - 1 - - - - - - false - - - inCommandShortcutRestrictX - - - Mod/Draft - - - - - - - - - - - Restrict Y - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - Y - - - 1 - - - - - - false - - - inCommandShortcutRestrictY - - - Mod/Draft - - - - - - - - - - - Restrict Z - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - Z - - - 1 - - - - - - false - - - RestrictZ - - - Mod/Draft - - - - - - - - - - diff --git a/src/Mod/Draft/Resources/ui/preferences-draftinterface.ui b/src/Mod/Draft/Resources/ui/preferences-draftinterface.ui index 1d2e2522dc..7f231e4ce7 100644 --- a/src/Mod/Draft/Resources/ui/preferences-draftinterface.ui +++ b/src/Mod/Draft/Resources/ui/preferences-draftinterface.ui @@ -6,12 +6,12 @@ 0 0 - 510 - 397 + 456 + 338 - General settings + User interface settings @@ -25,954 +25,847 @@ In-Command Shortcuts - + - - - - - - - Relative - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - R - - - 1 - - - - - - false - - - inCommandShortcutRelative - - - Mod/Draft - - - - + + + 0 + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + ` + + + 1 + + + + + + false + + + inCommandShortcutCycleSnap + + + Mod/Draft + + - - - - - - - - Continue - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - T - - - 1 - - - - - - false - - - inCommandShortcutContinue - - - Mod/Draft - - - - - - + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + S + + + 1 + + + + + + false + + + inCommandShortcutSnap + + + Mod/Draft + + - - - - - - Close - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - O - - - 1 - - - - - - false - - - inCommandShortcutClose - - - Mod/Draft - - - - + + + + Close + + - - - - - - - - - - Copy - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - P - - - 1 - - - - - - false - - - inCommandShortcutCopy - - - Mod/Draft - - - - + + + + Relative + + - - - - - - Subelement Mode - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - D - - - 1 - - - - - - false - - - inCommandShortcutSubelementMode - - - Mod/Draft - - - - + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + R + + + 1 + + + + + + false + + + inCommandShortcutRelative + + + Mod/Draft + + - - - - - - Fill - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - L - - - 1 - - - - - - false - - - inCommandShortcutFill - - - Mod/Draft - - - - + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + O + + + 1 + + + + + + false + + + inCommandShortcutClose + + + Mod/Draft + + - - - - - - - - - - Exit - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - A - - - 1 - - - - - - false - - - inCommandShortcutExit - - - Mod/Draft - - - - + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + T + + + 1 + + + + + + false + + + inCommandShortcutContinue + + + Mod/Draft + + - - - - - - Select Edge - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - E - - - 1 - - - - - - false - - - inCommandShortcutSelectEdge - - - Mod/Draft - - - - + + + + Continue + + - - - - - - Add Hold - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - Q - - - 1 - - - - - - false - - - inCommandShortcutAddHold - - - Mod/Draft - - - - + + + + Copy + + - - - - - - - - - - Length - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - H - - - 1 - - - - - - false - - - inCommandShortcutLength - - - Mod/Draft - - - - + + + + Increase Radius + + - - - - - - Wipe - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - W - - - 1 - - - - - - false - - - inCommandShortcutWipe - - - Mod/Draft - - - - + + + + Cycle Snap + + - - - - - - Set WP - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - U - - - 1 - - - - - - false - - - inCommandShortcutSetWP - - - Mod/Draft - - - - + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + [ + + + 1 + + + + + + false + + + inCommandShortcutIncreaseRadius + + + Mod/Draft + + - - - - - - - - - - Cycle Snap - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - ` - - - 1 - - - - - - false - - - inCommandShortcutCycleSnap - - - Mod/Draft - - - - + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + ] + + + 1 + + + + + + false + + + inCommandShortcutDecreaseRadius + + + Mod/Draft + + - - + + + + Snap + + - - + + + + Decrease Radius + + - - - - - - - - - - Snap - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - S - - - 1 - - - - - - false - - - inCommandShortcutSnap - - - Mod/Draft - - - - + + + + Length + + - - - - - - Increase Radius - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - [ - - - 1 - - - - - - false - - - inCommandShortcutIncreaseRadius - - - Mod/Draft - - - - + + + + Wipe + + - - - - - - Decrease Radius - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - ] - - - 1 - - - - - - false - - - inCommandShortcutDecreaseRadius - - - Mod/Draft - - - - + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + D + + + 1 + + + + + + false + + + inCommandShortcutSubelementMode + + + Mod/Draft + + - - - - - - - - - - Restrict X - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - X - - - 1 - - - - - - false - - - inCommandShortcutRestrictX - - - Mod/Draft - - - - + + + + Add Hold + + - - - - - - Restrict Y - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - Y - - - 1 - - - - - - false - - - inCommandShortcutRestrictY - - - Mod/Draft - - - - + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + L + + + 1 + + + + + + false + + + inCommandShortcutFill + + + Mod/Draft + + - - - - - - Restrict Z - - - - - - - true - - - - 0 - 0 - - - - - 25 - 16777215 - - - - Z - - - 1 - - - - - - false - - - RestrictZ - - - Mod/Draft - - - - + + + + Exit + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + P + + + 1 + + + + + + false + + + inCommandShortcutCopy + + + Mod/Draft + + + + + + + Fill + + + + + + + Subelement Mode + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + E + + + 1 + + + + + + false + + + inCommandShortcutSelectEdge + + + Mod/Draft + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + H + + + 1 + + + + + + false + + + inCommandShortcutLength + + + Mod/Draft + + + + + + + Select Edge + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + W + + + 1 + + + + + + false + + + inCommandShortcutWipe + + + Mod/Draft + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + A + + + 1 + + + + + + false + + + inCommandShortcutExit + + + Mod/Draft + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + Q + + + 1 + + + + + + false + + + inCommandShortcutAddHold + + + Mod/Draft + + + + + + + Set WP + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + U + + + 1 + + + + + + false + + + inCommandShortcutSetWP + + + Mod/Draft + + + + + + + Restrict X + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + X + + + 1 + + + + + + false + + + inCommandShortcutRestrictX + + + Mod/Draft + + + + + + + Restrict Y + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + Y + + + 1 + + + + + + false + + + inCommandShortcutRestrictY + + + Mod/Draft + + + + + + + Restrict Z + + + + + + + true + + + + 0 + 0 + + + + + 25 + 16777215 + + + + Z + + + 1 + + + + + + false + + + RestrictZ + + + Mod/Draft + + @@ -987,101 +880,61 @@ 0 + + Enable draft statusbar customization + Draft Statusbar true + + DisplayStatusbar + + + Mod/Draft + - - - - - - - Normally, after copying objects, the copies get selected. If this option is checked, the base objects will be selected instead. - - - Draft snap widget - - - selectBaseObjects - - - Mod/Draft - - - - + + + 0 + + + + + Enable snap statusbar widget + + + Draft snap widget + + + true + + + DisplayStatusbarSnapWidget + + + Mod/Draft + + - - - - - - When this is checked, the Draft tools will create Part primitives instead of Draft objects, when available. - - - Annotation scale widget - - - UsePartPrimitives - - - Mod/Draft - - - - - - - - - - - - - - - - - Mouse navigation mode - - - true - - - focusOnLength - - - Mod/Draft - - - - - - - - - If this is checked, objects will appear as filled by default. Otherwise, they will appear as wireframe - - - 3D View size - - - true - - - fillmode - - - Mod/Draft - - - - + + + + Enable draft statusbar annotation scale widget + + + Annotation scale widget + + + DisplayStatusbarScaleWidget + + + Mod/Draft + + diff --git a/src/Mod/Draft/draftutils/init_draft_statusbar.py b/src/Mod/Draft/draftutils/init_draft_statusbar.py index 515ecda1b2..3373ac713f 100644 --- a/src/Mod/Draft/draftutils/init_draft_statusbar.py +++ b/src/Mod/Draft/draftutils/init_draft_statusbar.py @@ -1,11 +1,3 @@ -"""Draft Statusbar commands. - -This module provide the code for the Draft Statusbar, activated by initGui -""" -## @package init_draft_statusbar -# \ingroup DRAFT -# \brief This module provides the code for the Draft Statusbar. - # *************************************************************************** # * * # * Copyright (c) 2020 Carlo Pavan * @@ -29,6 +21,13 @@ This module provide the code for the Draft Statusbar, activated by initGui # * USA * # * * # *************************************************************************** +"""Draft Statusbar commands. + +This module provide the code for the Draft Statusbar, activated by initGui +""" +## @package init_draft_statusbar +# \ingroup DRAFT +# \brief This module provides the code for the Draft Statusbar. import FreeCAD as App import FreeCADGui as Gui @@ -159,13 +158,6 @@ def _set_scale(action): scale = label_to_scale(text_scale) param.SetFloat("DraftAnnotationScale", scale) -#---------------------------------------------------------------------------- -# SNAP WIDGET FUNCTIONS -#---------------------------------------------------------------------------- - -def toggle_ortho(): - Gui.runCommand('Draft_Snap_Ortho',0) - #---------------------------------------------------------------------------- # MAIN DRAFT STATUSBAR FUNCTIONS #---------------------------------------------------------------------------- @@ -325,6 +317,12 @@ def show_draft_statusbar(): """ shows draft statusbar if present or initializes it """ + params = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") + display_statusbar = params.GetBool("DisplayStatusbar", True) + + if not display_statusbar: + return + mw = Gui.getMainWindow() if mw: sb = mw.statusBar() @@ -333,13 +331,13 @@ def show_draft_statusbar(): "draft_status_scale_widget") if scale_widget: scale_widget.show() - else: + elif params.GetBool("DisplayStatusbarScaleWidget", True): init_draft_statusbar(sb) snap_widget = sb.findChild(QtGui.QToolBar,"draft_snap_widget") if snap_widget: snap_widget.show() - else: + elif params.GetBool("DisplayStatusbarSnapWidget", True): init_draft_statusbar(sb) From 64db7219984ba9d0d27e7f5734006dc9e7984926 Mon Sep 17 00:00:00 2001 From: carlopav Date: Sat, 18 Apr 2020 11:39:48 +0200 Subject: [PATCH 13/33] [Draft] Snap improvement and statusbar cleanup after rebase [Draft] Attempt to make base classes compatible with super on py2 [Draft] Refactored imports of snapper and snaps gui tools . . --- src/Mod/Draft/draftguitools/gui_base.py | 97 +------- src/Mod/Draft/draftguitools/gui_snapper.py | 94 +++++++- src/Mod/Draft/draftguitools/gui_snaps.py | 254 ++++++++++++--------- 3 files changed, 236 insertions(+), 209 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_base.py b/src/Mod/Draft/draftguitools/gui_base.py index d3f072b7d3..86cc109d88 100644 --- a/src/Mod/Draft/draftguitools/gui_base.py +++ b/src/Mod/Draft/draftguitools/gui_base.py @@ -33,7 +33,7 @@ import draftutils.todo as todo from draftutils.messages import _msg, _log -class GuiCommandSimplest: +class GuiCommandSimplest(object): """Simplest base class for GuiCommands. This class only sets up the command name and the document object @@ -126,100 +126,7 @@ class GuiCommandNeedsSelection(GuiCommandSimplest): return False -class GuiCommandSimplest: - """Simplest base class for GuiCommands. - - This class only sets up the command name and the document object - to use for the command. - When it is executed, it logs the command name to the log file, - and prints the command name to the console. - - It implements the `IsActive` method, which must return `True` - when the command should be available. - It should return `True` when there is an active document, - otherwise the command (button or menu) should be disabled. - - This class is meant to be inherited by other GuiCommand classes - to quickly log the command name, and set the correct document object. - - Parameter - --------- - name: str, optional - It defaults to `'None'`. - The name of the action that is being run, - for example, `'Heal'`, `'Flip dimensions'`, - `'Line'`, `'Circle'`, etc. - - doc: App::Document, optional - It defaults to the value of `App.activeDocument()`. - The document object itself, which indicates where the actions - of the command will be executed. - - Attributes - ---------- - command_name: str - This is the command name, which is assigned by `name`. - - doc: App::Document - This is the document object itself, which is assigned by `doc`. - - This attribute should be used by functions to make sure - that the operations are performed in the correct document - and not in other documents. - To set the active document we can use - - >>> App.setActiveDocument(self.doc.Name) - """ - - def __init__(self, name="None", doc=App.activeDocument()): - self.command_name = name - self.doc = doc - - def IsActive(self): - """Return True when this command should be available. - - It is `True` when there is a document. - """ - if App.activeDocument(): - return True - else: - return False - - def Activated(self): - """Execute when the command is called. - - Log the command name to the log file and console. - Also update the `doc` attribute. - """ - self.doc = App.activeDocument() - _log("Document: {}".format(self.doc.Label)) - _log("GuiCommand: {}".format(self.command_name)) - _msg("{}".format(16*"-")) - _msg("GuiCommand: {}".format(self.command_name)) - - -class GuiCommandNeedsSelection(GuiCommandSimplest): - """Base class for GuiCommands that need a selection to be available. - - It re-implements the `IsActive` method to return `True` - when there is both an active document and an active selection. - - It inherits `GuiCommandSimplest` to set up the document - and other behavior. See this class for more information. - """ - - def IsActive(self): - """Return True when this command should be available. - - It is `True` when there is a selection. - """ - if App.activeDocument() and Gui.Selection.getSelection(): - return True - else: - return False - - -class GuiCommandBase: +class GuiCommandBase(object): """Generic class that is the basis of all Gui commands. This class should eventually replace `DraftTools.DraftTool`, diff --git a/src/Mod/Draft/draftguitools/gui_snapper.py b/src/Mod/Draft/draftguitools/gui_snapper.py index 542acf76d8..7e680899d4 100644 --- a/src/Mod/Draft/draftguitools/gui_snapper.py +++ b/src/Mod/Draft/draftguitools/gui_snapper.py @@ -32,26 +32,33 @@ defined by `gui_trackers.gridTracker`. # This module provides tools to handle point snapping and # everything that goes with it (toolbar buttons, cursor icons, etc.). +import FreeCAD as App +import FreeCADGui as Gui + +from pivy import coin +from PySide import QtCore, QtGui + import collections as coll import inspect import itertools import math -from pivy import coin -from PySide import QtCore, QtGui -import FreeCAD as App -import FreeCADGui as Gui import Draft import DraftVecUtils -from FreeCAD import Vector +import DraftGeomUtils + +import Part + import draftguitools.gui_trackers as trackers from draftutils.init_tools import get_draft_snap_commands from draftutils.messages import _msg, _wrn + __title__ = "FreeCAD Draft Snap tools" __author__ = "Yorik van Havre" __url__ = "https://www.freecadweb.org" + class Snapper: """Classes to manage snapping in Draft and Arch. @@ -75,6 +82,7 @@ class Snapper: """ def __init__(self): + self.activeview = None self.lastObj = [None, None] self.maxEdges = 0 @@ -180,6 +188,7 @@ class Snapper: ('intersection', ':/icons/Snap_Intersection.svg'), ('special', ':/icons/Snap_Special.svg')]) + def init_active_snaps(self): """ set self.active_snaps according to user prefs @@ -193,6 +202,7 @@ class Snapper: self.active_snaps.append(self.snaps[i]) i += 1 + def cstr(self, lastpoint, constrain, point): """Return constraints if needed.""" if constrain or self.mask: @@ -204,6 +214,7 @@ class Snapper: self.radiusTracker.update(fpt) return fpt + def snap(self, screenpos, lastpoint=None, active=True, constrain=False, noTracker=False): @@ -225,8 +236,6 @@ class Snapper: self.running = True - global Part, DraftGeomUtils - import Part, DraftGeomUtils self.spoint = None if not hasattr(self, "toolbar"): @@ -332,10 +341,12 @@ class Snapper: self.running = False return fp + def cycleSnapObject(self): """Increse the index of the snap object by one.""" self.snapObjectIndex = self.snapObjectIndex + 1 + def snapToObject(self, lastpoint, active, constrain, eline, point, oldActive): """Snap to an object.""" @@ -513,6 +524,7 @@ class Snapper: self.running = False return self.spoint + def toWP(self, point): """Project the given point on the working plane, if needed.""" if self.isEnabled("WorkingPlane"): @@ -520,6 +532,7 @@ class Snapper: return App.DraftWorkingPlane.projectPoint(point) return point + def getApparentPoint(self, x, y): """Return a 3D point, projected on the current working plane.""" view = Draft.get3DView() @@ -535,6 +548,7 @@ class Snapper: return App.DraftWorkingPlane.projectPoint(pt, dv) return pt + def snapToDim(self, obj): snaps = [] if obj.ViewObject: @@ -543,6 +557,7 @@ class Snapper: snaps.append([obj.ViewObject.Proxy.p3, 'endpoint', self.toWP(obj.ViewObject.Proxy.p3)]) return snaps + def snapToExtensions(self, point, last, constrain, eline): """Return a point snapped to extension or parallel line. @@ -652,6 +667,7 @@ class Snapper: return np,de return point,eline + def snapToCrossExtensions(self, point): """Snap to the intersection of the last 2 extension lines.""" if self.isEnabled('Extension'): @@ -682,6 +698,7 @@ class Snapper: return p return None + def snapToPolar(self,point,last): """Snap to polar lines from the given point.""" if self.isEnabled('Ortho') and (not self.mask): @@ -721,6 +738,7 @@ class Snapper: return np,de return point, None + def snapToGrid(self, point): """Return a grid snap point if available.""" if self.grid: @@ -738,6 +756,7 @@ class Snapper: return np return point + def snapToEndpoints(self, shape): """Return a list of endpoints snap locations.""" snaps = [] @@ -756,6 +775,7 @@ class Snapper: snaps.append([v, 'endpoint', self.toWP(v)]) return snaps + def snapToMidpoint(self, shape): """Return a list of midpoints snap locations.""" snaps = [] @@ -766,6 +786,7 @@ class Snapper: snaps.append([mp, 'midpoint', self.toWP(mp)]) return snaps + def snapToPerpendicular(self, shape, last): """Return a list of perpendicular snap locations.""" snaps = [] @@ -789,6 +810,7 @@ class Snapper: snaps.append([np, 'perpendicular', self.toWP(np)]) return snaps + def snapToOrtho(self, shape, last, constrain): """Return a list of ortho snap locations.""" snaps = [] @@ -806,6 +828,7 @@ class Snapper: snaps.append([p, 'ortho', self.toWP(p)]) return snaps + def snapToExtOrtho(self, last, constrain, eline): """Return an ortho X extension snap location.""" if self.isEnabled("Extension") and self.isEnabled("Ortho"): @@ -827,6 +850,7 @@ class Snapper: return None return None + def snapToHold(self, point): """Return a snap location that is orthogonal to hold points. @@ -877,6 +901,7 @@ class Snapper: return [p, 'extension', fp] return None + def snapToExtPerpendicular(self, last): """Return a perpendicular X extension snap location.""" if self.isEnabled("Extension") and self.isEnabled("Perpendicular"): @@ -887,6 +912,7 @@ class Snapper: return [np, 'perpendicular', np] return None + def snapToElines(self, e1, e2): """Return a snap at the infinite intersection of the given edges.""" snaps = [] @@ -899,6 +925,7 @@ class Snapper: snaps.append([p, 'intersection', self.toWP(p)]) return snaps + def snapToAngles(self, shape): """Return a list of angle snap locations.""" snaps = [] @@ -916,6 +943,7 @@ class Snapper: snaps.append([cur, 'angle', self.toWP(cur)]) return snaps + def snapToCenter(self, shape): """Return a list of center snap locations.""" snaps = [] @@ -929,14 +957,15 @@ class Snapper: 195, 217.5, 232.5, 255, 285, 307.5, 322.5, 345): ang = math.radians(i) - cur = Vector(math.sin(ang) * rad + pos.x, - math.cos(ang) * rad + pos.y, - pos.z) + cur = App.Vector(math.sin(ang) * rad + pos.x, + math.cos(ang) * rad + pos.y, + pos.z) snaps.append([cur, 'center', c]) else: snaps.append([c, 'center', c]) return snaps + def snapToFace(self, shape): """Return a face center snap location.""" snaps = [] @@ -946,6 +975,7 @@ class Snapper: snaps.append([pos, 'center', c]) return snaps + def snapToIntersection(self, shape): """Return a list of intersection snap locations.""" snaps = [] @@ -978,6 +1008,7 @@ class Snapper: # when trying to read their types return snaps + def snapToPolygon(self, obj): """Return a list of polygon center snap locations.""" snaps = [] @@ -993,6 +1024,19 @@ class Snapper: return snaps + def snapToVertex(self,info,active=False): + p = App.Vector(info['x'],info['y'],info['z']) + if active: + if self.isEnabled("Near"): + return [p,'endpoint',self.toWP(p)] + else: + return [] + elif self.isEnabled("Near"): + return [p,'passive',p] + else: + return [] + + def snapToSpecials(self, obj, lastpoint=None, eline=None): """Return special snap locations, if any.""" snaps = [] @@ -1028,6 +1072,7 @@ class Snapper: return snaps + def getScreenDist(self, dist, cursor): """Return a distance in 3D space from a screen pixels distance.""" view = Draft.get3DView() @@ -1035,6 +1080,7 @@ class Snapper: p2 = view.getPoint((cursor[0] + dist, cursor[1])) return (p2.sub(p1)).Length + def getPerpendicular(self, edge, pt): """Return a point on an edge, perpendicular to the given point.""" dv = pt.sub(edge.Vertexes[0].Point) @@ -1042,6 +1088,7 @@ class Snapper: np = (edge.Vertexes[0].Point).add(nv) return np + def setArchDims(self, p1, p2): """Show arc dimensions between 2 points.""" if self.isEnabled("Dimensions"): @@ -1058,6 +1105,7 @@ class Snapper: if self.dim2.Distance: self.dim2.on() + def setCursor(self, mode=None): """Set or reset the cursor to the given mode or resets.""" if self.selectMode: @@ -1091,11 +1139,13 @@ class Snapper: w.setCursor(cur) self.cursorMode = mode + def restack(self): """Lower the grid tracker so it doesn't obscure other objects.""" if self.grid: self.grid.lowerTracker() + def off(self, hideSnapBar=False): """Finish snapping.""" if self.tracker: @@ -1129,6 +1179,7 @@ class Snapper: self.running = False self.holdPoints = [] + def setSelectMode(self, mode): """Set the snapper into select mode (hides snapping temporarily).""" self.selectMode = mode @@ -1138,6 +1189,7 @@ class Snapper: if self.trackLine: self.trackLine.off() + def setAngle(self, delta=None): """Keep the current angle.""" if delta: @@ -1148,6 +1200,7 @@ class Snapper: if self.trackLine.Visible: self.mask = self.trackLine.p2().sub(self.trackLine.p1()) + def constrain(self, point, basepoint=None, axis=None): """Return a constrained point. @@ -1222,6 +1275,7 @@ class Snapper: return npoint + def unconstrain(self): """Unset the basepoint and the constrain line.""" self.basepoint = None @@ -1229,6 +1283,7 @@ class Snapper: if self.constrainLine: self.constrainLine.off() + def getPoint(self, last=None, callback=None, movecallback=None, extradlg=None, title=None, mode="point"): """Get a 3D point from the screen. @@ -1289,6 +1344,7 @@ class Snapper: if movecallback: movecallback(self.pt, self.snapInfo) + def getcoords(point, relative=False): """Get the global coordinates from a point.""" self.pt = point @@ -1297,12 +1353,14 @@ class Snapper: self.pt = last.add(v) accept() + def click(event_cb): event = event_cb.getEvent() if event.getButton() == 1: if event.getState() == coin.SoMouseButtonEvent.DOWN: accept() + def accept(): if self.callbackClick: self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.callbackClick) @@ -1320,6 +1378,7 @@ class Snapper: callback(self.pt) self.pt = None + def cancel(): if self.callbackClick: self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.callbackClick) @@ -1351,6 +1410,7 @@ class Snapper: self.callbackClick = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),click) self.callbackMove = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(),move) + def makeSnapToolBar(self): """Build the Snap toolbar.""" mw = Gui.getMainWindow() @@ -1366,6 +1426,7 @@ class Snapper: if not Draft.getParam("showSnapBar",True): self.toolbar.hide() + def init_draft_snap_buttons(self, commands, context, button_suffix): """ Init Draft Snap toolbar buttons. @@ -1399,6 +1460,7 @@ class Snapper: if len(b.statusTip()) == 0: b.setStatusTip(b.toolTip()) + def restore_snap_buttons_state(self, toolbar, button_suffix): """ Restore toolbar button's checked state according to @@ -1425,6 +1487,7 @@ class Snapper: else: a.setToolTip(a.toolTip()+" (OFF)") + def get_snap_toolbar(self): """retuns snap toolbar object""" mw = Gui.getMainWindow() @@ -1434,10 +1497,12 @@ class Snapper: return toolbar return None + def toggleGrid(self): "toggle FreeCAD Draft Grid" Gui.runCommand("Draft_ToggleGrid") + def showradius(self): """Show the snap radius indicator.""" self.radius = self.getScreenDist(Draft.getParam("snapRange", 8), @@ -1446,6 +1511,7 @@ class Snapper: self.radiusTracker.update(self.radius) self.radiusTracker.on() + def isEnabled(self, snap): "Returns true if the given snap is on" if "Lock" in self.active_snaps and snap in self.active_snaps: @@ -1453,6 +1519,7 @@ class Snapper: else: return False + def toggle_snap(self, snap, set_to = None): "Sets the given snap on/off according to the given parameter" if set_to: # set mode @@ -1474,6 +1541,7 @@ class Snapper: self.save_snap_state() return status + def save_snap_state(self): """ save snap state to user preferences to be restored in next session @@ -1487,6 +1555,7 @@ class Snapper: snap_modes += "0" param.SetString("snapModes",snap_modes) + def show(self): """Show the toolbar and the grid.""" if not hasattr(self, "toolbar"): @@ -1510,12 +1579,14 @@ class Snapper: if h: c.height.setValue(h) + def hide(self): """Hide the toolbar.""" if hasattr(self, "toolbar"): self.toolbar.hide() self.toolbar.toggleViewAction().setVisible(True) + def setGrid(self): """Set the grid, if visible.""" self.setTrackers() @@ -1523,6 +1594,7 @@ class Snapper: if self.grid.Visible: self.grid.set() + def setTrackers(self): """Set the trackers.""" v = Draft.get3DView() @@ -1570,9 +1642,11 @@ class Snapper: self.trackers[8].append(self.extLine2) self.trackers[9].append(self.holdTracker) self.activeview = v + if self.grid and (not self.forceGridOff): self.grid.set() + def addHoldPoint(self): """Add hold snap point to list of hold points.""" if self.spoint and self.spoint not in self.holdPoints: diff --git a/src/Mod/Draft/draftguitools/gui_snaps.py b/src/Mod/Draft/draftguitools/gui_snaps.py index 75eb999335..57b005c18f 100644 --- a/src/Mod/Draft/draftguitools/gui_snaps.py +++ b/src/Mod/Draft/draftguitools/gui_snaps.py @@ -28,23 +28,72 @@ # \brief Provide the Draft_Snap commands used by the snapping mechanism # in Draft. -import draftguitools.gui_base as gui_base import FreeCADGui as Gui import draftguitools.gui_base as gui_base -from draftutils.translate import _tr +from PySide import QtGui from PySide.QtCore import QT_TRANSLATE_NOOP from draftutils.translate import _tr -class Draft_Snap_Lock(gui_base.GuiCommandSimplest): - """GuiCommand for the Draft_Snap_Lock tool. +# UTILITIES ----------------------------------------------------------------- - Activate or deactivate all snap methods at once. - """ - def __init__(self): - super().__init__(name=_tr("Main toggle snap")) +def get_snap_statusbar_widget(): + """retuns snap statusbar button""" + mw = Gui.getMainWindow() + if mw: + sb = mw.statusBar() + if sb: + return sb.findChild(QtGui.QToolBar,"draft_snap_widget") + return None + + +def sync_snap_toolbar_button(button, status): + """set snap toolbar button to given state""" + snap_toolbar = Gui.Snapper.get_snap_toolbar() + if not snap_toolbar: + return + for a in snap_toolbar.actions(): + if a.objectName() == button: + if button == "Draft_Snap_Lock_Button": + # for lock button + snap_toolbar.actions()[0].setChecked(status) + for a in snap_toolbar.actions()[1:]: + a.setEnabled(status) + else: + # for every other button + a.setChecked(status) + if a.isChecked(): + a.setToolTip(a.toolTip().replace("OFF","ON")) + else: + a.setToolTip(a.toolTip().replace("ON","OFF")) + + +def sync_snap_statusbar_button(button, status): + """set snap statusbar button to given state""" + ssw = get_snap_statusbar_widget() + if not ssw: + return + for child in ssw.children(): + if child.objectName() == "Snap_Statusbutton": + ssb = child + actions = [] + for a in ssb.menu().actions() + ssw.children()[-6:]: + actions.append(a) + + if button == "Draft_Snap_Lock_Statusbutton": + ssb.setChecked(status) + for a in actions[1:]: + a.setEnabled(status) + else: + for a in actions: + if a.objectName() == button: + a.setChecked(status) + + +# SNAP GUI TOOLS ------------------------------------------------------------ + class Draft_Snap_Lock(gui_base.GuiCommandSimplest): """GuiCommand for the Draft_Snap_Lock tool. @@ -53,7 +102,7 @@ class Draft_Snap_Lock(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Main toggle snap")) + super(Draft_Snap_Lock, self).__init__(name=_tr("Main toggle snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -68,17 +117,14 @@ class Draft_Snap_Lock(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() - + super(Draft_Snap_Lock, self).Activated() + if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "masterbutton"): - Gui.Snapper.masterbutton.toggle() + status = Gui.Snapper.toggle_snap('Lock') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Lock"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Lock"+"_Statusbutton", status) - if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "masterbutton"): - Gui.Snapper.masterbutton.toggle() - -Gui.addCommand('Draft_Snap_Lock', Draft_Snap_Lock()) Gui.addCommand('Draft_Snap_Lock', Draft_Snap_Lock()) @@ -90,7 +136,7 @@ class Draft_Snap_Midpoint(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Midpoint snap")) + super(Draft_Snap_Midpoint, self).__init__(name=_tr("Midpoint snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -103,13 +149,13 @@ class Draft_Snap_Midpoint(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Midpoint, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtonmidpoint": - b.toggle() + status = Gui.Snapper.toggle_snap('Midpoint') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Midpoint"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Midpoint_Statusbutton", status) Gui.addCommand('Draft_Snap_Midpoint', Draft_Snap_Midpoint()) @@ -122,7 +168,7 @@ class Draft_Snap_Perpendicular(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Perpendicular snap")) + super(Draft_Snap_Perpendicular, self).__init__(name=_tr("Perpendicular snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -137,13 +183,13 @@ class Draft_Snap_Perpendicular(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Perpendicular, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtonperpendicular": - b.toggle() + status = Gui.Snapper.toggle_snap('Perpendicular') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Perpendicular"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Perpendicular_Statusbutton", status) Gui.addCommand('Draft_Snap_Perpendicular', Draft_Snap_Perpendicular()) @@ -156,7 +202,7 @@ class Draft_Snap_Grid(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Grid snap")) + super(Draft_Snap_Grid, self).__init__(name=_tr("Grid snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -168,13 +214,13 @@ class Draft_Snap_Grid(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Grid, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtongrid": - b.toggle() + status = Gui.Snapper.toggle_snap('Grid') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Grid"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Grid_Statusbutton", status) Gui.addCommand('Draft_Snap_Grid', Draft_Snap_Grid()) @@ -187,7 +233,7 @@ class Draft_Snap_Intersection(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Intersection snap")) + super(Draft_Snap_Intersection, self).__init__(name=_tr("Intersection snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -202,13 +248,13 @@ class Draft_Snap_Intersection(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Intersection, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtonintersection": - b.toggle() + status = Gui.Snapper.toggle_snap('Intersection') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Intersection"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Intersection_Statusbutton", status) Gui.addCommand('Draft_Snap_Intersection', Draft_Snap_Intersection()) @@ -221,7 +267,7 @@ class Draft_Snap_Parallel(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Parallel snap")) + super(Draft_Snap_Parallel, self).__init__(name=_tr("Parallel snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -234,13 +280,13 @@ class Draft_Snap_Parallel(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Parallel, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtonparallel": - b.toggle() + status = Gui.Snapper.toggle_snap('Parallel') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Parallel"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Parallel_Statusbutton", status) Gui.addCommand('Draft_Snap_Parallel', Draft_Snap_Parallel()) @@ -253,7 +299,7 @@ class Draft_Snap_Endpoint(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Endpoint snap")) + super(Draft_Snap_Endpoint, self).__init__(name=_tr("Endpoint snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -266,13 +312,13 @@ class Draft_Snap_Endpoint(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Endpoint, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtonendpoint": - b.toggle() + status = Gui.Snapper.toggle_snap('Endpoint') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Endpoint"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Endpoint_Statusbutton", status) Gui.addCommand('Draft_Snap_Endpoint', Draft_Snap_Endpoint()) @@ -286,7 +332,7 @@ class Draft_Snap_Angle(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Angle snap (30 and 45 degrees)")) + super(Draft_Snap_Angle, self).__init__(name=_tr("Angle snap (30 and 45 degrees)")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -300,13 +346,13 @@ class Draft_Snap_Angle(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Angle, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtonangle": - b.toggle() + status = Gui.Snapper.toggle_snap('Angle') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Angle"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Angle_Statusbutton", status) Gui.addCommand('Draft_Snap_Angle', Draft_Snap_Angle()) @@ -319,7 +365,7 @@ class Draft_Snap_Center(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Arc center snap")) + super(Draft_Snap_Center, self).__init__(name=_tr("Arc center snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -331,13 +377,13 @@ class Draft_Snap_Center(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Center, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtoncenter": - b.toggle() + status = Gui.Snapper.toggle_snap('Center') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Center"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Center_Statusbutton", status) Gui.addCommand('Draft_Snap_Center', Draft_Snap_Center()) @@ -350,7 +396,7 @@ class Draft_Snap_Extension(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Edge extension snap")) + super(Draft_Snap_Extension, self).__init__(name=_tr("Edge extension snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -363,13 +409,13 @@ class Draft_Snap_Extension(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Extension, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtonextension": - b.toggle() + status = Gui.Snapper.toggle_snap('Extension') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Extension"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Extension_Statusbutton", status) Gui.addCommand('Draft_Snap_Extension', Draft_Snap_Extension()) @@ -382,7 +428,7 @@ class Draft_Snap_Near(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Near snap")) + super(Draft_Snap_Near, self).__init__(name=_tr("Near snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -394,13 +440,13 @@ class Draft_Snap_Near(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Near, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtonpassive": - b.toggle() + status = Gui.Snapper.toggle_snap('Near') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Near"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Near_Statusbutton", status) Gui.addCommand('Draft_Snap_Near', Draft_Snap_Near()) @@ -414,7 +460,7 @@ class Draft_Snap_Ortho(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Orthogonal snap")) + super(Draft_Snap_Ortho, self).__init__(name=_tr("Orthogonal snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -428,13 +474,13 @@ class Draft_Snap_Ortho(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Ortho, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtonortho": - b.toggle() + status = Gui.Snapper.toggle_snap('Ortho') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Ortho"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Ortho"+"_Statusbutton", status) Gui.addCommand('Draft_Snap_Ortho', Draft_Snap_Ortho()) @@ -447,7 +493,7 @@ class Draft_Snap_Special(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Special point snap")) + super(Draft_Snap_Special, self).__init__(name=_tr("Special point snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -460,13 +506,13 @@ class Draft_Snap_Special(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Special, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtonspecial": - b.toggle() + status = Gui.Snapper.toggle_snap('Special') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Special"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Special_Statusbutton", status) Gui.addCommand('Draft_Snap_Special', Draft_Snap_Special()) @@ -480,7 +526,7 @@ class Draft_Snap_Dimensions(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Dimension display")) + super(Draft_Snap_Dimensions, self).__init__(name=_tr("Dimension display")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -494,13 +540,13 @@ class Draft_Snap_Dimensions(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_Dimensions, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtonDimensions": - b.toggle() + status = Gui.Snapper.toggle_snap('Dimensions') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_Dimensions"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_Dimensions"+"_Statusbutton", status) Gui.addCommand('Draft_Snap_Dimensions', Draft_Snap_Dimensions()) @@ -516,7 +562,7 @@ class Draft_Snap_WorkingPlane(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Working plane snap")) + super(Draft_Snap_WorkingPlane, self).__init__(name=_tr("Working plane snap")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -536,13 +582,13 @@ class Draft_Snap_WorkingPlane(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(Draft_Snap_WorkingPlane, self).Activated() if hasattr(Gui, "Snapper"): - if hasattr(Gui.Snapper, "toolbarButtons"): - for b in Gui.Snapper.toolbarButtons: - if b.objectName() == "SnapButtonWorkingPlane": - b.toggle() + status = Gui.Snapper.toggle_snap('WorkingPlane') + # change interface consistently + sync_snap_toolbar_button("Draft_Snap_WorkingPlane"+"_Button", status) + sync_snap_statusbar_button("Draft_Snap_WorkingPlane_Statusbutton", status) Gui.addCommand('Draft_Snap_WorkingPlane', Draft_Snap_WorkingPlane()) @@ -555,7 +601,7 @@ class ShowSnapBar(gui_base.GuiCommandSimplest): """ def __init__(self): - super().__init__(name=_tr("Show snap toolbar")) + super(ShowSnapBar, self).__init__(name=_tr("Show snap toolbar")) def GetResources(self): """Set icon, menu and tooltip.""" @@ -569,7 +615,7 @@ class ShowSnapBar(gui_base.GuiCommandSimplest): def Activated(self): """Execute when the command is called.""" - super().Activated() + super(ShowSnapBar, self).Activated() if hasattr(Gui, "Snapper"): Gui.Snapper.show() From 81df9b76aeffb6f01c1cbbee4a74bb856bdc8c4b Mon Sep 17 00:00:00 2001 From: carlopav Date: Sat, 18 Apr 2020 22:54:40 +0200 Subject: [PATCH 14/33] [Draft] Snapper cleanup Cleanup after @vocx-fc review --- src/Mod/Draft/draftguitools/gui_snapper.py | 181 +++++++++++---------- 1 file changed, 92 insertions(+), 89 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_snapper.py b/src/Mod/Draft/draftguitools/gui_snapper.py index 7e680899d4..84ee1dc9e4 100644 --- a/src/Mod/Draft/draftguitools/gui_snapper.py +++ b/src/Mod/Draft/draftguitools/gui_snapper.py @@ -32,9 +32,6 @@ defined by `gui_trackers.gridTracker`. # This module provides tools to handle point snapping and # everything that goes with it (toolbar buttons, cursor icons, etc.). -import FreeCAD as App -import FreeCADGui as Gui - from pivy import coin from PySide import QtCore, QtGui @@ -47,6 +44,9 @@ import Draft import DraftVecUtils import DraftGeomUtils +import FreeCAD as App +import FreeCADGui as Gui + import Part import draftguitools.gui_trackers as trackers @@ -82,7 +82,6 @@ class Snapper: """ def __init__(self): - self.activeview = None self.lastObj = [None, None] self.maxEdges = 0 @@ -236,6 +235,9 @@ class Snapper: self.running = True + global Part, DraftGeomUtils + import Part, DraftGeomUtils + self.spoint = None if not hasattr(self, "toolbar"): @@ -602,69 +604,73 @@ class Snapper: self.setCursor(tsnap[1]) return tsnap[2], eline - for o in [self.lastObj[1], self.lastObj[0]]: + for o in (self.lastObj[1], self.lastObj[0]): if o and (self.isEnabled('Extension') - or self.isEnabled('Parallel')): + or self.isEnabled('Parallel')): ob = App.ActiveDocument.getObject(o) - if ob: - if ob.isDerivedFrom("Part::Feature"): - edges = ob.Shape.Edges - if Draft.getType(ob) == "Wall": - for so in [ob]+ob.Additions: - if Draft.getType(so) == "Wall": - if so.Base: - edges.extend(so.Base.Shape.Edges) - edges.reverse() - if (not self.maxEdges) or (len(edges) <= self.maxEdges): - for e in edges: - if DraftGeomUtils.geomType(e) == "Line": - np = self.getPerpendicular(e,point) - if not DraftGeomUtils.isPtOnEdge(np,e): - if (np.sub(point)).Length < self.radius: - if self.isEnabled('Extension'): - if np != e.Vertexes[0].Point: - p0 = e.Vertexes[0].Point - if self.tracker and not self.selectMode: - self.tracker.setCoords(np) - self.tracker.setMarker(self.mk['extension']) - self.tracker.on() - if self.extLine: - if self.snapStyle: - dv = np.sub(p0) - self.extLine.p1(p0.add(dv.multiply(0.5))) - else: - self.extLine.p1(p0) - self.extLine.p2(np) - self.extLine.on() - self.setCursor('extension') - ne = Part.LineSegment(p0,np).toShape() - # storing extension line for intersection calculations later - if len(self.lastExtensions) == 0: - self.lastExtensions.append(ne) - elif len(self.lastExtensions) == 1: - if not DraftGeomUtils.areColinear(ne,self.lastExtensions[0]): - self.lastExtensions.append(self.lastExtensions[0]) - self.lastExtensions[0] = ne - else: - if (not DraftGeomUtils.areColinear(ne,self.lastExtensions[0])) and \ - (not DraftGeomUtils.areColinear(ne,self.lastExtensions[1])): - self.lastExtensions[1] = self.lastExtensions[0] - self.lastExtensions[0] = ne - return np,ne + if not ob: + continue + if not ob.isDerivedFrom("Part::Feature"): + continue + edges = ob.Shape.Edges + if Draft.getType(ob) == "Wall": + for so in [ob]+ob.Additions: + if Draft.getType(so) == "Wall": + if so.Base: + edges.extend(so.Base.Shape.Edges) + edges.reverse() + if (not self.maxEdges) or (len(edges) <= self.maxEdges): + for e in edges: + if DraftGeomUtils.geomType(e) != "Line": + continue + np = self.getPerpendicular(e,point) + if DraftGeomUtils.isPtOnEdge(np,e): + continue + if (np.sub(point)).Length < self.radius: + if self.isEnabled('Extension'): + if np != e.Vertexes[0].Point: + p0 = e.Vertexes[0].Point + if self.tracker and not self.selectMode: + self.tracker.setCoords(np) + self.tracker.setMarker(self.mk['extension']) + self.tracker.on() + if self.extLine: + if self.snapStyle: + dv = np.sub(p0) + self.extLine.p1(p0.add(dv.multiply(0.5))) else: - if self.isEnabled('Parallel'): - if last: - ve = DraftGeomUtils.vec(e) - if not DraftVecUtils.isNull(ve): - de = Part.LineSegment(last,last.add(ve)).toShape() - np = self.getPerpendicular(de,point) - if (np.sub(point)).Length < self.radius: - if self.tracker and not self.selectMode: - self.tracker.setCoords(np) - self.tracker.setMarker(self.mk['parallel']) - self.tracker.on() - self.setCursor('parallel') - return np,de + self.extLine.p1(p0) + self.extLine.p2(np) + self.extLine.on() + self.setCursor('extension') + ne = Part.LineSegment(p0,np).toShape() + # storing extension line for intersection calculations later + if len(self.lastExtensions) == 0: + self.lastExtensions.append(ne) + elif len(self.lastExtensions) == 1: + if not DraftGeomUtils.areColinear(ne,self.lastExtensions[0]): + self.lastExtensions.append(self.lastExtensions[0]) + self.lastExtensions[0] = ne + else: + if (not DraftGeomUtils.areColinear(ne,self.lastExtensions[0])) and \ + (not DraftGeomUtils.areColinear(ne,self.lastExtensions[1])): + self.lastExtensions[1] = self.lastExtensions[0] + self.lastExtensions[0] = ne + return np,ne + else: + if self.isEnabled('Parallel'): + if last: + ve = DraftGeomUtils.vec(e) + if not DraftVecUtils.isNull(ve): + de = Part.LineSegment(last,last.add(ve)).toShape() + np = self.getPerpendicular(de,point) + if (np.sub(point)).Length < self.radius: + if self.tracker and not self.selectMode: + self.tracker.setCoords(np) + self.tracker.setMarker(self.mk['parallel']) + self.tracker.on() + self.setCursor('parallel') + return np,de return point,eline @@ -1024,15 +1030,15 @@ class Snapper: return snaps - def snapToVertex(self,info,active=False): - p = App.Vector(info['x'],info['y'],info['z']) + def snapToVertex(self, info, active=False): + p = App.Vector(info['x'], info['y'], info['z']) if active: if self.isEnabled("Near"): - return [p,'endpoint',self.toWP(p)] + return [p, 'endpoint', self.toWP(p)] else: return [] elif self.isEnabled("Near"): - return [p,'passive',p] + return [p, 'passive', p] else: return [] @@ -1046,6 +1052,7 @@ class Snapper: # special snapping for wall: snap to its base shape if it is linear if obj.Base: if not obj.Base.Shape.Solids: + for v in obj.Base.Shape.Vertexes: snaps.append([v.Point, 'special', self.toWP(v.Point)]) elif (Draft.getType(obj) == "Structure"): @@ -1339,12 +1346,11 @@ class Snapper: active=ctrl, constrain=shift) if hasattr(App, "DraftWorkingPlane"): self.ui.displayPoint(self.pt, last, - plane = App.DraftWorkingPlane, - mask = App.Snapper.affinity) + plane=App.DraftWorkingPlane, + mask=App.Snapper.affinity) if movecallback: movecallback(self.pt, self.snapInfo) - def getcoords(point, relative=False): """Get the global coordinates from a point.""" self.pt = point @@ -1353,14 +1359,12 @@ class Snapper: self.pt = last.add(v) accept() - def click(event_cb): event = event_cb.getEvent() if event.getButton() == 1: if event.getState() == coin.SoMouseButtonEvent.DOWN: accept() - def accept(): if self.callbackClick: self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.callbackClick) @@ -1378,7 +1382,6 @@ class Snapper: callback(self.pt) self.pt = None - def cancel(): if self.callbackClick: self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.callbackClick) @@ -1447,7 +1450,7 @@ class Snapper: b.setText(QtCore.QCoreApplication.translate("Draft_Snap", "Snap " + gc[11:])) b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap", "Snap " + gc[11:])) b.setObjectName(gc + button_suffix) - b.setWhatsThis("Draft_"+gc[11:].capitalize()) + b.setWhatsThis("Draft_" + gc[11:].capitalize()) b.setCheckable(True) b.setChecked(True) context.addAction(b) @@ -1470,36 +1473,36 @@ class Snapper: param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") snap_modes = param.GetString("snapModes") - for b in toolbar.actions(): - if len(b.statusTip()) == 0: - b.setStatusTip(b.toolTip()) + for button in toolbar.actions(): + if len(button.statusTip()) == 0: + button.setStatusTip(button.toolTip()) # restore toolbar buttons state if snap_modes: - for a in toolbar.findChildren(QtGui.QAction): - snap = a.objectName()[11:].replace(button_suffix,"") + for action in toolbar.findChildren(QtGui.QAction): + snap = action.objectName()[11:].replace(button_suffix, "") if snap in Gui.Snapper.snaps: i = Gui.Snapper.snaps.index(snap) state = bool(int(snap_modes[i])) - a.setChecked(state) + action.setChecked(state) if state: - a.setToolTip(a.toolTip()+" (ON)") + action.setToolTip(action.toolTip() + " (ON)") else: - a.setToolTip(a.toolTip()+" (OFF)") + action.setToolTip(action.toolTip() + " (OFF)") def get_snap_toolbar(self): - """retuns snap toolbar object""" + """Retuns snap toolbar object.""" mw = Gui.getMainWindow() if mw: - toolbar = mw.findChild(QtGui.QToolBar,"Draft Snap") + toolbar = mw.findChild(QtGui.QToolBar, "Draft Snap") if toolbar: return toolbar return None def toggleGrid(self): - "toggle FreeCAD Draft Grid" + """Toggle FreeCAD Draft Grid.""" Gui.runCommand("Draft_ToggleGrid") @@ -1513,7 +1516,7 @@ class Snapper: def isEnabled(self, snap): - "Returns true if the given snap is on" + """Returns true if the given snap is on""" if "Lock" in self.active_snaps and snap in self.active_snaps: return True else: @@ -1521,7 +1524,7 @@ class Snapper: def toggle_snap(self, snap, set_to = None): - "Sets the given snap on/off according to the given parameter" + """Sets the given snap on/off according to the given parameter""" if set_to: # set mode if set_to is True: if not snap in self.active_snaps: @@ -1544,7 +1547,7 @@ class Snapper: def save_snap_state(self): """ - save snap state to user preferences to be restored in next session + Save snap state to user preferences to be restored in next session. """ param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") snap_modes = "" From c8577544609b9588f4c0c37253368371e2c75c12 Mon Sep 17 00:00:00 2001 From: carlopav Date: Sun, 19 Apr 2020 17:47:59 +0200 Subject: [PATCH 15/33] [Draft] Further cleanup of the branch thanks to vocx revisions --- src/Mod/Draft/draftguitools/gui_snaps.py | 13 +++++++------ src/Mod/Draft/draftutils/init_draft_statusbar.py | 10 +++++++--- src/Mod/Draft/draftutils/init_tools.py | 12 ++++++------ 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_snaps.py b/src/Mod/Draft/draftguitools/gui_snaps.py index 57b005c18f..b26a8becea 100644 --- a/src/Mod/Draft/draftguitools/gui_snaps.py +++ b/src/Mod/Draft/draftguitools/gui_snaps.py @@ -28,11 +28,12 @@ # \brief Provide the Draft_Snap commands used by the snapping mechanism # in Draft. -import FreeCADGui as Gui -import draftguitools.gui_base as gui_base - from PySide import QtGui from PySide.QtCore import QT_TRANSLATE_NOOP + +import FreeCADGui as Gui + +import draftguitools.gui_base as gui_base from draftutils.translate import _tr @@ -40,7 +41,7 @@ from draftutils.translate import _tr def get_snap_statusbar_widget(): - """retuns snap statusbar button""" + """Return snap statusbar button.""" mw = Gui.getMainWindow() if mw: sb = mw.statusBar() @@ -50,7 +51,7 @@ def get_snap_statusbar_widget(): def sync_snap_toolbar_button(button, status): - """set snap toolbar button to given state""" + """Set snap toolbar button to given state.""" snap_toolbar = Gui.Snapper.get_snap_toolbar() if not snap_toolbar: return @@ -71,7 +72,7 @@ def sync_snap_toolbar_button(button, status): def sync_snap_statusbar_button(button, status): - """set snap statusbar button to given state""" + """Set snap statusbar button to given state.""" ssw = get_snap_statusbar_widget() if not ssw: return diff --git a/src/Mod/Draft/draftutils/init_draft_statusbar.py b/src/Mod/Draft/draftutils/init_draft_statusbar.py index 3373ac713f..56e1612d75 100644 --- a/src/Mod/Draft/draftutils/init_draft_statusbar.py +++ b/src/Mod/Draft/draftutils/init_draft_statusbar.py @@ -29,10 +29,13 @@ This module provide the code for the Draft Statusbar, activated by initGui # \ingroup DRAFT # \brief This module provides the code for the Draft Statusbar. +from PySide import QtCore +from PySide import QtGui +from PySide.QtCore import QT_TRANSLATE_NOOP + import FreeCAD as App import FreeCADGui as Gui -from PySide import QtCore, QtGui -from PySide.QtCore import QT_TRANSLATE_NOOP + from draftutils.init_tools import get_draft_snap_commands #---------------------------------------------------------------------------- @@ -148,7 +151,8 @@ def _set_scale(action): if custom_scale[1]: print(custom_scale[0]) scale = label_to_scale(custom_scale[0]) - if scale is None: return + if scale is None: + return param.SetFloat("DraftAnnotationScale", scale) cs = scale_to_label(scale) scale_widget.scaleLabel.setText(cs) diff --git a/src/Mod/Draft/draftutils/init_tools.py b/src/Mod/Draft/draftutils/init_tools.py index 7335ae2ccd..a4787f1a9d 100644 --- a/src/Mod/Draft/draftutils/init_tools.py +++ b/src/Mod/Draft/draftutils/init_tools.py @@ -115,13 +115,13 @@ def get_draft_utility_commands(): def get_draft_snap_commands(): """Return the snapping commands list.""" - return ['Draft_Snap_Lock', - 'Draft_Snap_Endpoint', 'Draft_Snap_Midpoint', - 'Draft_Snap_Center', 'Draft_Snap_Angle', + return ['Draft_Snap_Lock', + 'Draft_Snap_Endpoint', 'Draft_Snap_Midpoint', + 'Draft_Snap_Center', 'Draft_Snap_Angle', 'Draft_Snap_Intersection', 'Draft_Snap_Perpendicular', - 'Draft_Snap_Extension', 'Draft_Snap_Parallel', - 'Draft_Snap_Special', 'Draft_Snap_Near', - 'Draft_Snap_Ortho', 'Draft_Snap_Grid', + 'Draft_Snap_Extension', 'Draft_Snap_Parallel', + 'Draft_Snap_Special', 'Draft_Snap_Near', + 'Draft_Snap_Ortho', 'Draft_Snap_Grid', 'Draft_Snap_WorkingPlane', 'Draft_Snap_Dimensions', ] From 33999badba035f3b69883149eaad9d54e74e69bc Mon Sep 17 00:00:00 2001 From: donovaly Date: Sat, 28 Mar 2020 04:09:20 +0100 Subject: [PATCH 16/33] [Part] color preferences: add missing tooltips see: https://forum.freecadweb.org/viewtopic.php?f=8&t=44595 --- src/Mod/Part/Gui/DlgSettingsObjectColor.ui | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Mod/Part/Gui/DlgSettingsObjectColor.ui b/src/Mod/Part/Gui/DlgSettingsObjectColor.ui index 9de8a266bd..0a4d195120 100644 --- a/src/Mod/Part/Gui/DlgSettingsObjectColor.ui +++ b/src/Mod/Part/Gui/DlgSettingsObjectColor.ui @@ -40,7 +40,7 @@ The default color for new shapes - + 204 204 @@ -89,7 +89,7 @@ The default line color for new shapes - + 25 25 @@ -157,7 +157,7 @@ The default color for new vertices - + 25 25 @@ -225,7 +225,7 @@ The color of bounding boxes in the 3D view - + 255 255 @@ -248,6 +248,12 @@ 0 + + Bottom side of surface will be rendered the same way than top. +If not checked, it depends on the option "Backlight color" +(preferences section Display -> 3D View); either the backlight color +will be used or black. + Two-side rendering @@ -303,6 +309,9 @@ + + Text color for document annotations + AnnotationTextColor From 16d44f115b50894cb7d6a177b0161304874c9597 Mon Sep 17 00:00:00 2001 From: Jean-Marie Verdun Date: Sun, 19 Apr 2020 13:10:32 -0400 Subject: [PATCH 17/33] Add initial link support --- src/Mod/Arch/importOBJ.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Mod/Arch/importOBJ.py b/src/Mod/Arch/importOBJ.py index b449846717..29162c691f 100644 --- a/src/Mod/Arch/importOBJ.py +++ b/src/Mod/Arch/importOBJ.py @@ -72,14 +72,23 @@ def getIndices(obj,shape,offsetv,offsetvn): try: if not isinstance(e.Curve,Part.LineSegment): if not curves: - myshape = obj.Shape.copy(False) - myshape.Placement=obj.getGlobalPlacement() + if obj.isDerivedFrom("App::Link"): + myshape = obj.LinkedObject.Shape.copy(False) + myshape.Placement=obj.LinkPlacement + else: + myshape = obj.Shape.copy(False) + myshape.Placement=obj.getGlobalPlacement() mesh=MeshPart.meshFromShape(Shape=myshape, LinearDeflection=0.1, AngularDeflection=0.7, Relative=True) FreeCAD.Console.PrintWarning(translate("Arch","Found a shape containing curves, triangulating")+"\n") break except: # unimplemented curve type - myshape = obj.Shape.copy(False) - myshape.Placement=obj.getGlobalPlacement() + if obj.isDerivedFrom("App::Link"): + if obj.Shape: + myshape = obj.Shape.copy(False) + myshape.Placement=obj.LinkPlacement + else: + myshape = obj.Shape.copy(False) + myshape.Placement=obj.getGlobalPlacement() mesh=MeshPart.meshFromShape(Shape=myshape, LinearDeflection=0.1, AngularDeflection=0.7, Relative=True) FreeCAD.Console.PrintWarning(translate("Arch","Found a shape containing curves, triangulating")+"\n") break @@ -157,7 +166,7 @@ def export(exportList,filename,colors=None): materials = [] outfile.write("mtllib " + os.path.basename(filenamemtl) + "\n") for obj in objectslist: - if obj.isDerivedFrom("Part::Feature") or obj.isDerivedFrom("Mesh::Feature"): + if obj.isDerivedFrom("Part::Feature") or obj.isDerivedFrom("Mesh::Feature") or obj.isDerivedFrom("App::Link") or obj.isDerivedFrom("App::Link"): hires = None if FreeCAD.GuiUp: visible = obj.ViewObject.isVisible() From bf643bb6e1672a149b69891a87193f50a0ab924e Mon Sep 17 00:00:00 2001 From: Jean-Marie Verdun Date: Sun, 19 Apr 2020 14:17:04 -0400 Subject: [PATCH 18/33] Fix indent and double "or" --- src/Mod/Arch/importOBJ.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Mod/Arch/importOBJ.py b/src/Mod/Arch/importOBJ.py index 29162c691f..12919344ce 100644 --- a/src/Mod/Arch/importOBJ.py +++ b/src/Mod/Arch/importOBJ.py @@ -73,22 +73,22 @@ def getIndices(obj,shape,offsetv,offsetvn): if not isinstance(e.Curve,Part.LineSegment): if not curves: if obj.isDerivedFrom("App::Link"): - myshape = obj.LinkedObject.Shape.copy(False) - myshape.Placement=obj.LinkPlacement + myshape = obj.LinkedObject.Shape.copy(False) + myshape.Placement=obj.LinkPlacement else: - myshape = obj.Shape.copy(False) - myshape.Placement=obj.getGlobalPlacement() + myshape = obj.Shape.copy(False) + myshape.Placement=obj.getGlobalPlacement() mesh=MeshPart.meshFromShape(Shape=myshape, LinearDeflection=0.1, AngularDeflection=0.7, Relative=True) FreeCAD.Console.PrintWarning(translate("Arch","Found a shape containing curves, triangulating")+"\n") break except: # unimplemented curve type if obj.isDerivedFrom("App::Link"): if obj.Shape: - myshape = obj.Shape.copy(False) - myshape.Placement=obj.LinkPlacement + myshape = obj.Shape.copy(False) + myshape.Placement=obj.LinkPlacement else: - myshape = obj.Shape.copy(False) - myshape.Placement=obj.getGlobalPlacement() + myshape = obj.Shape.copy(False) + myshape.Placement=obj.getGlobalPlacement() mesh=MeshPart.meshFromShape(Shape=myshape, LinearDeflection=0.1, AngularDeflection=0.7, Relative=True) FreeCAD.Console.PrintWarning(translate("Arch","Found a shape containing curves, triangulating")+"\n") break @@ -166,7 +166,7 @@ def export(exportList,filename,colors=None): materials = [] outfile.write("mtllib " + os.path.basename(filenamemtl) + "\n") for obj in objectslist: - if obj.isDerivedFrom("Part::Feature") or obj.isDerivedFrom("Mesh::Feature") or obj.isDerivedFrom("App::Link") or obj.isDerivedFrom("App::Link"): + if obj.isDerivedFrom("Part::Feature") or obj.isDerivedFrom("Mesh::Feature") or obj.isDerivedFrom("App::Link"): hires = None if FreeCAD.GuiUp: visible = obj.ViewObject.isVisible() From 0c326f094f707f7ee77a4db3479d3af33141705a Mon Sep 17 00:00:00 2001 From: "luz.paz" Date: Sat, 18 Apr 2020 08:00:16 -0400 Subject: [PATCH 19/33] [skip ci] fix documentation typos Found via codespell v1.17.0.dev0 ``` codespell -q 3 -L aci,ake,aline,alle,alledges,alocation,als,ang,anid,ba,beginn,behaviour,bloaded,byteorder,calculater,cancelled,cancelling,cas,cascade,centimetre,childs,colour,colours,commen,connexion,currenty,dof,doubleclick,dum,eiter,elemente,ende,feld,finde,findf,freez,hist,iff,indicies,initialisation,initialise,initialised,initialises,initialisiert,ist,kilometre,lod,mantatory,methode,metres,millimetre,modell,nd,noe,normale,normaly,nto,numer,oder,orgin,orginx,orginy,ot,pard,pres,programm,que,recurrance,rougly,seperator,serie,sinc,strack,substraction,te,thist,thru,tread,uint,unter,vertexes,wallthickness,whitespaces -S ./.git,*.po,*.ts,./ChangeLog.txt,./src/3rdParty,./src/Mod/Assembly/App/opendcm,./src/CXX,./src/zipios++,./src/Base/swig*,./src/Mod/Robot/App/kdl_cp,./src/Mod/Import/App/SCL,./src/WindowsInstaller,./src/Doc/FreeCAD.uml ``` --- src/Mod/Draft/draftguitools/gui_arcs.py | 2 +- src/Mod/Draft/draftguitools/gui_groups.py | 4 ++-- src/Mod/Draft/draftguitools/gui_togglemodes.py | 2 +- src/Mod/Draft/draftobjects/label.py | 2 +- src/Mod/Mesh/App/MeshPy.xml | 2 +- src/Mod/Part/App/AppPartPy.cpp | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_arcs.py b/src/Mod/Draft/draftguitools/gui_arcs.py index b29766c1f4..eed62a60fb 100644 --- a/src/Mod/Draft/draftguitools/gui_arcs.py +++ b/src/Mod/Draft/draftguitools/gui_arcs.py @@ -139,7 +139,7 @@ class Arc_3Points(gui_base.GuiCommandSimplest): Parameters ---------- point: Base::Vector - The dynamic point pased by the callback + The dynamic point passed by the callback as we move the pointer in the 3D view. info: str diff --git a/src/Mod/Draft/draftguitools/gui_groups.py b/src/Mod/Draft/draftguitools/gui_groups.py index bc790bbf0c..3aadd4e965 100644 --- a/src/Mod/Draft/draftguitools/gui_groups.py +++ b/src/Mod/Draft/draftguitools/gui_groups.py @@ -51,7 +51,7 @@ class AddToGroup(gui_base.GuiCommandNeedsSelection): It adds selected objects to a group, or removes them from any group. - It inherits `GuiCommandNeedsSelection` to only be availbale + It inherits `GuiCommandNeedsSelection` to only be available when there is a document and a selection. See this class for more information. """ @@ -151,7 +151,7 @@ class SelectGroup(gui_base.GuiCommandNeedsSelection): in this case it works in an intuitive manner, selecting only the objects under the group. - It inherits `GuiCommandNeedsSelection` to only be availbale + It inherits `GuiCommandNeedsSelection` to only be available when there is a document and a selection. See this class for more information. """ diff --git a/src/Mod/Draft/draftguitools/gui_togglemodes.py b/src/Mod/Draft/draftguitools/gui_togglemodes.py index 2a6644e5ce..954024be08 100644 --- a/src/Mod/Draft/draftguitools/gui_togglemodes.py +++ b/src/Mod/Draft/draftguitools/gui_togglemodes.py @@ -160,7 +160,7 @@ class ToggleDisplayMode(gui_base.GuiCommandNeedsSelection): Switches the display mode of selected objects from flatlines to wireframe and back. - It inherits `GuiCommandNeedsSelection` to only be availbale + It inherits `GuiCommandNeedsSelection` to only be available when there is a document and a selection. See this class for more information. """ diff --git a/src/Mod/Draft/draftobjects/label.py b/src/Mod/Draft/draftobjects/label.py index 6d6af72010..e025c30703 100644 --- a/src/Mod/Draft/draftobjects/label.py +++ b/src/Mod/Draft/draftobjects/label.py @@ -62,7 +62,7 @@ def make_label(targetpoint=None, target=None, direction=None, ["Horizontal","Vertical","Custom"] distance : Quantity - Lenght of the straight segment of label leader line + Length of the straight segment of label leader line labeltype : String Label type in diff --git a/src/Mod/Mesh/App/MeshPy.xml b/src/Mod/Mesh/App/MeshPy.xml index 974b027428..a43402f289 100644 --- a/src/Mod/Mesh/App/MeshPy.xml +++ b/src/Mod/Mesh/App/MeshPy.xml @@ -469,7 +469,7 @@ an empty dictionary if there is no intersection. getPlanarSegments(dev,[min faces=0]) -> list Get all planes of the mesh as segment. In the worst case each triangle can be regarded as single -plane if none of its neighours is coplanar. +plane if none of its neighbors are coplanar. diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp index 1210c81d55..6bbee793bb 100644 --- a/src/Mod/Part/App/AppPartPy.cpp +++ b/src/Mod/Part/App/AppPartPy.cpp @@ -750,7 +750,7 @@ private: p1.Transform(loc.Transformation()); p2.Transform(loc.Transformation()); p3.Transform(loc.Transformation()); - // TODO: verify if tolerence should be hard coded + // TODO: verify if tolerance should be hard coded if (!p1.IsEqual(p2, 0.01) && !p2.IsEqual(p3, 0.01) && !p3.IsEqual(p1, 0.01)) { PyObject *t1 = PyTuple_Pack(3, PyFloat_FromDouble(p1.X()), PyFloat_FromDouble(p1.Y()), PyFloat_FromDouble(p1.Z())); PyObject *t2 = PyTuple_Pack(3, PyFloat_FromDouble(p2.X()), PyFloat_FromDouble(p2.Y()), PyFloat_FromDouble(p2.Z())); From 2ce452c6505f52cd5dfcc0252f69260e3a6f600b Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Mon, 20 Apr 2020 13:34:38 +0200 Subject: [PATCH 20/33] Fixed bad conflict merge in PArtDesign --- src/Mod/PartDesign/SprocketFeature.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Mod/PartDesign/SprocketFeature.py b/src/Mod/PartDesign/SprocketFeature.py index 0cfa0c9adc..95977518cb 100644 --- a/src/Mod/PartDesign/SprocketFeature.py +++ b/src/Mod/PartDesign/SprocketFeature.py @@ -141,13 +141,10 @@ class ViewProviderSprocket: class SprocketTaskPanel: -<<<<<<< HEAD """ The editmode TaskPanel for Sprocket objects """ -======= - '''The editmode TaskPanel for Sprocket objects''' ->>>>>>> 07b2401c1... Converted class names from private to public, per feedback from pull request + def __init__(self,obj,mode): self.obj = obj From e8e67e8c5ebbc9f9ed9ea67aba5b891969595ece Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Mon, 20 Apr 2020 16:22:03 +0200 Subject: [PATCH 21/33] Adding FreeCAD liberapay account --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 09ab5698fd..175d975d86 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -6,7 +6,7 @@ open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username +liberapay: FreeCAD issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: ['https://www.patreon.com/yorikvanhavre', 'https://www.patreon.com/kkremitzki', 'https://www.patreon.com/thundereal'] From 1090da622a2ccfb037e2b441518afc71a6a565f6 Mon Sep 17 00:00:00 2001 From: triplus Date: Mon, 20 Apr 2020 20:59:16 +0200 Subject: [PATCH 22/33] NaviCube fix compiler warning Forum discussion: https://forum.freecadweb.org/viewtopic.php?f=10&t=45012 --- src/Gui/NaviCube.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Gui/NaviCube.cpp b/src/Gui/NaviCube.cpp index 244230e533..2690031d42 100644 --- a/src/Gui/NaviCube.cpp +++ b/src/Gui/NaviCube.cpp @@ -974,8 +974,8 @@ void NaviCubeImplementation::drawNaviCube(bool pickMode) { static GLubyte xbmp[] = { 0x11,0x11,0x0a,0x04,0x0a,0x11,0x11 }; glColor3f(1, 0, 0); glBegin(GL_LINES); - glVertex3f(-1.1 , -1.1, -1.1); - glVertex3f(+0.5 , -1.1, -1.1); + glVertex3f(-1.1f, -1.1f, -1.1f); + glVertex3f(+0.5f, -1.1f, -1.1f); glEnd(); glRasterPos3d(a, -a, -a); glBitmap(8, 7, 0, 0, 0, 0, xbmp); @@ -983,8 +983,8 @@ void NaviCubeImplementation::drawNaviCube(bool pickMode) { static GLubyte ybmp[] = { 0x04,0x04,0x04,0x04,0x0a,0x11,0x11 }; glColor3f(0, 1, 0); glBegin(GL_LINES); - glVertex3f(-1.1 , -1.1, -1.1); - glVertex3f(-1.1 , +0.5, -1.1); + glVertex3f(-1.1f, -1.1f, -1.1f); + glVertex3f(-1.1f, +0.5f, -1.1f); glEnd(); glRasterPos3d( -a, a, -a); glBitmap(8, 7, 0, 0, 0, 0, ybmp); @@ -992,8 +992,8 @@ void NaviCubeImplementation::drawNaviCube(bool pickMode) { static GLubyte zbmp[] = { 0x1f,0x10,0x08,0x04,0x02,0x01,0x1f }; glColor3f(0, 0, 1); glBegin(GL_LINES); - glVertex3f(-1.1 , -1.1, -1.1); - glVertex3f(-1.1 , -1.1, +0.5); + glVertex3f(-1.1f, -1.1f, -1.1f); + glVertex3f(-1.1f, -1.1f, +0.5f); glEnd(); glRasterPos3d( -a, -a, a); glBitmap(8, 7, 0, 0, 0, 0, zbmp); From dc742e85231b31bccf99f885538cf8ffd8b380af Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Tue, 21 Apr 2020 12:20:10 +0200 Subject: [PATCH 23/33] Draft: Fixed broken backwards compatibility with annotations --- .../draftviewproviders/view_dimension.py | 73 ++++++++++--------- src/Mod/Draft/draftviewproviders/view_text.py | 18 +++-- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/Mod/Draft/draftviewproviders/view_dimension.py b/src/Mod/Draft/draftviewproviders/view_dimension.py index b73553f21a..dbc59a1386 100644 --- a/src/Mod/Draft/draftviewproviders/view_dimension.py +++ b/src/Mod/Draft/draftviewproviders/view_dimension.py @@ -225,6 +225,10 @@ class ViewProviderLinearDimension(ViewProviderDimensionBase): def attach(self, vobj): '''Setup the scene sub-graph of the view provider''' + + # backwards compatibility + self.ScaleMultiplier = 1.00 + self.Object = vobj.Object self.color = coin.SoBaseColor() self.font = coin.SoFont() @@ -341,8 +345,8 @@ class ViewProviderLinearDimension(ViewProviderDimensionBase): self.p2 = self.p1 self.p3 = self.p4 if proj: - if hasattr(obj.ViewObject,"ExtLines") and hasattr(obj.ViewObject,"ScaleMultiplier"): - dmax = obj.ViewObject.ExtLines.Value * obj.ViewObject.ScaleMultiplier + if hasattr(obj.ViewObject,"ExtLines"): + dmax = obj.ViewObject.ExtLines.Value * self.ScaleMultiplier if dmax and (proj.Length > dmax): if (dmax > 0): self.p1 = self.p2.add(DraftVecUtils.scaleTo(proj,dmax)) @@ -402,8 +406,8 @@ class ViewProviderLinearDimension(ViewProviderDimensionBase): rot3 = App.Placement(DraftVecUtils.getPlaneRotation(u3,v3,norm)).Rotation.Q self.transExtOvershoot1.rotation.setValue((rot3[0],rot3[1],rot3[2],rot3[3])) self.transExtOvershoot2.rotation.setValue((rot3[0],rot3[1],rot3[2],rot3[3])) - if hasattr(obj.ViewObject,"TextSpacing") and hasattr(obj.ViewObject,"ScaleMultiplier"): - ts = obj.ViewObject.TextSpacing.Value * obj.ViewObject.ScaleMultiplier + if hasattr(obj.ViewObject,"TextSpacing"): + ts = obj.ViewObject.TextSpacing.Value * self.ScaleMultiplier offset = DraftVecUtils.scaleTo(v1,ts) else: offset = DraftVecUtils.scaleTo(v1,0.05) @@ -474,11 +478,13 @@ class ViewProviderLinearDimension(ViewProviderDimensionBase): def onChanged(self, vobj, prop): """called when a view property has changed""" if prop == "ScaleMultiplier" and hasattr(vobj,"ScaleMultiplier"): + if vobj.ScaleMultiplier: # don't set if zero + self.ScaleMultiplier = vobj.ScaleMultiplier # update all dimension values if hasattr(self,"font"): - self.font.size = vobj.FontSize.Value*vobj.ScaleMultiplier + self.font.size = vobj.FontSize.Value * self.ScaleMultiplier if hasattr(self,"font3d"): - self.font3d.size = vobj.FontSize.Value*100*vobj.ScaleMultiplier + self.font3d.size = vobj.FontSize.Value * 100 * self.ScaleMultiplier if hasattr(self,"node") and hasattr(self,"p2") and hasattr(vobj,"ArrowSize"): self.remove_dim_arrows() self.draw_dim_arrows(vobj) @@ -492,10 +498,10 @@ class ViewProviderLinearDimension(ViewProviderDimensionBase): vobj.Object.touch() elif (prop == "FontSize") and hasattr(vobj,"FontSize"): - if hasattr(self,"font") and hasattr(vobj,"ScaleMultiplier"): - self.font.size = vobj.FontSize.Value*vobj.ScaleMultiplier - if hasattr(self,"font3d") and hasattr(vobj,"ScaleMultiplier"): - self.font3d.size = vobj.FontSize.Value*100*vobj.ScaleMultiplier + if hasattr(self,"font"): + self.font.size = vobj.FontSize.Value * self.ScaleMultiplier + if hasattr(self,"font3d"): + self.font3d.size = vobj.FontSize.Value * 100 * self.ScaleMultiplier vobj.Object.touch() elif (prop == "FontName") and hasattr(vobj,"FontName"): @@ -514,22 +520,19 @@ class ViewProviderLinearDimension(ViewProviderDimensionBase): elif (prop in ["ArrowSize","ArrowType"]) and hasattr(vobj,"ArrowSize"): if hasattr(self,"node") and hasattr(self,"p2"): - if hasattr(vobj,"ScaleMultiplier"): - self.remove_dim_arrows() - self.draw_dim_arrows(vobj) - vobj.Object.touch() + self.remove_dim_arrows() + self.draw_dim_arrows(vobj) + vobj.Object.touch() elif (prop == "DimOvershoot") and hasattr(vobj,"DimOvershoot"): - if hasattr(vobj,"ScaleMultiplier"): - self.remove_dim_overshoot() - self.draw_dim_overshoot(vobj) - vobj.Object.touch() + self.remove_dim_overshoot() + self.draw_dim_overshoot(vobj) + vobj.Object.touch() elif (prop == "ExtOvershoot") and hasattr(vobj,"ExtOvershoot"): - if hasattr(vobj,"ScaleMultiplier"): - self.remove_ext_overshoot() - self.draw_ext_overshoot(vobj) - vobj.Object.touch() + self.remove_ext_overshoot() + self.draw_ext_overshoot(vobj) + vobj.Object.touch() elif (prop == "ShowLine") and hasattr(vobj,"ShowLine"): if vobj.ShowLine: @@ -559,7 +562,7 @@ class ViewProviderLinearDimension(ViewProviderDimensionBase): # set scale symbol = utils.ARROW_TYPES.index(vobj.ArrowType) - s = vobj.ArrowSize.Value * vobj.ScaleMultiplier + s = vobj.ArrowSize.Value * self.ScaleMultiplier self.trans1.scaleFactor.setValue((s,s,s)) self.trans2.scaleFactor.setValue((s,s,s)) @@ -593,7 +596,7 @@ class ViewProviderLinearDimension(ViewProviderDimensionBase): from pivy import coin # set scale - s = vobj.DimOvershoot.Value * vobj.ScaleMultiplier + s = vobj.DimOvershoot.Value * self.ScaleMultiplier self.transDimOvershoot1.scaleFactor.setValue((s,s,s)) self.transDimOvershoot2.scaleFactor.setValue((s,s,s)) @@ -624,7 +627,7 @@ class ViewProviderLinearDimension(ViewProviderDimensionBase): from pivy import coin # set scale - s = vobj.ExtOvershoot.Value * vobj.ScaleMultiplier + s = vobj.ExtOvershoot.Value * self.ScaleMultiplier self.transExtOvershoot1.scaleFactor.setValue((s,s,s)) self.transExtOvershoot2.scaleFactor.setValue((s,s,s)) @@ -728,6 +731,8 @@ class ViewProviderAngularDimension(ViewProviderDimensionBase): self.onChanged(vobj,"FontName") self.onChanged(vobj,"ArrowType") self.onChanged(vobj,"LineColor") + # backwards compatibility + self.ScaleMultiplier = 1.00 def updateData(self, obj, prop): if hasattr(self,"arc"): @@ -853,21 +858,23 @@ class ViewProviderAngularDimension(ViewProviderDimensionBase): def onChanged(self, vobj, prop): if prop == "ScaleMultiplier" and hasattr(vobj,"ScaleMultiplier"): + if vobj.ScaleMultiplier: + self.ScaleMultiplier = vobj.ScaleMultiplier # update all dimension values if hasattr(self,"font"): - self.font.size = vobj.FontSize.Value*vobj.ScaleMultiplier + self.font.size = vobj.FontSize.Value * self.ScaleMultiplier if hasattr(self,"font3d"): - self.font3d.size = vobj.FontSize.Value*100*vobj.ScaleMultiplier + self.font3d.size = vobj.FontSize.Value * 100 * self.ScaleMultiplier if hasattr(self,"node") and hasattr(self,"p2") and hasattr(vobj,"ArrowSize"): self.remove_dim_arrows() self.draw_dim_arrows(vobj) self.updateData(vobj.Object,"Start") vobj.Object.touch() - elif prop == "FontSize" and hasattr(vobj,"ScaleMultiplier"): + elif prop == "FontSize": if hasattr(self,"font"): - self.font.size = vobj.FontSize.Value*vobj.ScaleMultiplier + self.font.size = vobj.FontSize.Value * self.ScaleMultiplier if hasattr(self,"font3d"): - self.font3d.size = vobj.FontSize.Value*100*vobj.ScaleMultiplier + self.font3d.size = vobj.FontSize.Value * 100 * self.ScaleMultiplier vobj.Object.touch() elif prop == "FontName": if hasattr(self,"font") and hasattr(self,"font3d"): @@ -880,7 +887,7 @@ class ViewProviderAngularDimension(ViewProviderDimensionBase): elif prop == "LineWidth": if hasattr(self,"drawstyle"): self.drawstyle.lineWidth = vobj.LineWidth - elif prop in ["ArrowSize","ArrowType"] and hasattr(vobj,"ScaleMultiplier"): + elif prop in ["ArrowSize","ArrowType"]: if hasattr(self,"node") and hasattr(self,"p2"): self.remove_dim_arrows() self.draw_dim_arrows(vobj) @@ -901,7 +908,7 @@ class ViewProviderAngularDimension(ViewProviderDimensionBase): # set scale symbol = utils.ARROW_TYPES.index(vobj.ArrowType) - s = vobj.ArrowSize.Value * vobj.ScaleMultiplier + s = vobj.ArrowSize.Value * self.ScaleMultiplier self.trans1.scaleFactor.setValue((s,s,s)) self.trans2.scaleFactor.setValue((s,s,s)) @@ -926,4 +933,4 @@ class ViewProviderAngularDimension(ViewProviderDimensionBase): self.node3d.insertChild(self.marks,2) def getIcon(self): - return ":/icons/Draft_DimensionAngular.svg" \ No newline at end of file + return ":/icons/Draft_DimensionAngular.svg" diff --git a/src/Mod/Draft/draftviewproviders/view_text.py b/src/Mod/Draft/draftviewproviders/view_text.py index bbf2636315..e080996ce1 100644 --- a/src/Mod/Draft/draftviewproviders/view_text.py +++ b/src/Mod/Draft/draftviewproviders/view_text.py @@ -93,6 +93,10 @@ class ViewProviderText(ViewProviderDraftAnnotation): def attach(self,vobj): '''Setup the scene sub-graph of the view provider''' + + # backwards compatibility + self.ScaleMultiplier = 1.00 + self.mattext = coin.SoMaterial() textdrawstyle = coin.SoDrawStyle() textdrawstyle.style = coin.SoDrawStyle.FILLED @@ -123,7 +127,6 @@ class ViewProviderText(ViewProviderDraftAnnotation): self.onChanged(vobj,"Justification") self.onChanged(vobj,"LineSpacing") - def getDisplayModes(self,vobj): return ["2D text","3D text"] @@ -148,8 +151,11 @@ class ViewProviderText(ViewProviderDraftAnnotation): def onChanged(self,vobj,prop): if prop == "ScaleMultiplier": - if "ScaleMultiplier" in vobj.PropertiesList and "FontSize" in vobj.PropertiesList: - self.font.size = vobj.FontSize.Value * vobj.ScaleMultiplier + if "ScaleMultiplier" in vobj.PropertiesList: + if vobj.ScaleMultiplier: + self.ScaleMultiplier = vobj.ScaleMultiplier + if "FontSize" in vobj.PropertiesList: + self.font.size = vobj.FontSize.Value * self.ScaleMultiplier elif prop == "TextColor": if "TextColor" in vobj.PropertiesList: l = vobj.TextColor @@ -158,8 +164,8 @@ class ViewProviderText(ViewProviderDraftAnnotation): if "FontName" in vobj.PropertiesList: self.font.name = vobj.FontName.encode("utf8") elif prop == "FontSize": - if "FontSize" in vobj.PropertiesList and "ScaleMultiplier" in vobj.PropertiesList: - self.font.size = vobj.FontSize.Value * vobj.ScaleMultiplier + if "FontSize" in vobj.PropertiesList: + self.font.size = vobj.FontSize.Value * self.ScaleMultiplier elif prop == "Justification": try: if getattr(vobj, "Justification", None) is not None: @@ -177,4 +183,4 @@ class ViewProviderText(ViewProviderDraftAnnotation): elif prop == "LineSpacing": if "LineSpacing" in vobj.PropertiesList: self.text2d.spacing = vobj.LineSpacing - self.text3d.spacing = vobj.LineSpacing \ No newline at end of file + self.text3d.spacing = vobj.LineSpacing From 6f2fd045185e633b9de3c6846e9f1fad95a6feff Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Tue, 21 Apr 2020 12:46:08 +0200 Subject: [PATCH 24/33] Draft: Fixed further regressions --- src/Mod/Draft/Draft.py | 6 +++--- src/Mod/Draft/getSVG.py | 2 +- src/Mod/Draft/importDXF.py | 2 +- src/Mod/Draft/importSVG.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 9fd870b6f2..23bc62de04 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -514,7 +514,7 @@ def makeCopy(obj,force=None,reparent=False): _Point(newobj) if gui: _ViewProviderPoint(newobj.ViewObject) - elif (getType(obj) == "Dimension") or (force == "Dimension"): + elif (getType(obj) in ["Dimension","LinearDimension"]) or (force == "Dimension"): newobj = FreeCAD.ActiveDocument.addObject(obj.TypeId,getRealName(obj.Name)) _Dimension(newobj) if gui: @@ -1025,7 +1025,7 @@ def move(objectslist,vector,copy=False): else: newobj = obj newobj.Placement.Base = obj.Placement.Base.add(real_vector) - elif getType(obj) == "Dimension": + elif getType(obj) in ["Dimension","LinearDimension"]: if copy: newobj = FreeCAD.ActiveDocument.addObject("App::FeaturePython",getRealName(obj.Name)) _Dimension(newobj) @@ -1589,7 +1589,7 @@ def getDXF(obj,direction=None): ny = DraftVecUtils.project(vec,plane.v) return Vector(nx.Length,ny.Length,0) - if getType(obj) == "Dimension": + if getType(obj) in ["Dimension","LinearDimension"]: p1 = getProj(obj.Start) p2 = getProj(obj.End) p3 = getProj(obj.Dimline) diff --git a/src/Mod/Draft/getSVG.py b/src/Mod/Draft/getSVG.py index 7d90a0d3d7..28ee5b6628 100644 --- a/src/Mod/Draft/getSVG.py +++ b/src/Mod/Draft/getSVG.py @@ -485,7 +485,7 @@ def getSVG(obj,scale=1,linewidth=0.35,fontsize=12,fillstyle="shape color",direct svg += getPath(obj.Edges,pathname="") - elif getType(obj) == "Dimension": + elif getType(obj) in ["Dimension","LinearDimension"]: if gui: if not obj.ViewObject: print ("export of dimensions to SVG is only available in GUI mode") diff --git a/src/Mod/Draft/importDXF.py b/src/Mod/Draft/importDXF.py index e972cb8816..0859b0eb1d 100644 --- a/src/Mod/Draft/importDXF.py +++ b/src/Mod/Draft/importDXF.py @@ -3817,7 +3817,7 @@ def export(objectslist, filename, nospline=False, lwPoly=False): style='STANDARD', layer=getStrGroup(ob))) - elif Draft.getType(ob) == "Dimension": + elif Draft.getType(ob) in ["Dimension","LinearDimension"]: p1 = DraftVecUtils.tup(ob.Start) p2 = DraftVecUtils.tup(ob.End) base = Part.LineSegment(ob.Start, ob.End).toShape() diff --git a/src/Mod/Draft/importSVG.py b/src/Mod/Draft/importSVG.py index d760552062..e9aa9d78ad 100644 --- a/src/Mod/Draft/importSVG.py +++ b/src/Mod/Draft/importSVG.py @@ -1526,7 +1526,7 @@ class svgHandler(xml.sax.ContentHandler): # see issue #2062 sh = sh.transformGeometry(transform) return sh - elif Draft.getType(sh) == "Dimension": + elif Draft.getType(sh) in ["Dimension","LinearDimension"]: pts = [] for p in [sh.Start, sh.End, sh.Dimline]: cp = Vector(p) From dd41c1a4922d12b785e21ad40f341479e30d3572 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 16 Mar 2020 22:45:21 -0600 Subject: [PATCH 25/33] Draft: GuiCommandSimplest to serve as base of simple Gui Commands This class defines the `command_name` of the command, so that it is output to the report view, and is also recorded in the log file. It also stores the current document so it can be used inside the command. The class implements with `IsActive` method so that the command is only active when an active document exists. Also `GuiCommandNeedsSelection`, which subclasses the former class. It reimplements `IsActive` in order to be available only when there is a selection. --- src/Mod/Draft/draftguitools/gui_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_base.py b/src/Mod/Draft/draftguitools/gui_base.py index 86cc109d88..fd02f8c563 100644 --- a/src/Mod/Draft/draftguitools/gui_base.py +++ b/src/Mod/Draft/draftguitools/gui_base.py @@ -33,7 +33,7 @@ import draftutils.todo as todo from draftutils.messages import _msg, _log -class GuiCommandSimplest(object): +class GuiCommandSimplest: """Simplest base class for GuiCommands. This class only sets up the command name and the document object @@ -126,7 +126,7 @@ class GuiCommandNeedsSelection(GuiCommandSimplest): return False -class GuiCommandBase(object): +class GuiCommandBase: """Generic class that is the basis of all Gui commands. This class should eventually replace `DraftTools.DraftTool`, From 0ba89d1662e6d9540762dfae96a4ee742865f0fc Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sun, 29 Mar 2020 21:53:21 -0600 Subject: [PATCH 26/33] Draft: move DraftTools utilities to another module These small functions and utilities are placed in another module, so they can be imported and called by the individual Gui Command classes as necessary. When these Gui Command classes are moved to their respective modules, they will be able to import this auxiliary module as well. --- src/Mod/Draft/CMakeLists.txt | 1 + src/Mod/Draft/DraftTools.py | 147 +------ src/Mod/Draft/draftguitools/gui_tool_utils.py | 389 ++++++++++++++++++ 3 files changed, 404 insertions(+), 133 deletions(-) create mode 100644 src/Mod/Draft/draftguitools/gui_tool_utils.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index f076366bb0..3186aebb05 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -86,6 +86,7 @@ SET(Draft_view_providers SET(Draft_GUI_tools draftguitools/__init__.py draftguitools/gui_base.py + draftguitools/gui_tool_utils.py draftguitools/gui_circulararray.py draftguitools/gui_orthoarray.py draftguitools/gui_polararray.py diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 0b54833b5f..8925928a89 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -114,144 +114,29 @@ elif defaultWP == 3: plane.alignToPointAndAxis(Vector(0,0,0), Vector(1,0,0), 0) # last snapped objects, for quick intersection calculation lastObj = [0,0] -# set modifier keys -MODS = ["shift","ctrl","alt"] -MODCONSTRAIN = MODS[Draft.getParam("modconstrain",0)] -MODSNAP = MODS[Draft.getParam("modsnap",1)] -MODALT = MODS[Draft.getParam("modalt",2)] +# Set modifier keys +from draftguitools.gui_tool_utils import MODCONSTRAIN +from draftguitools.gui_tool_utils import MODSNAP +from draftguitools.gui_tool_utils import MODALT # --------------------------------------------------------------------------- # General functions # --------------------------------------------------------------------------- -def formatUnit(exp,unit="mm"): - '''returns a formatting string to set a number to the correct unit''' - return FreeCAD.Units.Quantity(exp,FreeCAD.Units.Length).UserString +from draftguitools.gui_tool_utils import formatUnit -def selectObject(arg): - '''this is a scene even handler, to be called from the Draft tools - when they need to select an object''' - if (arg["Type"] == "SoKeyboardEvent"): - if (arg["Key"] == "ESCAPE"): - FreeCAD.activeDraftCommand.finish() - # TODO : this part raises a coin3D warning about scene traversal, to be fixed. - elif (arg["Type"] == "SoMouseButtonEvent"): - if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): - cursor = arg["Position"] - snapped = Draft.get3DView().getObjectInfo((cursor[0],cursor[1])) - if snapped: - obj = FreeCAD.ActiveDocument.getObject(snapped['Object']) - FreeCADGui.Selection.addSelection(obj) - FreeCAD.activeDraftCommand.component=snapped['Component'] - FreeCAD.activeDraftCommand.proceed() +from draftguitools.gui_tool_utils import selectObject -def getPoint(target,args,mobile=False,sym=False,workingplane=True,noTracker=False): - """Function used by the Draft Tools. - returns a constrained 3d point and its original point. - if mobile=True, the constraining occurs from the location of - mouse cursor when Shift is pressed, otherwise from last entered - point. If sym=True, x and y values stay always equal. If workingplane=False, - the point won't be projected on the Working Plane. if noTracker is True, the - tracking line will not be displayed - """ +from draftguitools.gui_tool_utils import getPoint - ui = FreeCADGui.draftToolBar +from draftguitools.gui_tool_utils import getSupport - if target.node: - last = target.node[-1] - else: - last = None +from draftguitools.gui_tool_utils import setWorkingPlaneToObjectUnderCursor - amod = hasMod(args, MODSNAP) - cmod = hasMod(args, MODCONSTRAIN) - point = None +from draftguitools.gui_tool_utils import setWorkingPlaneToSelectedObject - if hasattr(FreeCADGui, "Snapper"): - point = FreeCADGui.Snapper.snap(args["Position"],lastpoint=last,active=amod,constrain=cmod,noTracker=noTracker) - info = FreeCADGui.Snapper.snapInfo - mask = FreeCADGui.Snapper.affinity - if not point: - p = FreeCADGui.ActiveDocument.ActiveView.getCursorPos() - point = FreeCADGui.ActiveDocument.ActiveView.getPoint(p) - info = FreeCADGui.ActiveDocument.ActiveView.getObjectInfo(p) - mask = None +from draftguitools.gui_tool_utils import hasMod - ctrlPoint = Vector(point) - if target.node: - if target.featureName == "Rectangle": - ui.displayPoint(point, target.node[0], plane=plane, mask=mask) - else: - ui.displayPoint(point, target.node[-1], plane=plane, mask=mask) - else: - ui.displayPoint(point, plane=plane, mask=mask) - return point,ctrlPoint,info - -def getSupport(mouseEvent=None): - """returns the supporting object and sets the working plane""" - plane.save() - if mouseEvent: - return setWorkingPlaneToObjectUnderCursor(mouseEvent) - return setWorkingPlaneToSelectedObject() - -def setWorkingPlaneToObjectUnderCursor(mouseEvent): - objectUnderCursor = Draft.get3DView().getObjectInfo(( - mouseEvent["Position"][0], - mouseEvent["Position"][1])) - - if not objectUnderCursor: - return None - - try: - componentUnderCursor = getattr( - FreeCAD.ActiveDocument.getObject( - objectUnderCursor['Object'] - ).Shape, - objectUnderCursor["Component"]) - - if not plane.weak: - return None - - if "Face" in objectUnderCursor["Component"]: - plane.alignToFace(componentUnderCursor) - else: - plane.alignToCurve(componentUnderCursor) - plane.weak = True - return objectUnderCursor - except: - pass - - return None - -def setWorkingPlaneToSelectedObject(): - sel = FreeCADGui.Selection.getSelectionEx() - if len(sel) != 1: - return None - sel = sel[0] - if sel.HasSubObjects \ - and len(sel.SubElementNames) == 1 \ - and "Face" in sel.SubElementNames[0]: - if plane.weak: - plane.alignToFace(sel.SubObjects[0]) - plane.weak = True - return sel.Object - return None - -def hasMod(args,mod): - """checks if args has a specific modifier""" - if mod == "shift": - return args["ShiftDown"] - elif mod == "ctrl": - return args["CtrlDown"] - elif mod == "alt": - return args["AltDown"] - -def setMod(args,mod,state): - """sets a specific modifier state in args""" - if mod == "shift": - args["ShiftDown"] = state - elif mod == "ctrl": - args["CtrlDown"] = state - elif mod == "alt": - args["AltDown"] = state +from draftguitools.gui_tool_utils import setMod # --------------------------------------------------------------------------- @@ -367,12 +252,8 @@ class DraftTool: # --------------------------------------------------------------------------- # Geometry constructors # --------------------------------------------------------------------------- -def redraw3DView(): - """redraw3DView(): forces a redraw of 3d view.""" - try: - FreeCADGui.ActiveDocument.ActiveView.redraw() - except AttributeError as err: - pass +from draftguitools.gui_tool_utils import redraw3DView + class Creator(DraftTool): """A generic Draft Creator Tool used by creation tools such as line or arc""" diff --git a/src/Mod/Draft/draftguitools/gui_tool_utils.py b/src/Mod/Draft/draftguitools/gui_tool_utils.py new file mode 100644 index 0000000000..c26435a37f --- /dev/null +++ b/src/Mod/Draft/draftguitools/gui_tool_utils.py @@ -0,0 +1,389 @@ +# *************************************************************************** +# * (c) 2009 Yorik van Havre * +# * (c) 2010 Ken Cline * +# * (c) 2020 Eliud Cabrera Castillo * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * FreeCAD is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides the utility functions for Draft Gui Commands. + +These functions are used by different command classes in the `DraftTools` +module. We assume that the graphical interface was already loaded +as they operate on selections and graphical properties. +""" +## @package gui_tool_utils +# \ingroup DRAFT +# \brief Provides the utility functions for Draft Gui Commands. + +import FreeCAD as App +import FreeCADGui as Gui +import draftutils.gui_utils as gui_utils +import draftutils.utils as utils +from draftutils.messages import _wrn + +# Set modifier keys from the parameter database +MODS = ["shift", "ctrl", "alt"] +MODCONSTRAIN = MODS[utils.get_param("modconstrain", 0)] +MODSNAP = MODS[utils.get_param("modsnap", 1)] +MODALT = MODS[utils.get_param("modalt", 2)] + + +def format_unit(exp, unit="mm"): + """Return a formatting string to set a number to the correct unit.""" + return App.Units.Quantity(exp, App.Units.Length).UserString + + +formatUnit = format_unit + + +def select_object(arg): + """Handle the selection of objects depending on buttons pressed. + + This is a scene event handler, to be called from the Draft tools + when they need to select an object. + :: + self.call = self.view.addEventCallback("SoEvent", select_object) + + Parameters + ---------- + arg: Coin event + The Coin event received from the 3D view. + + If it is of type Keyboard and the `ESCAPE` key, it runs the `finish` + method of the active command. + + If it is of type Mouse button and `BUTTON1` press, + it captures the position of the cursor (x, y) + and the object below that cursor to add it to the active selection; + then it runs the `proceed` method of the active command + to continue with the command's logic. + """ + if arg["Type"] == "SoKeyboardEvent": + if arg["Key"] == "ESCAPE": + App.activeDraftCommand.finish() + # TODO: this part raises a coin3D warning about scene traversal. + # It needs to be fixed. + elif arg["Type"] == "SoMouseButtonEvent": + if arg["State"] == "DOWN" and arg["Button"] == "BUTTON1": + cursor = arg["Position"] + snapped = gui_utils.get_3d_view().getObjectInfo((cursor[0], + cursor[1])) + if snapped: + obj = App.ActiveDocument.getObject(snapped['Object']) + Gui.Selection.addSelection(obj) + App.activeDraftCommand.component = snapped['Component'] + App.activeDraftCommand.proceed() + + +selectObject = select_object + + +def has_mod(args, mod): + """Check if args has a specific modifier. + + Parameters + ---------- + args: Coin event + The Coin event received from the 3D view. + + mod: str + A string indicating the modifier, either `'shift'`, `'ctrl'`, + or `'alt'`. + + Returns + ------- + bool + It returns `args["ShiftDown"]`, `args["CtrlDown"]`, + or `args["AltDown"]`, depending on the passed value of `mod`. + """ + if mod == "shift": + return args["ShiftDown"] + elif mod == "ctrl": + return args["CtrlDown"] + elif mod == "alt": + return args["AltDown"] + + +hasMod = has_mod + + +def set_mod(args, mod, state): + """Set a specific modifier state in args. + + Parameters + ---------- + args: Coin event + The Coin event received from the 3D view. + + mod: str + A string indicating the modifier, either `'shift'`, `'ctrl'`, + or `'alt'`. + + state: bool + The boolean value of `state` is assigned to `args["ShiftDown"]`, + `args["CtrlDown"]`, or `args["AltDown"]` + depending on `mod`. + """ + if mod == "shift": + args["ShiftDown"] = state + elif mod == "ctrl": + args["CtrlDown"] = state + elif mod == "alt": + args["AltDown"] = state + + +setMod = set_mod + + +def get_point(target, args, + mobile=False, sym=False, workingplane=True, noTracker=False): + """Return a constrained 3D point and its original point. + + It is used by the Draft tools. + + Parameters + ---------- + target: object (class) + The target object with a `node` attribute. If this is present, + return the last node, otherwise return `None`. + + In the Draft tools, `target` is essentially the same class + of the Gui command, that is, `self`. Therefore, this method + probably makes more sense as a class method. + + args: Coin event + The Coin event received from the 3D view. + + mobile: bool, optional + It defaults to `False`. + If it is `True` the constraining occurs from the location of + the mouse cursor when `Shift` is pressed; otherwise from the last + entered point. + + sym: bool, optional + It defaults to `False`. + If it is `True`, the x and y values always stay equal. + + workingplane: bool, optional + It defaults to `True`. + If it is `False`, the point won't be projected on the currently + active working plane. + + noTracker: bool, optional + It defaults to `False`. + If it is `True`, the tracking line will not be displayed. + + Returns + ------- + CoinPoint, Base::Vector3, str + It returns a tuple with some information. + + The first is the Coin point returned by `Snapper.snap` + or by the `ActiveView`; the second is that same point + turned into an `App.Vector`, + and the third is some information of the point + returned by the `Snapper` or by the `ActiveView`. + """ + ui = Gui.draftToolBar + + if target.node: + last = target.node[-1] + else: + last = None + + amod = hasMod(args, MODSNAP) + cmod = hasMod(args, MODCONSTRAIN) + point = None + + if hasattr(Gui, "Snapper"): + point = Gui.Snapper.snap(args["Position"], + lastpoint=last, + active=amod, + constrain=cmod, + noTracker=noTracker) + info = Gui.Snapper.snapInfo + mask = Gui.Snapper.affinity + if not point: + p = Gui.ActiveDocument.ActiveView.getCursorPos() + point = Gui.ActiveDocument.ActiveView.getPoint(p) + info = Gui.ActiveDocument.ActiveView.getObjectInfo(p) + mask = None + + ctrlPoint = App.Vector(point) + if target.node: + if target.featureName == "Rectangle": + ui.displayPoint(point, target.node[0], + plane=App.DraftWorkingPlane, mask=mask) + else: + ui.displayPoint(point, target.node[-1], + plane=App.DraftWorkingPlane, mask=mask) + else: + ui.displayPoint(point, plane=App.DraftWorkingPlane, mask=mask) + return point, ctrlPoint, info + + +getPoint = get_point + + +def set_working_plane_to_object_under_cursor(mouseEvent): + """Set the working plane to the object under the cursor. + + It tests for an object under the cursor. + If it is found, it checks whether a `'face'` or `'curve'` component + is selected in the object's `Shape`. + Then it tries to align the working plane to that face or curve. + + The working plane is only aligned to the face if + the working plane is not `'weak'`. + + Parameters + ---------- + mouseEvent: Coin event + Coin event with the mouse, that is, a click. + + Returns + ------- + None + If no object was found in the 3D view under the cursor. + Or if the working plane is not `weak`. + Or if there was an exception with aligning the working plane + to the component under the cursor. + + Coin info + The `getObjectInfo` of the object under the cursor. + """ + objectUnderCursor = gui_utils.get_3d_view().getObjectInfo(( + mouseEvent["Position"][0], + mouseEvent["Position"][1])) + + if not objectUnderCursor: + return None + + try: + # Get the component "face" or "curve" under the "Shape" + # of the selected object + componentUnderCursor = getattr( + App.ActiveDocument.getObject(objectUnderCursor['Object']).Shape, + objectUnderCursor["Component"]) + + if not App.DraftWorkingPlane.weak: + return None + + if "Face" in objectUnderCursor["Component"]: + App.DraftWorkingPlane.alignToFace(componentUnderCursor) + else: + App.DraftWorkingPlane.alignToCurve(componentUnderCursor) + App.DraftWorkingPlane.weak = True + return objectUnderCursor + except Exception: + pass + + return None + + +setWorkingPlaneToObjectUnderCursor = set_working_plane_to_object_under_cursor + + +def set_working_plane_to_selected_object(): + """Set the working plane to the selected object's face. + + The working plane is only aligned to the face if + the working plane is `'weak'`. + + Returns + ------- + None + If more than one object was selected. + Or if the selected object has many subelements. + Or if the single subelement is not a `'Face'`. + + App::DocumentObject + The single object that contains a single selected face. + """ + sel = Gui.Selection.getSelectionEx() + if len(sel) != 1: + return None + sel = sel[0] + if (sel.HasSubObjects + and len(sel.SubElementNames) == 1 + and "Face" in sel.SubElementNames[0]): + if App.DraftWorkingPlane.weak: + App.DraftWorkingPlane.alignToFace(sel.SubObjects[0]) + App.DraftWorkingPlane.weak = True + return sel.Object + return None + + +setWorkingPlaneToSelectedObject = set_working_plane_to_selected_object + + +def get_support(mouseEvent=None): + """Return the supporting object and set the working plane. + + It saves the current working plane, then sets it to the selected object. + + Parameters + ---------- + mouseEvent: Coin event, optional + It defaults to `None`. + Coin event with the mouse, that is, a click. + + If there is a mouse event it calls + `set_working_plane_to_object_under_cursor`. + Otherwise, it calls `set_working_plane_to_selected_object`. + + Returns + ------- + None + If there was a mouse event, but there was nothing under the cursor. + Or if the working plane is not `weak`. + Or if there was an exception with aligning the working plane + to the component under the cursor. + Or if more than one object was selected. + Or if the selected object has many subelements. + Or if the single subelement is not a `'Face'`. + + Coin info + If there was a mouse event, the `getObjectInfo` + of the object under the cursor. + + App::DocumentObject + If there was no mouse event, the single selected object + that contains the single selected face that was used + to align the working plane. + """ + App.DraftWorkingPlane.save() + if mouseEvent: + return setWorkingPlaneToObjectUnderCursor(mouseEvent) + return setWorkingPlaneToSelectedObject() + + +getSupport = get_support + + +def redraw_3d_view(): + """Force a redraw of 3D view or do nothing if it fails.""" + try: + Gui.ActiveDocument.ActiveView.redraw() + except AttributeError as err: + _wrn(err) + + +redraw3DView = redraw_3d_view From 9b3096353cb2b8dfbcccab06c9766c6ec8c7fac4 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sun, 29 Mar 2020 17:13:15 -0600 Subject: [PATCH 27/33] Draft: move the base DraftTool class to its own module This is the base class of most Draft Gui Commands, particularly those which were developed a long time ago. It is inherited by `Creator` and `Modifier` to set some specific options for some tools. Newer Gui Commands do not use these classes and thus are independent of `DraftTool`. Nevertheless, in the future we expect to organize further the commands so that they all derive from a few classes, so that they all share some common properties and behaviors. --- src/Mod/Draft/CMakeLists.txt | 1 + src/Mod/Draft/DraftTools.py | 108 +------- .../Draft/draftguitools/gui_base_original.py | 253 ++++++++++++++++++ 3 files changed, 255 insertions(+), 107 deletions(-) create mode 100644 src/Mod/Draft/draftguitools/gui_base_original.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 3186aebb05..900205eb74 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -86,6 +86,7 @@ SET(Draft_view_providers SET(Draft_GUI_tools draftguitools/__init__.py draftguitools/gui_base.py + draftguitools/gui_base_original.py draftguitools/gui_tool_utils.py draftguitools/gui_circulararray.py draftguitools/gui_orthoarray.py diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 8925928a89..c7f8812f18 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -138,116 +138,10 @@ from draftguitools.gui_tool_utils import hasMod from draftguitools.gui_tool_utils import setMod - # --------------------------------------------------------------------------- # Base Class # --------------------------------------------------------------------------- -class DraftTool: - """The base class of all Draft Tools""" - - def __init__(self): - self.commitList = [] - - def IsActive(self): - if FreeCADGui.ActiveDocument: - return True - else: - return False - - def Activated(self, name="None", noplanesetup=False, is_subtool=False): - if FreeCAD.activeDraftCommand and not is_subtool: - FreeCAD.activeDraftCommand.finish() - - global Part, DraftGeomUtils - import Part, DraftGeomUtils - - self.ui = None - self.call = None - self.support = None - self.point = None - self.commitList = [] - self.doc = FreeCAD.ActiveDocument - if not self.doc: - self.finish() - return - - FreeCAD.activeDraftCommand = self - self.view = Draft.get3DView() - self.ui = FreeCADGui.draftToolBar - self.featureName = name - self.ui.sourceCmd = self - self.ui.setTitle(name) - self.ui.show() - if not noplanesetup: - plane.setup() - self.node = [] - self.pos = [] - self.constrain = None - self.obj = None - self.extendedCopy = False - self.ui.setTitle(name) - self.planetrack = None - if Draft.getParam("showPlaneTracker",False): - self.planetrack = trackers.PlaneTracker() - if hasattr(FreeCADGui,"Snapper"): - FreeCADGui.Snapper.setTrackers() - - def finish(self,close=False): - self.node = [] - FreeCAD.activeDraftCommand = None - if self.ui: - self.ui.offUi() - self.ui.sourceCmd = None - if self.planetrack: - self.planetrack.finalize() - plane.restore() - if hasattr(FreeCADGui,"Snapper"): - FreeCADGui.Snapper.off() - if self.call: - try: - self.view.removeEventCallback("SoEvent",self.call) - except RuntimeError: - # the view has been deleted already - pass - self.call = None - if self.commitList: - ToDo.delayCommit(self.commitList) - self.commitList = [] - - def commit(self,name,func): - """stores actions to be committed to the FreeCAD document""" - self.commitList.append((name,func)) - - def getStrings(self,addrot=None): - """returns a couple of useful strings for building python commands""" - - # current plane rotation - p = plane.getRotation() - qr = p.Rotation.Q - qr = '('+str(qr[0])+','+str(qr[1])+','+str(qr[2])+','+str(qr[3])+')' - - # support object - if self.support and FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("useSupport",False): - sup = 'FreeCAD.ActiveDocument.getObject("' + self.support.Name + '")' - else: - sup = 'None' - - # contents of self.node - points='[' - for n in self.node: - if len(points) > 1: - points += ',' - points += DraftVecUtils.toString(n) - points += ']' - - # fill mode - if self.ui: - fil = str(bool(self.ui.fillmode)) - else: - fil = "True" - - return qr,sup,points,fil - +from draftguitools.gui_base_original import DraftTool # --------------------------------------------------------------------------- # Geometry constructors diff --git a/src/Mod/Draft/draftguitools/gui_base_original.py b/src/Mod/Draft/draftguitools/gui_base_original.py new file mode 100644 index 0000000000..77ffcde084 --- /dev/null +++ b/src/Mod/Draft/draftguitools/gui_base_original.py @@ -0,0 +1,253 @@ +# *************************************************************************** +# * (c) 2009 Yorik van Havre * +# * (c) 2010 Ken Cline * +# * (c) 2020 Eliud Cabrera Castillo * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * FreeCAD is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides the Base object for most old Draft Gui Commands. + +This class is used by Gui Commands to set up some properties +of the DraftToolBar, the Snapper, and the working plane. +""" +## @package gui_base_original +# \ingroup DRAFT +# \brief Provides the Base object for most old Draft Gui Commands. + +import FreeCAD as App +import FreeCADGui as Gui +import DraftVecUtils +import draftutils.utils as utils +import draftutils.gui_utils as gui_utils +import draftutils.todo as todo +import draftguitools.gui_trackers as trackers +from draftutils.messages import _msg, _log + + +class DraftTool: + """The base class of all Draft Tools. + + This is the original class that was defined in `DraftTools.py` + before any re-organization of the code. + It must be preserved exactly like this to keep the original tools + running without problems. + + This class is subclassed by `Creator` and `Modifier` + to set up a few additional properties of these two types. + + This class connects with the `draftToolBar` and `Snapper` classes + that are installed in the `FreeCADGui` namespace in order to set up some + properties of the running tools such as the task panel, the snapping + functions, and the grid trackers. + + It also connects with the `DraftWorkingPlane` class + that is installed in the `FreeCAD` namespace in order to set up + the working plane if it doesn't exist. + + This class is intended to be replaced by newer classes inside the + `gui_base` module, in particular, `GuiCommandBase`. + """ + + def __init__(self): + self.commitList = [] + + def IsActive(self): + """Return True when this command should be available. + + It is `True` when there is a document. + """ + if Gui.ActiveDocument: + return True + else: + return False + + def Activated(self, name="None", noplanesetup=False, is_subtool=False): + """Execute when the command is called. + + If an active Gui Command exists, it will call the `finish` method + of it to terminate it. + + If no active Gui Command exists, it will proceed with configuration + of the tool. The child class that subclasses this class + then should continue with its own Activated method. + + Parameters + ---------- + name: str, optional + It defaults to `'None'`. + It is the `featureName` of the object, to know what is being run. + + noplanesetup: bool, optional + It defaults to `False`. + If it is `False` it will set up the working plane + by running `App.DraftWorkingPlane.setup()`. + + is_subtool: bool, optional + It defaults to `False`. + This is set to `True` when we want to modify an object + by using the mechanism of a `subtool`, introduced + through the `Draft_SubelementHighlight` command. + That is, first we run `Draft_SubelementHighlight` + then we can use `Draft_Move` while setting `is_subtool` to `True`. + """ + if App.activeDraftCommand and not is_subtool: + App.activeDraftCommand.finish() + + # The Part module is first initialized when using any Gui Command + # for the first time. + global Part, DraftGeomUtils + import Part + import DraftGeomUtils + + self.ui = None + self.call = None + self.support = None + self.point = None + self.commitList = [] + self.doc = App.ActiveDocument + if not self.doc: + self.finish() + return + + App.activeDraftCommand = self + self.view = gui_utils.get_3d_view() + self.ui = Gui.draftToolBar + self.featureName = name + self.ui.sourceCmd = self + self.ui.setTitle(name) + self.ui.show() + if not noplanesetup: + App.DraftWorkingPlane.setup() + self.node = [] + self.pos = [] + self.constrain = None + self.obj = None + self.extendedCopy = False + self.ui.setTitle(name) + self.planetrack = None + if utils.get_param("showPlaneTracker", False): + self.planetrack = trackers.PlaneTracker() + if hasattr(Gui, "Snapper"): + Gui.Snapper.setTrackers() + + _log("GuiCommand: {}".format(self.featureName)) + _msg("{}".format(16*"-")) + _msg("GuiCommand: {}".format(self.featureName)) + + def finish(self, close=False): + """Finish the current command. + + These are general cleaning tasks that are performed + when terminating all commands. + + These include setting the node list to empty, + setting to `None` the active command, + turning off the graphical interface (task panel), + finishing the plane tracker, restoring the working plane, + turning off the snapper. + + If a callback is installed in the 3D view, the callback is removed, + and set to `None`. + + If the commit list is non-empty it will commit the instructions on + the list with `draftutils.todo.ToDo.delayCommit`, + and the list will be set to empty. + """ + self.node = [] + App.activeDraftCommand = None + if self.ui: + self.ui.offUi() + self.ui.sourceCmd = None + if self.planetrack: + self.planetrack.finalize() + App.DraftWorkingPlane.restore() + if hasattr(Gui, "Snapper"): + Gui.Snapper.off() + if self.call: + try: + self.view.removeEventCallback("SoEvent", self.call) + except RuntimeError: + # the view has been deleted already + pass + self.call = None + if self.commitList: + todo.ToDo.delayCommit(self.commitList) + self.commitList = [] + + def commit(self, name, func): + """Store actions in the commit list to be run later. + + Parameters + ---------- + name: str + An arbitraty string that indicates the name of the operation + to run. + + func: list of str + Each element of the list is a string that will be run by + `Gui.doCommand`. + + See the complete information in the `draftutils.todo.ToDo` class. + """ + self.commitList.append((name, func)) + + def getStrings(self, addrot=None): + """Return useful strings that will be used to build commands. + + Returns + ------- + str, str, str, str + A tuple of four strings that represent useful information. + + * the current working plane rotation quaternion as a string + * the support object if available as a string + * the list of nodes inside the `node` attribute as a string + * the string `'True'` or `'False'` depending on the fill mode + of the current tool + """ + # Current plane rotation as a string + p = App.DraftWorkingPlane.getRotation() + qr = p.Rotation.Q + qr = "({0}, {1}, {2}, {3})".format(qr[0], qr[1], qr[2], qr[3]) + + # Support object + _params = "User parameter:BaseApp/Preferences/Mod/Draft" + _params_group = App.ParamGet(_params) + if self.support and _params_group.GetBool("useSupport", False): + sup = 'FreeCAD.ActiveDocument.getObject' + sup += '("{}")'.format(self.support.Name) + else: + sup = 'None' + + # Contents of self.node + points = '[' + for n in self.node: + if len(points) > 1: + points += ', ' + points += DraftVecUtils.toString(n) + points += ']' + + # Fill mode + if self.ui: + fil = str(bool(self.ui.fillmode)) + else: + fil = "True" + + return qr, sup, points, fil From 181c5cb60d8e68c95a8189e73ae43e64cd7a7152 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sun, 29 Mar 2020 18:53:30 -0600 Subject: [PATCH 28/33] Draft: import Part module where necessary for the tools Also import `DraftGeomUtils` where necessary. These modules were previously imported globaly when the base `DraftTool` class was first called by the individual tools through the `Creator` and `Modifier` classes. However, since `DraftTool` was moved to a separate module, these `Part` and `DraftGeomUtils` modules are no longer in the `DraftTools` namespace, and therefore they cannot be imported globally anymore. Now the `Part` and `DraftGeomUtils` modules are imported in the specific functions where they are actually needed. --- src/Mod/Draft/DraftTools.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index c7f8812f18..18ab76699d 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -272,6 +272,7 @@ class Line(Creator): def undolast(self): """undoes last line segment""" + import Part if (len(self.node) > 1): self.node.pop() last = self.node[-1] @@ -288,6 +289,7 @@ class Line(Creator): def drawSegment(self,point): """draws a new segment""" + import Part if self.planetrack and self.node: self.planetrack.set(self.node[-1]) if (len(self.node) == 1): @@ -440,6 +442,7 @@ class BSpline(Line): def undolast(self): """undoes last line segment""" + import Part if (len(self.node) > 1): self.node.pop() self.bsplinetrack.update(self.node) @@ -449,6 +452,7 @@ class BSpline(Line): FreeCAD.Console.PrintMessage(translate("draft", "Last point has been removed")+"\n") def drawUpdate(self,point): + import Part if (len(self.node) == 1): self.bsplinetrack.on() if self.planetrack: @@ -560,6 +564,7 @@ class BezCurve(Line): def updateShape(self, pts): '''creates shape for display during creation process.''' + import Part edges = [] if len(pts) >= 2: #allow lower degree segment poles=pts[1:] @@ -714,7 +719,8 @@ class CubicBezCurve(Line): def updateShape(self, pts): '''creates shape for display during creation process.''' -# not quite right. draws 1 big bez. sb segmented + import Part + # not quite right. draws 1 big bez. sb segmented edges = [] if len(pts) >= 2: #allow lower degree segment @@ -957,6 +963,7 @@ class Arc(Creator): def action(self,arg): """scene event handler""" + import DraftGeomUtils if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() @@ -1172,6 +1179,7 @@ class Arc(Creator): FreeCAD.Console.PrintMessage(translate("draft", "Pick radius")+"\n") def numericRadius(self,rad): + import DraftGeomUtils """this function gets called by the toolbar when valid radius have been entered there""" if (self.step == 1): self.rad = rad @@ -1271,6 +1279,7 @@ class Polygon(Creator): def action(self,arg): """scene event handler""" + import DraftGeomUtils if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() @@ -1401,6 +1410,7 @@ class Polygon(Creator): def numericRadius(self,rad): """this function gets called by the toolbar when valid radius have been entered there""" + import DraftGeomUtils self.rad = rad if len(self.tangents) == 2: cir = DraftGeomUtils.circleFrom2tan1rad(self.tangents[0], self.tangents[1], rad) @@ -2686,6 +2696,7 @@ class Offset(Modifier): def action(self,arg): """scene event handler""" + import DraftGeomUtils if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() @@ -3297,6 +3308,7 @@ class Trimex(Modifier): self.linetrack = trackers.lineTracker() import DraftGeomUtils + import Part if not "Shape" in self.obj.PropertiesList: return if "Placement" in self.obj.PropertiesList: @@ -3415,6 +3427,7 @@ class Trimex(Modifier): if real: newedges = [] import DraftGeomUtils + import Part # finding the active point vlist = [] @@ -3529,6 +3542,7 @@ class Trimex(Modifier): def trimObject(self): """trims the actual object""" + import Part if self.extrudeMode: delta = self.extrude(self.shift,real=True) #print("delta",delta) @@ -3591,6 +3605,7 @@ class Trimex(Modifier): def trimObjects(self,objectslist): """attempts to trim two objects together""" import Part + import DraftGeomUtils wires = [] for obj in objectslist: if not Draft.getType(obj) in ["Wire","Circle"]: From 9f5e215751cd6b0e3adf549019a567a63eb6281e Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 30 Mar 2020 02:09:19 -0600 Subject: [PATCH 29/33] Draft: move Creator class to gui_base_original module --- src/Mod/Draft/DraftTools.py | 11 +----- .../Draft/draftguitools/gui_base_original.py | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 18ab76699d..69c88b5b67 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -148,17 +148,8 @@ from draftguitools.gui_base_original import DraftTool # --------------------------------------------------------------------------- from draftguitools.gui_tool_utils import redraw3DView +from draftguitools.gui_base_original import Creator -class Creator(DraftTool): - """A generic Draft Creator Tool used by creation tools such as line or arc""" - - def __init__(self): - DraftTool.__init__(self) - - def Activated(self,name="None",noplanesetup=False): - DraftTool.Activated(self,name,noplanesetup) - if not noplanesetup: - self.support = getSupport() class Line(Creator): """The Line FreeCAD command definition""" diff --git a/src/Mod/Draft/draftguitools/gui_base_original.py b/src/Mod/Draft/draftguitools/gui_base_original.py index 77ffcde084..c0bc6aa968 100644 --- a/src/Mod/Draft/draftguitools/gui_base_original.py +++ b/src/Mod/Draft/draftguitools/gui_base_original.py @@ -38,6 +38,7 @@ import draftutils.utils as utils import draftutils.gui_utils as gui_utils import draftutils.todo as todo import draftguitools.gui_trackers as trackers +import draftguitools.gui_tool_utils as gui_tool_utils from draftutils.messages import _msg, _log @@ -251,3 +252,36 @@ class DraftTool: fil = "True" return qr, sup, points, fil + + +class Creator(DraftTool): + """A generic Creator tool, used by creation tools such as line or arc. + + It runs the Activated method from the parent class. + If `noplanesetup` is `False`, it sets the appropriate `support` attribute + and sets the working plane with `gui_tool_utils.get_support`. + + It inherits `DraftTool`, which sets up the majority of the behavior + of this class. + """ + + def __init__(self): + super().__init__() + + def Activated(self, name="None", noplanesetup=False): + """Execute when the command is called. + + Parameters + ---------- + name: str, optional + It defaults to `'None'`. + It is the `featureName` of the object, to know what is being run. + + noplanesetup: bool, optional + It defaults to `False`. + If it is `False` it will set up the working plane + by running `App.DraftWorkingPlane.setup()`. + """ + super().Activated(name, noplanesetup) + if not noplanesetup: + self.support = gui_tool_utils.get_support() From 982a0fdd9c05420915640e2966696cefe78796ee Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 30 Mar 2020 02:36:20 -0600 Subject: [PATCH 30/33] Draft: move Modifier class to gui_base_original module --- src/Mod/Draft/DraftTools.py | 7 +------ src/Mod/Draft/draftguitools/gui_base_original.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 69c88b5b67..be8990d108 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -2105,13 +2105,8 @@ class ShapeString(Creator): #--------------------------------------------------------------------------- # Modifier functions #--------------------------------------------------------------------------- +from draftguitools.gui_base_original import Modifier -class Modifier(DraftTool): - """A generic Modifier Tool, used by modification tools such as move""" - - def __init__(self): - DraftTool.__init__(self) - self.copymode = False class Move(Modifier): """The Draft_Move FreeCAD command definition""" diff --git a/src/Mod/Draft/draftguitools/gui_base_original.py b/src/Mod/Draft/draftguitools/gui_base_original.py index c0bc6aa968..654eab44a5 100644 --- a/src/Mod/Draft/draftguitools/gui_base_original.py +++ b/src/Mod/Draft/draftguitools/gui_base_original.py @@ -285,3 +285,18 @@ class Creator(DraftTool): super().Activated(name, noplanesetup) if not noplanesetup: self.support = gui_tool_utils.get_support() + + +class Modifier(DraftTool): + """A generic Modifier tool, used by modification tools such as move. + + After initializing the parent class, it sets the `copymode` attribute + to `False`. + + It inherits `DraftTool`, which sets up the majority of the behavior + of this class. + """ + + def __init__(self): + super().__init__() + self.copymode = False From b8d7f8df57a0ebc9dc31dc779853fe9b36e951cf Mon Sep 17 00:00:00 2001 From: carlopav Date: Fri, 17 Apr 2020 18:31:39 +0200 Subject: [PATCH 31/33] [Draft] Fixed scale multiplier for text object --- src/Mod/Draft/draftviewproviders/view_text.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Mod/Draft/draftviewproviders/view_text.py b/src/Mod/Draft/draftviewproviders/view_text.py index e080996ce1..e442b1689e 100644 --- a/src/Mod/Draft/draftviewproviders/view_text.py +++ b/src/Mod/Draft/draftviewproviders/view_text.py @@ -54,10 +54,6 @@ class ViewProviderText(ViewProviderDraftAnnotation): def set_properties(self, vobj): - vobj.addProperty("App::PropertyFloat","ScaleMultiplier", - "Annotation",QT_TRANSLATE_NOOP("App::Property", - "Dimension size overall multiplier")) - vobj.addProperty("App::PropertyLength","FontSize", "Text",QT_TRANSLATE_NOOP("App::Property", "The size of the text")) From 6e712f601b7620cda491e5e478fc26d6a2d69067 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Tue, 21 Apr 2020 13:43:28 +0200 Subject: [PATCH 32/33] Arch: Added IFC import option to replace project,site and building with a group --- .../Resources/ui/preferences-ifc-export.ui | 15 ++------ src/Mod/Arch/Resources/ui/preferences-ifc.ui | 31 +++++++++------ src/Mod/Arch/importIFC.py | 38 ++++++++++++++----- 3 files changed, 51 insertions(+), 33 deletions(-) diff --git a/src/Mod/Arch/Resources/ui/preferences-ifc-export.ui b/src/Mod/Arch/Resources/ui/preferences-ifc-export.ui index 3d1df06a58..5706fac8f6 100644 --- a/src/Mod/Arch/Resources/ui/preferences-ifc-export.ui +++ b/src/Mod/Arch/Resources/ui/preferences-ifc-export.ui @@ -7,26 +7,17 @@ 0 0 463 - 421 + 466 - IFC-Export + IFC export 6 - - 9 - - - 9 - - - 9 - - + 9 diff --git a/src/Mod/Arch/Resources/ui/preferences-ifc.ui b/src/Mod/Arch/Resources/ui/preferences-ifc.ui index 089594c571..632689be18 100644 --- a/src/Mod/Arch/Resources/ui/preferences-ifc.ui +++ b/src/Mod/Arch/Resources/ui/preferences-ifc.ui @@ -7,26 +7,17 @@ 0 0 463 - 495 + 577 - IFC + IFC import 6 - - 9 - - - 9 - - - 9 - - + 9 @@ -394,6 +385,22 @@ FreeCAD object properties + + + + If this option is checked, the default Project, Site and Building objects that are usually found in an IFC file are not imported, and all objects are placed in a Group + + + Replace Project, Site and Buiding by Group + + + ifcReplaceProject + + + Mod/Arch + + + diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index bd6e343660..dbc09b5f2b 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -161,7 +161,8 @@ def getPreferences(): 'IMPORT_PROPERTIES': p.GetBool("ifcImportProperties",False), 'SPLIT_LAYERS': p.GetBool("ifcSplitLayers",False), 'FITVIEW_ONIMPORT': p.GetBool("ifcFitViewOnImport",False), - 'ALLOW_INVALID': p.GetBool("ifcAllowInvalid",False) + 'ALLOW_INVALID': p.GetBool("ifcAllowInvalid",False), + 'REPLACE_PROJECT': p.GetBool("ifcReplaceProject",False) } if preferences['MERGE_MODE_ARCH'] > 0: @@ -299,23 +300,24 @@ def insert(filename,docname,skip=[],only=[],root=None,preferences=None): FreeCADGui.ActiveDocument.activeView().viewAxonometric() # Create the base project object - if len(ifcfile.by_type("IfcProject")) > 0: - projectImporter = importIFCHelper.ProjectImporter(ifcfile, objects) - projectImporter.execute() - else: - # https://forum.freecadweb.org/viewtopic.php?f=39&t=40624 - print("No IfcProject found in the ifc file. Nothing imported") - return doc + if not preferences['REPLACE_PROJECT']: + if len(ifcfile.by_type("IfcProject")) > 0: + projectImporter = importIFCHelper.ProjectImporter(ifcfile, objects) + projectImporter.execute() + else: + # https://forum.freecadweb.org/viewtopic.php?f=39&t=40624 + print("No IfcProject found in the ifc file. Nothing imported") + return doc # handle IFC products for product in products: count += 1 - pid = product.id() guid = product.GlobalId ptype = product.is_a() + if preferences['DEBUG']: print(count,"/",len(products),"object #"+str(pid),":",ptype,end="") # build list of related property sets @@ -382,6 +384,15 @@ def insert(filename,docname,skip=[],only=[],root=None,preferences=None): if ptype in preferences['SKIP']: # preferences-set type skip list if preferences['DEBUG']: print(" skipped.") continue + if preferences['REPLACE_PROJECT']: # options-enabled project/site/building skip + if ptype in ['IfcProject','IfcSite']: + if preferences['DEBUG']: print(" skipped.") + continue + elif ptype in ['IfcBuilding']: + if len(ifcfile.by_type("IfcBuilding")) == 1: + # let multiple buildings through... + if preferences['DEBUG']: print(" skipped.") + continue # check if this object is sharing its shape (mapped representation) clone = None @@ -1220,6 +1231,15 @@ def insert(filename,docname,skip=[],only=[],root=None,preferences=None): if l: setattr(p[0],p[1],l) + # Grouping everything if required + if preferences['REPLACE_PROJECT']: + rootgroup = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup","Group") + rootgroup.Label = os.path.basename(filename) + for key,obj in objects.items(): + # only add top-level objects + if not obj.InList: + rootgroup.addObject(obj) + FreeCAD.ActiveDocument.recompute() if ZOOMOUT and FreeCAD.GuiUp: From 71414fca965aa21882158c2f25f5cc6eb542814f Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Tue, 21 Apr 2020 14:46:07 +0200 Subject: [PATCH 33/33] Arch: Also skip default storey when importing IFC with 'replace by group' option --- src/Mod/Arch/Resources/ui/preferences-ifc.ui | 4 ++-- src/Mod/Arch/importIFC.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Mod/Arch/Resources/ui/preferences-ifc.ui b/src/Mod/Arch/Resources/ui/preferences-ifc.ui index 632689be18..e11ea0d3c8 100644 --- a/src/Mod/Arch/Resources/ui/preferences-ifc.ui +++ b/src/Mod/Arch/Resources/ui/preferences-ifc.ui @@ -388,10 +388,10 @@ FreeCAD object properties - If this option is checked, the default Project, Site and Building objects that are usually found in an IFC file are not imported, and all objects are placed in a Group + If this option is checked, the default Project, Site, Building and Storeys objects that are usually found in an IFC file are not imported, and all objects are placed in a Group. Buildins ans storeys are still imported if there is more than one. - Replace Project, Site and Buiding by Group + Replace Project, Site, Buiding and Storey by Group ifcReplaceProject diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index dbc09b5f2b..299a08ee28 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -393,6 +393,11 @@ def insert(filename,docname,skip=[],only=[],root=None,preferences=None): # let multiple buildings through... if preferences['DEBUG']: print(" skipped.") continue + elif ptype in ['IfcBuildingStorey']: + if len(ifcfile.by_type("IfcBuildingStorey")) == 1: + # let multiple storeys through... + if preferences['DEBUG']: print(" skipped.") + continue # check if this object is sharing its shape (mapped representation) clone = None