diff --git a/src/Mod/PartDesign/CMakeLists.txt b/src/Mod/PartDesign/CMakeLists.txt index 432616c7a..a189626c9 100644 --- a/src/Mod/PartDesign/CMakeLists.txt +++ b/src/Mod/PartDesign/CMakeLists.txt @@ -29,3 +29,28 @@ INSTALL( Mod/PartDesign/Scripts ) +SET(WizardShaft_SRCS + WizardShaft/__init__.py + WizardShaft/WizardShaft.py + WizardShaft/WizardShaftTable.py + WizardShaft/Shaft.py + WizardShaft/ShaftFeature.py + WizardShaft/ShaftDiagram.py + WizardShaft/SegmentFunction.py +) +SOURCE_GROUP("wizardshaft" FILES ${WizardShaft_SRCS}) + +SET(all_files ${WizardShaft_SRCS}) + +ADD_CUSTOM_TARGET(WizardShaft ALL + SOURCES ${all_files} +) + +fc_copy_sources(Mod/PartDesign "${CMAKE_BINARY_DIR}/Mod/PartDesign" ${all_files}) + +INSTALL( + FILES + ${WizardShaft_SRCS} + DESTINATION + Mod/PartDesign/WizardShaft +) diff --git a/src/Mod/PartDesign/InitGui.py b/src/Mod/PartDesign/InitGui.py index 1097233fa..1afa2de53 100644 --- a/src/Mod/PartDesign/InitGui.py +++ b/src/Mod/PartDesign/InitGui.py @@ -1,4 +1,4 @@ -# PartDesign gui init module +# PartDesign gui init module # (c) 2003 Juergen Riegel # # Gathering all the information to start FreeCAD @@ -29,11 +29,10 @@ #* Juergen Riegel 2002 * #***************************************************************************/ - - class PartDesignWorkbench ( Workbench ): - "PartDesign workbench object" - Icon = """ + "PartDesign workbench object" + from WizardShaft import WizardShaft + Icon = """ /* XPM */ static char * partdesign_xpm[] = { "16 16 9 1", @@ -62,15 +61,15 @@ class PartDesignWorkbench ( Workbench ): ".+@@@####@.@.. ", " ......+++.. ", " ... "}; - """ + """ MenuText = "Part Design" ToolTip = "Part Design workbench" - def Initialize(self): - # load the module - import PartDesignGui - import PartDesign - def GetClassName(self): - return "PartDesignGui::Workbench" + def Initialize(self): + # load the module + import PartDesignGui + import PartDesign + def GetClassName(self): + return "PartDesignGui::Workbench" Gui.addWorkbench(PartDesignWorkbench()) diff --git a/src/Mod/PartDesign/WizardShaft/InitGui.py b/src/Mod/PartDesign/WizardShaft/InitGui.py new file mode 100644 index 000000000..73e570ce2 --- /dev/null +++ b/src/Mod/PartDesign/WizardShaft/InitGui.py @@ -0,0 +1,76 @@ +# PartDesign gui init module +# (c) 2003 Juergen Riegel +# +# Gathering all the information to start FreeCAD +# This is the second one of three init scripts, the third one +# runs when the gui is up + +#*************************************************************************** +#* (c) Juergen Riegel (juergen.riegel@web.de) 2002 * +#* * +#* 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 General Public License (GPL) * +#* 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 * +#* * +#* Juergen Riegel 2002 * +#***************************************************************************/ + +class PartDesignWorkbench ( Workbench ): + "PartDesign workbench object" + from WizardShaft import WizardShaft + Icon = """ + /* XPM */ + static char * partdesign_xpm[] = { + "16 16 9 1", + " c None", + ". c #040006", + "+ c #070F38", + "@ c #002196", + "# c #0030F3", + "$ c #5A4D20", + "% c #858EB2", + "& c #DEB715", + "* c #BFB99D", + " & ........ ", + "&&&$..@@@@@@+...", + "&&&&$@#####@..@.", + "&&&&&$......@#@.", + "&&&&&&@@@+.###@.", + "$&&&&&&@#@.###@.", + ".$&&&&&%#@.###@.", + ".@*&&&*%#@.###@.", + ".@#*&**%#@.###@.", + ".@#@%%%.@@.###@.", + ".@@@@@@@#@.###@.", + ".@#######@.###@.", + ".@#######@.##+. ", + ".+@@@####@.@.. ", + " ......+++.. ", + " ... "}; + """ + MenuText = "Part Design" + ToolTip = "Part Design workbench" + + def Initialize(self): + # load the module + import PartDesignGui + import PartDesign + def GetClassName(self): + return "PartDesignGui::Workbench" + +Gui.addWorkbench(PartDesignWorkbench()) + diff --git a/src/Mod/PartDesign/WizardShaft/SegmentFunction.py b/src/Mod/PartDesign/WizardShaft/SegmentFunction.py new file mode 100644 index 000000000..db50120d3 --- /dev/null +++ b/src/Mod/PartDesign/WizardShaft/SegmentFunction.py @@ -0,0 +1,152 @@ +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +class SegmentFunctionSegment: + "One segment of a segment function" + start = 0 + variable = "x" + coefficient = 0 + exponent = 0 + + def __init__(self, st, var, coeff, exp): + self.start = st + self.variable = var + self.coefficient = coeff + self.exponent = exp + + def hasStart(self, xval): + "Return true if the start of this segment is xval" + #FIXME: 1E-9 is arbitrary here. But since units are in meters, 1E-9 is a nanometer... + return abs(self.start - xval) < 1E-9 + + def value(self, xval): + if xval < self.start: + return 0 + else: + return self.coefficient * pow(xval - self.start, self.exponent) + + def clone(self): + return SegmentFunctionSegment(self.start, self.variable, self.coefficient, self.exponent) + + def negate(self): + self.coefficient *= -1 + + def integrate(self): + self.exponent = self.exponent + 1 + self.coefficient = self.coefficient * 1 / self.exponent + + def asString(self): + return "%f * {%s - %f}^%i" % (self.coefficient, self.variable, self.start, self.exponent) + +class SegmentFunction: + "Function that is defined segment-wise" + variable = "x" + segments = [] + name = "f(x)" + + def __init__(self, name = "f(x)"): + self.variable = "x" + self.segments = [] + self.name = name + + def negate(self): + for s in self.segments: + s.negate() + return self + + def index(self, xval): + "Find insert position for start value xval" + lastStart = 0.0 + for i in range(len(self.segments)): + newStart = self.segments[i].start + if (xval >= lastStart) and (xval < newStart): + return i + lastStart = newStart + return len(self.segments) + + def buildFromDict(self, var, dict): + self.variable = var + for key in sorted(dict.iterkeys()): + #if abs(dict[key]) > 1E-9: + self.segments.append(SegmentFunctionSegment(key, var, dict[key], 0)) + + def addSegments(self, dict): + for key in sorted(dict.iterkeys()): + if abs(dict[key]) > 1E-9: + self.segments.insert(self.index(key), SegmentFunctionSegment(key, self.variable, dict[key], 0)) + + def setMaxX(self, mx): + self.maxX = mx + + def value(self, xval): + "Return the value of the function at the specified x value" + result = 0 + for s in self.segments: + result = result + s.value(xval) + return result + + def lowervalue(self, xval): + "Return the value of the previous segment at the specified x value" + result = 0 + for s in self.segments: + result = result + s.value(xval - 1E-8) + return result + + def clone(self): + result = SegmentFunction() + result.variable = self.variable + for s in self.segments: + result.segments.append(s.clone()) + return result + + def integrate(self): + "Integrate all segments with respect to the variable" + for s in self.segments: + s.integrate() + + def integrated(self): + "Return a copy of self integrated with respect to the variable" + result = self.clone() + result.integrate() + return result + + def evaluate(self, maxX, pointsX): + # Note: This usually creates a few more points than specified in pointsX + offset = (maxX - self.segments[0].start) / (pointsX - 1) + xvals = set([self.segments[0].start + s * offset for s in range(pointsX)]) + starts = set([self.segments[i].start for i in range(len(self.segments))]) + xvals = xvals.union(starts) # Make sure we have a point on each segment start + result = [] + for xval in sorted(xvals): + if xval in starts: + result.append( (xval, self.lowervalue(xval)) ) # create double point at segment border + result.append( (xval, self.value(xval)) ) + return result + + def output(self): + print self.name, " = ", + for i in range(len(self.segments)): + print self.segments[i].asString(), + if i < len(self.segments) - 1: + print " + ", + print "" + diff --git a/src/Mod/PartDesign/WizardShaft/Shaft.py b/src/Mod/PartDesign/WizardShaft/Shaft.py new file mode 100644 index 000000000..4d10a227e --- /dev/null +++ b/src/Mod/PartDesign/WizardShaft/Shaft.py @@ -0,0 +1,258 @@ +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +import os, tempfile +import FreeCADGui +import WebGui +from SegmentFunction import SegmentFunction +from ShaftFeature import ShaftFeature +from ShaftDiagram import Diagram + +htmlHeader = """ + + + + + + + + +

+""" +htmlFooter = """ +

+ + +""" + +class ShaftSegment: + length = 0.0 + diameter = 0.0 + loadType = "None" + loadSize = 0.0 + loadLocation = 0.0 + + def __init__(self, l, d): + self.length = l + self.diameter = d + +class Shaft: + "The axis of the shaft is always assumed to correspond to the X-axis" + # List of shaft segments (each segment has a different diameter) + segments = [] + # The sketch + sketch = 0 + #featureWindow = None + # The diagrams + diagrams = {} # map of function name against Diagram object + # Directory for diagram files + tmpdir = "" + # HTML browser + haveBrowser = False + #htmlWindow = None + # Calculation of shaft + Qy = 0 # force in direction of y axis + Qz = 0 # force in direction of z axis + Mbz = 0 # bending moment around z axis + Mby = 0 # bending moment around y axis + Mtz = 0 # torsion moment around z axis + + def __init__(self, doc): + # Create a temporary directory + self.tmpdir = tempfile.mkdtemp() + self.sketch = ShaftFeature(doc) + + def getLengthTo(self, index): + "Get the total length of all segments up to the given one" + result = 0.0 + for i in range(index): + result += self.segments[i].length + return result + + def addSegment(self, l, d): + #print "Adding segment: ", l, " : ", d + self.segments.append(ShaftSegment(l,d)) + self.sketch.addSegment(l, d) + # We don't call equilibrium() here because the new segment has no loads defined yet + + def updateSegment(self, index, length = None, diameter = None): + oldLength = self.segments[index].length + #print "Old length of ", index, ": ", oldLength, ", new Length: ", length, " diameter: ", diameter + if length is not None: + self.segments[index].length = length + if diameter is not None: + self.segments[index].diameter = diameter + self.sketch.updateSegment(index, oldLength, self.segments[index].length, self.segments[index].diameter) + self.equilibrium() + self.updateDiagrams() + + def updateLoad(self, index, loadType = None, loadSize = None, loadLocation = None): + if (loadType is not None): + self.segments[index].loadType = loadType + if (loadSize is not None): + self.segments[index].loadSize = loadSize + if (loadLocation is not None): + if (loadLocation >= 0) and (loadLocation <= self.segments[index].length): + self.segments[index].loadLocation = loadLocation + else: + # TODO: Show warning + print "Load location must be inside segment" + + #self.feature.updateForces() graphical representation of the forces + self.equilibrium() + self.updateDiagrams() + + def updateDiagrams(self): + if (self.Qy == 0) or (self.Mbz == 0): + return + if self.Qy.name in self.diagrams: + # Update diagram + self.diagrams[self.Qy.name].update(self.Qy, self.getLengthTo(len(self.segments)) / 1000.0) + else: + # Create diagram + self.diagrams[self.Qy.name] = Diagram(self.tmpdir) + self.diagrams[self.Qy.name].create("Shear force", self.Qy, self.getLengthTo(len(self.segments)) / 1000.0, "x", "mm", 1000.0, "Q_y", "N", 1.0, 10) + if self.Mbz.name in self.diagrams: + # Update diagram + self.diagrams[self.Mbz.name].update(self.Mbz, self.getLengthTo(len(self.segments)) / 1000.0) + else: + # Create diagram + self.diagrams[self.Mbz.name] = Diagram(self.tmpdir) + self.diagrams[self.Mbz.name].create("Bending moment", self.Mbz, self.getLengthTo(len(self.segments)) / 1000.0, "x", "mm", 1000.0, "M_{b,z}", "Nm", 1.0, 10) + + if self.haveBrowser is False: + # Create HTML file with the diagrams + htmlFile = os.path.join(self.tmpdir, "diagrams.html") + file = open(htmlFile, "w") + file.write(htmlHeader) + for fname in self.diagrams.iterkeys(): + plotFile = os.path.join(self.tmpdir, (fname + ".png")) + file.write("\n" % (plotFile, fname)) + file.write(htmlFooter) + file.close() + WebGui.openBrowser(htmlFile) + self.haveBrowser = True + # Once the diagram is created, it will take care of refreshing the HTML view + + def equilibrium(self): + # Build equilibrium equations + forces = {0.0:0.0} # dictionary of (location : outer force) + moments = {0.0:0.0} # dictionary of (location : outer moment) + variableNames = [""] # names of all variables + locations = {} # dictionary of (variableName : location) + coefficientsFy = [0] # force equilibrium equation + coefficientsMbz = [0] # moment equilibrium equation + + for i in range(len(self.segments)): + lType = self.segments[i].loadType + load = -1 # -1 means unknown (just for debug printing) + location = -1 + + if lType == "Fixed": + # Fixed segment + if i == 0: + location = 0 + variableNames.append("Fy%u" % i) + coefficientsFy.append(1) + coefficientsMbz.append(0) + variableNames.append("Mz%u" % i) + coefficientsFy.append(0) + coefficientsMbz.append(1) # Force does not contribute because location is zero + elif i == len(self.segments) - 1: + location = self.getLengthTo(len(self.segments)) / 1000 + variableNames.append("Fy%u" % i) + coefficientsFy.append(1) + coefficientsMbz.append(location) + variableNames.append("Mz%u" % i) + coefficientsFy.append(0) + coefficientsMbz.append(1) + else: + # TODO: Better error message + print "Fixed constraint must be at beginning or end of shaft" + return + + locations["Fy%u" % i] = location + locations["Mz%u" % i] = location + elif lType == "Static": + # Static load (currently force only) + load = self.segments[i].loadSize + location = (self.getLengthTo(i) + self.segments[i].loadLocation) / 1000 # convert to meters + coefficientsFy[0] = coefficientsFy[0] - load + forces[location] = load + coefficientsMbz[0] = coefficientsMbz[0] - load * location + moments[location] = 0 + #elif lType == "None": + # # No loads on segment + + print "Segment: %u, type: %s, load: %f, location: %f" % (i, lType, load, location) + + self.printEquilibrium(variableNames, coefficientsFy) + self.printEquilibrium(variableNames, coefficientsMbz) + + # Build matrix and vector for linear algebra solving algorithm + import numpy as np + if (len(coefficientsFy) < 3) or (len(coefficientsMbz) < 3): + return + A = np.array([coefficientsFy[1:], coefficientsMbz[1:]]) + b = np.array([coefficientsFy[0], coefficientsMbz[0]]) + solution = np.linalg.solve(A, b) + + # Complete dictionary of forces and moments + if variableNames[1][0] == "F": + forces[locations[variableNames[1]]] = solution[0] + else: + moments[locations[variableNames[1]]] = solution[0] + + if variableNames[2][0] == "F": + forces[locations[variableNames[2]]] = solution[1] + else: + moments[locations[variableNames[2]]] = solution[1] + + print forces + print moments + self.Qy = SegmentFunction("Qy") + self.Qy.buildFromDict("x", forces) + self.Qy.output() + self.Mbz = self.Qy.integrated().negate() + self.Mbz.addSegments(moments) # takes care of boundary conditions + self.Mbz.name = "Mbz" + self.Mbz.output() + + def printEquilibrium(self, var, coeff): + # Auxiliary method for debugging purposes + for i in range(len(var)): + if i == 0: + print "%f = " % coeff[i], + else: + print "%f * %s" % (coeff[i], var[i]), + if (i < len(var) - 1) and (i != 0): + print " + ", + print "" + + def __del__(self): + "Remove the temporary directory" + for fname in self.diagrams.iterkeys(): + os.remove(os.path.join(self.tmpdir, (fname + ".dat"))) + os.remove(os.path.join(self.tmpdir, (fname + ".pyxplot"))) + os.remove(os.path.join(self.tmpdir, (fname + ".png"))) + os.remove(os.path.join(self.tmpdir, "diagrams.html")) + os.rmdir(self.tmpdir) diff --git a/src/Mod/PartDesign/WizardShaft/ShaftDiagram.py b/src/Mod/PartDesign/WizardShaft/ShaftDiagram.py new file mode 100644 index 000000000..715453d8a --- /dev/null +++ b/src/Mod/PartDesign/WizardShaft/ShaftDiagram.py @@ -0,0 +1,192 @@ +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +import os, subprocess +from PyQt4 import QtCore, QtGui +import FreeCAD, FreeCADGui + +header = " ### FREECAD SHAFT WIZARD ###\n #\n" + +class Diagram: + function = 0 # This is assumed to be always a SegmentFunction + fname = "y(x)" + xlength = 0.0 + xname = "x" + xunit = "" + xscale = 1.0 + yname = "y" + yunit = "" + yscale = 1.0 + numxpoints = 10 + points = [] + # process object of pyxplot running in the background + pyxplot = None + # Filesystem watcher + graphicsFile = "" + timer = 0 + watcher = 0 + updatePending = False + + def __init__(self, tmpdir): + self.tmpdir = tmpdir + # Set up watcher and timer + self.watcher = QtCore.QFileSystemWatcher() + self.watcher.fileChanged.connect(self.updateFinished) + self.timer = QtCore.QTimer() + self.timer.setInterval(100) + self.timer.timeout.connect(self.pollFile) + + def create(self, title, function, xlength, xname, xunit, xscale, yname, yunit, yscale, numxpoints): + # Initialize + self.title = title + self.function = function + self.xlength = xlength + self.xname = xname + self.xunit = xunit + self.xscale = xscale + self.yname = yname + self.yunit = yunit + self.yscale = yscale + self.numxpoints = numxpoints + self.graphicsFile = os.path.join(self.tmpdir, self.function.name + '.png') + + # Calculate points + self.points = self.function.evaluate(self.xlength, self.numxpoints) + # Write files + self.write() + + def update(self, function = None, xlength = None): + if function is not None: + self.function = function + if xlength is not None: + self.xlength = xlength + # Calculate points + self.points = self.function.evaluate(self.xlength, self.numxpoints) + # Write files + self.write() + + def write(self): + "Write diagram files" + # Check if pyxplot is still running in the background + if (self.pyxplot is not None) and (self.pyxplot.poll() is None): + # Process hasn't terminated yet, set flag to update again + # as soon as the graphicsFile has been written by the current pyxplot process + self.updatePending = True + return + + # Get max and min values + (xmin, xmax) = self.minmaxX() + (ymin, ymax) = self.minmaxY() + + # Create data file + dataFile = os.path.join(self.tmpdir, (self.function.name + ".dat")) + file = open(dataFile, "w") + file.write(header) + file.write(" # File automatically exported by FreeCAD Shaft Wizard\n") + file.write(" # This file contains xy data, filled with following columns:\n") + file.write(" # 1: %s [%s]\n" % (self.xname, self.xunit)) + file.write(" # 2: %s [%s]\n" % (self.yname, self.yunit)) + file.write(" #\n") + for (x, y) in self.points: + file.write("%f %f\n" % (x * self.xscale, y * self.yscale)) + file.close() + + # Create pyxplot file + commandFile = os.path.join(self.tmpdir, (self.function.name + ".pyxplot")) + file = open(commandFile, "w") + file.write(header) + file.write(" # File automatically exported by FreeCAD Shaft Wizard\n") + file.write(" # This file contains a script to plot xy data.\n") + file.write(" # To use it execute:\n") + file.write(" #\n") + file.write(" # pyxplot %s\n" % (commandFile)) + file.write(" #\n") + file.write(" #################################################################\n") + # Write general options + file.write("set numeric display latex\n") + file.write("set terminal png\n") + file.write("set output '%s'\n" % (self.graphicsFile)) + file.write("set nokey\n") + file.write("set grid\n") + file.write("# X axis\n") + file.write("set xlabel '$%s$ [%s]'\n" % (self.xname, self.xunit)) + file.write("set xrange [%f:%f]\n" % ((xmin * self.xscale * 1.05, xmax * self.xscale * 1.05))) + file.write("set xtic\n") + file.write("# Y axis\n") + file.write("set ylabel '$%s$ [%s]'\n" % (self.yname, self.yunit)) + file.write("set yrange [%f:%f]\n" % ((ymin * self.yscale * 1.05, ymax * self.yscale * 1.05))) + file.write("set ytic\n") + file.write("# Line styles\n") + file.write("set style 1 line linetype 1 linewidth 2 colour rgb (0):(0):(0)\n") + file.write("# Title\n") + file.write("set title '%s'" % self.title) + # Write plot call + file.write("# Plot\n") + file.write("plot '%s' using 1:2 axes x1y1 with lines style 1\n" % (dataFile)) + # Close file + file.close() + + # Run pyxplot on the files, but don't wait for execution to finish + # Instead, set timer to poll for the process to finish + try: + self.pyxplot = subprocess.Popen(["pyxplot", commandFile]) + # Poll the process to add a watcher as soon as it has created the graphics file + if len(self.watcher.files()) == 0: + self.timer.start() + except OSError: + FreeCAD.Console.PrintError("Can't execute pyxplot. Maybe it is not installed?\n") + + self.updatePending = False + + def pollFile(self): + # Check if process has finished + if self.pyxplot.poll() is None: + return + # Check if the graphics file has appeared and then set a watcher on it + if not os.path.isfile(self.graphicsFile): + return + # We don't need the timer any more now + self.timer.stop() + FreeCADGui.SendMsgToActiveView('Refresh') + self.watcher.addPath(self.graphicsFile) + + def updateFinished(self, filePath): + # filePath is not important because we are only watching a single file + # FIXME: Will give a warning in the console if the active window is not the HTML page + FreeCADGui.SendMsgToActiveView('Refresh') + if self.updatePending is True: + self.write() + + def minmaxX(self): + xpoints = [] + for (x, y) in self.points: + xpoints.append(x) + return (min(xpoints), max(xpoints)) + + def minmaxY(self): + ypoints = [] + for (x, y) in self.points: + ypoints.append(y) + return (min(ypoints), max(ypoints)) + + + diff --git a/src/Mod/PartDesign/WizardShaft/ShaftFeature.py b/src/Mod/PartDesign/WizardShaft/ShaftFeature.py new file mode 100644 index 000000000..beac8705e --- /dev/null +++ b/src/Mod/PartDesign/WizardShaft/ShaftFeature.py @@ -0,0 +1,133 @@ +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +import FreeCAD, FreeCADGui +import Part, Sketcher + +class ShaftFeature: + "Creates and updates the feature of the shaft" + Doc = 0 + App = FreeCAD + Gui = FreeCADGui + sketch = 0 + feature = 0 + segments = 0 # number of segments + totalLength = 0 # total length of all segments + lastRadius = 0 # radius of last segment (required for adding segments) + + def __init__(self, doc): + "Create new feature" + self.Doc = doc + + # TODO: Discover existing sketch and get data from it + self.sketch = self.Doc.addObject("Sketcher::SketchObject","SketchShaft") + self.sketch.Placement = self.App.Placement(self.App.Vector(0,0,0),self.App.Rotation(0,0,0,1)) + + def addSegment(self, length, diameter): + "Add a segment at the end of the shaft" + # Find constraint indices of vertical line constraint, horizontal line constraint + # FIXME: Should have a unique id instead of indices that might change with user editing + # 0-3 belong to the centerline + # 4-6 to the first vertical section + # 7-9 to the first horizontal section + # 10-12 to the second vertical section + # etc. etc. + constrRadius = 4 + self.segments * 6 + constrLength = 7 + self.segments * 6 + # Find line index of vertical segment, horizontal segment, last shaft segment + # FIXME: Should have a unique id instead of indices that might change with user editing + segRadius = 1 + self.segments * 2 + segLength = 2 + self.segments * 2 + prevSegLength = 0 + self.segments * 2 + prevSegEnd = 3 + (self.segments - 1) * 2 + segEnd = prevSegEnd + 2 + + radius = diameter / 2 + oldLength = self.totalLength + self.totalLength += length + self.segments += 1 + + if oldLength == 0: + # First segment of shaft + # Create centerline + self.sketch.addGeometry(Part.Line(self.App.Vector(0,0,0), self.App.Vector(self.totalLength,0,0))) + self.sketch.addConstraint(Sketcher.Constraint('DistanceX',0, self.totalLength)) # Constraint1 + self.sketch.addConstraint(Sketcher.Constraint('PointOnObject',0,1,-1)) # Constraint2 + self.sketch.addConstraint(Sketcher.Constraint('PointOnObject',0,1,-2)) # Constraint3 + self.sketch.addConstraint(Sketcher.Constraint('Horizontal', 0)) # Constraint4 + # Create first segment + self.sketch.addGeometry(Part.Line(self.App.Vector(0,0,0), self.App.Vector(0,radius,0))) + self.sketch.addConstraint(Sketcher.Constraint('DistanceY',1,radius)) # Constraint5 + self.sketch.addConstraint(Sketcher.Constraint('Coincident',0,1,1,1)) # Constraint6 + self.sketch.addConstraint(Sketcher.Constraint('Vertical',1)) # Constraint7 + self.sketch.addGeometry(Part.Line(self.App.Vector(0,radius,0), self.App.Vector(length,radius,0))) + self.sketch.addConstraint(Sketcher.Constraint('DistanceX',2,length)) # Constraint8 + self.sketch.addConstraint(Sketcher.Constraint('Coincident',2,1,1,2)) # Constraint9 + self.sketch.addConstraint(Sketcher.Constraint('Horizontal',2)) # Constraint10 + else: + # remove line that closes the shaft + self.sketch.delGeometry(prevSegEnd) + # TODO: Delete the two constraints? Or will they disappear automatically? + # Adjust length of centerline + self.sketch.setDatum(0,self.totalLength) + # Add segment at the end + self.sketch.addGeometry(Part.Line(self.App.Vector(oldLength,self.lastRadius,0), self.App.Vector(oldLength,radius,0))) + self.sketch.addConstraint(Sketcher.Constraint('DistanceY', 0, 1, segRadius, 2, radius)) + self.sketch.addConstraint(Sketcher.Constraint('Coincident',segRadius,1,prevSegLength,2)) + self.sketch.addConstraint(Sketcher.Constraint('Vertical',segRadius)) + self.sketch.addGeometry(Part.Line(self.App.Vector(oldLength,radius,0), self.App.Vector(oldLength+length,radius,0))) + self.sketch.addConstraint(Sketcher.Constraint('DistanceX',segLength,length)) + self.sketch.addConstraint(Sketcher.Constraint('Coincident',segLength,1,segRadius,2)) + self.sketch.addConstraint(Sketcher.Constraint('Horizontal',segLength)) + + # close the sketch + self.sketch.addGeometry(Part.Line(self.App.Vector(oldLength+length,radius,0), self.App.Vector(oldLength+length,0,0))) + self.sketch.addConstraint(Sketcher.Constraint('Coincident',0,2,segEnd,2)) + self.sketch.addConstraint(Sketcher.Constraint('Coincident',segEnd,1,segLength,2)) + lastRadius = radius + + if oldLength == 0: + # create feature + self.feature = self.Doc.addObject("PartDesign::Revolution","RevolutionShaft") + self.feature.Sketch = self.sketch + self.feature.ReferenceAxis = (self.sketch,['H_Axis']) + self.feature.Angle = 360.0 + self.Doc.recompute() + self.Gui.hide("SketchShaft") + else: + self.Doc.recompute() + # FIXME: Will give a warning in the console if the active window is not the feature + self.Gui.SendMsgToActiveView("ViewFit") + + def updateSegment(self, segment, oldLength, length, diameter): + constrRadius = 4 + segment * 6 + constrLength = 7 + segment * 6 + # update total length + self.totalLength = self.totalLength - oldLength + length + # Adjust length of centerline + self.sketch.setDatum(0,self.totalLength) + # Adjust segment length + self.sketch.setDatum(constrLength, length) + self.sketch.setDatum(constrRadius, diameter/2) + # Update feature + self.Doc.recompute() + self.Gui.SendMsgToActiveView("ViewFit") diff --git a/src/Mod/PartDesign/WizardShaft/WizardShaft.py b/src/Mod/PartDesign/WizardShaft/WizardShaft.py new file mode 100644 index 000000000..04c3ea147 --- /dev/null +++ b/src/Mod/PartDesign/WizardShaft/WizardShaft.py @@ -0,0 +1,118 @@ +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +import FreeCAD, FreeCADGui +import os +from PyQt4 import QtCore, QtGui +from WizardShaftTable import WizardShaftTable +from Shaft import Shaft + +class WizardShaft: + "Shaft Wizard" + # GUI + App = FreeCAD + Gui = FreeCADGui + doc = Gui.ActiveDocument + table = 0 + # Shaft + shaft = 0 + # Feature + sketch = 0 + featureWindow = 0 + + def __init__(self): + #FreeCADGui.activeWorkbench() + # + "Initialize the user interface" + ### Create a dock window + wizardWidget = QtGui.QDockWidget("Shaft Wizard") + wizardWidget.setObjectName("Shaft Wizard") + mw = QtGui.qApp.activeWindow() + mw.addDockWidget(QtCore.Qt.TopDockWidgetArea,wizardWidget) + cw = mw.centralWidget() # This is a qmdiarea widget + + # Get active document or create a new one + # Important because when setting the default data in WizardShaftTable() the + # updateSketch() slot will be activated and it relies on finding a valid document + if self.doc == None: + self.Gui.activateWorkbench("PartDesignWorkbench") + self.doc = self.App.newDocument() + # Grab the newly created feature window + featureWindow = cw.subWindowList()[-1] + else: + featureWindow = cw.activeSubWindow() + + # Create Shaft object + self.shaft = Shaft(self.doc) + #self.shaft.featureWindow = featureWindow + + # Assign a table widget to the dock window + self.table = WizardShaftTable(self, self.shaft) + wizardWidget.setWidget(self.table.widget) + # Grab the newly created HTML window + cw = mw.centralWidget() + htmlWindow = cw.subWindowList()[-1] + #self.shaft.htmlWindow = htmlWindow + + def updateEdge(self, column, start): + print "Not implemented yet - waiting for robust references..." + return + if self.sketchClosed is not True: + return + # Create a chamfer or fillet at the start or end edge of the segment + if start is True: + row = rowStartEdgeType + idx = 0 + else: + row = rowEndEdgeType + idx = 1 + + edgeType = self.tableWidget.item(row, column).text().toAscii()[0].upper() + if not ((edgeType == "C") or (edgeType == "F")): + return # neither chamfer nor fillet defined + + if edgeType == "C": + objName = self.doc.addObject("PartDesign::Chamfer","ChamferShaft%u" % (column * 2 + idx)) + else: + objName = self.doc.addObject("PartDesign::Fillet","FilletShaft%u" % (column * 2 + idx)) + if objName == "": + return + + edgeName = "Edge%u" % self.getEdgeIndex(column, idx, edgeType) + self.doc.getObject(objName).Base = (self.doc.getObject("RevolutionShaft"),"[%s]" % edgeName) + # etc. etc. + + def getEdgeIndex(self, column, startIdx): + # FIXME: This is impossible without robust references anchored in the sketch!!! + return + +class WizardShaftGui: + def Activated(self): + WizardShaft() + + def GetResources(self): + IconPath = FreeCAD.ConfigGet("AppHomePath") + "Mod/PartDesign/Gui/Resources/icons/WizardShaftIcon.png" + MenuText = 'Start the shaft design wizard' + ToolTip = 'Start the shaft design wizard' + return {'Pixmap' : IconPath, 'MenuText': MenuText, 'ToolTip': ToolTip} + +FreeCADGui.addCommand('PartDesign_WizardShaft', WizardShaftGui()) diff --git a/src/Mod/PartDesign/WizardShaft/WizardShaftTable.py b/src/Mod/PartDesign/WizardShaft/WizardShaftTable.py new file mode 100644 index 000000000..705644193 --- /dev/null +++ b/src/Mod/PartDesign/WizardShaft/WizardShaftTable.py @@ -0,0 +1,332 @@ +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +from PyQt4 import QtCore, QtGui + +class WizardShaftTable: + "The table widget that contains all the data of the shaft" + # Dictionary to access different parameters without using a numeric row index that might change + # as the code evolves + rowDict = { + "Length" : 0, + "Diameter" : 1, + "LoadType" : 2, + "LoadSize" : 3, + "LoadLocation" : 4, + "StartEdgeType" : 5, + "StartEdgeSize" : 6, + "EndEdgeType" : 7, + "EndEdgeSize" : 8 + } + rowDictReverse = {} + headers = ["Length [mm]", + "Diameter [mm]", + "Load type", + "Load [N]", + "Location [mm]", + "Start edge type", + "Start edge size", + "End edge type", + "End edge size" + ] + widget = 0 + wizard = 0 + shaft = 0 + + def __init__(self, w, s): + for key in self.rowDict.iterkeys(): + self.rowDictReverse[self.rowDict[key]] = key + # Set parent wizard (for connecting slots) + self.wizard = w + self.shaft = s + # Create table widget + self.widget = QtGui.QTableWidget(len(self.rowDict), 0) + self.widget.resize(QtCore.QSize(300,100)) + #self.widget.setFocusPolicy(QtCore.Qt.StrongFocus) + + # Label rows and columns + self.widget.setVerticalHeaderLabels(self.headers) + self.widget.setHorizontalHeaderLabels(["Section 1", "Section 2"]) + #self.widget.columnMoved.connect(column, oldIndex, newIndex) + + # Create context menu + action = QtGui.QAction("Add column", self.widget) + action.triggered.connect(self.slotInsertColumn) + self.widget.addAction(action) + self.widget.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) + + # Set some default data + # Section 1 + self.addColumn() + self.setLength(0, 40.0) + self.setDiameter(0, 50.0) + self.setLoadType(0, "Static") + self.setLoadSize(0, 1000.0) + self.setLoadLocation(0, 25.0) + # Section 2 + self.addColumn() + self.setLength(1, 80.0) + self.setDiameter(1, 60.0) + self.setLoadType(1, "Fixed") + + def slotInsertColumn(self, point): + # FIXME: Allow inserting columns, not just adding at the end + # Note: need to re-name all the following column headers then + # if (column == self.tableWidget.columnCount()): + self.addColumn() + # else: + # self.insertColumn(index) + + def addColumn(self): + "Add a new column, fill it with widgets, and connect the signals" + index = self.widget.columnCount() + # Make an intelligent guess at the length/dia of the next segment + if index > 0: + length = self.shaft.segments[index-1].length + diameter = self.shaft.segments[index-1].diameter + if index > 2: + diameter -= 5.0 + else: + diameter += 5.0 + else: + length = 20.0 + diameter = 10.0 + self.shaft.addSegment(length, diameter) + + self.widget.insertColumn(index) + self.widget.setHorizontalHeaderItem(index + 1, QtGui.QTableWidgetItem("Section %s" % (index + 1))) + + # Length + widget = QtGui.QDoubleSpinBox(self.widget) + widget.setMinimum(0) + widget.setMaximum(1E9) + self.widget.setCellWidget(self.rowDict["Length"],index, widget) + widget.setValue(length) + widget.valueChanged.connect(self.slotValueChanged) + widget.editingFinished.connect(self.slotEditingFinished) + # Diameter + widget = QtGui.QDoubleSpinBox(self.widget) + widget.setMinimum(0) + widget.setMaximum(1E9) + self.widget.setCellWidget(self.rowDict["Diameter"],index, widget) + widget.setValue(diameter) + widget.valueChanged.connect(self.slotValueChanged) + widget.editingFinished.connect(self.slotEditingFinished) + # Load type + widget = QtGui.QListWidget(self.widget) + widget.insertItem(0, QtGui.QListWidgetItem("None", widget)) + widget.insertItem(1, QtGui.QListWidgetItem("Fixed", widget)) + widget.insertItem(2, QtGui.QListWidgetItem("Static", widget)) + widget.insertItem(3, QtGui.QListWidgetItem("Bearing", widget)) + widget.insertItem(4, QtGui.QListWidgetItem("Pulley", widget)) + self.widget.setCellWidget(self.rowDict["LoadType"],index, widget) + widget.setCurrentRow(0) + widget.currentItemChanged.connect(self.slotLoadType) + # Load size + widget = QtGui.QDoubleSpinBox(self.widget) + widget.setMinimum(-1E9) + widget.setMaximum(1E9) + self.widget.setCellWidget(self.rowDict["LoadSize"],index, widget) + widget.setValue(0) + widget.valueChanged.connect(self.slotValueChanged) + widget.editingFinished.connect(self.slotEditingFinished) + # Load location + widget = QtGui.QDoubleSpinBox(self.widget) + widget.setMinimum(0) + widget.setMaximum(1E9) + self.widget.setCellWidget(self.rowDict["LoadLocation"],index, widget) + widget.setValue(0) + widget.valueChanged.connect(self.slotValueChanged) + widget.editingFinished.connect(self.slotEditingFinished) + # Start edge type + widget = QtGui.QListWidget(self.widget) + widget.insertItem(0, QtGui.QListWidgetItem("None", widget)) + widget.insertItem(1, QtGui.QListWidgetItem("Chamfer", widget)) + widget.insertItem(2, QtGui.QListWidgetItem("Fillet", widget)) + self.widget.setCellWidget(self.rowDict["StartEdgeType"],index, widget) + widget.setCurrentRow(0) + widget.currentItemChanged.connect(self.slotStartEdgeType) + # Start edge size + widget = QtGui.QDoubleSpinBox(self.widget) + widget.setMinimum(0) + widget.setMaximum(1E9) + self.widget.setCellWidget(self.rowDict["StartEdgeSize"],index, widget) + widget.setValue(1) + widget.valueChanged.connect(self.slotValueChanged) + widget.editingFinished.connect(self.slotEditingFinished) + # End edge type + widget = QtGui.QListWidget(self.widget) + widget.insertItem(0, QtGui.QListWidgetItem("None", widget)) + widget.insertItem(1, QtGui.QListWidgetItem("Chamfer", widget)) + widget.insertItem(2, QtGui.QListWidgetItem("Fillet", widget)) + self.widget.setCellWidget(self.rowDict["EndEdgeType"],index, widget) + widget.setCurrentRow(0) + widget.currentItemChanged.connect(self.slotEndEdgeType) + # End edge size + widget = QtGui.QDoubleSpinBox(self.widget) + widget.setMinimum(0) + widget.setMaximum(1E9) + self.widget.setCellWidget(self.rowDict["EndEdgeSize"],index, widget) + widget.setValue(1) + widget.valueChanged.connect(self.slotValueChanged) + widget.editingFinished.connect(self.slotEditingFinished) + + def slotValueChanged(self, value): + (self.editedRow, self.editedColumn) = self.getFocusedCell() + self.editedValue = value + + def slotEditingFinished(self): + rowName = self.rowDictReverse[self.editedRow] + if rowName is None: + return + if rowName == "Length": + self.shaft.updateSegment(self.editedColumn, length = self.getDoubleValue(rowName, self.editedColumn)) + elif rowName == "Diameter": + self.shaft.updateSegment(self.editedColumn, diameter = self.getDoubleValue(rowName, self.editedColumn)) + elif rowName == "LoadType": + self.shaft.updateLoad(self.editedColumn, loadType = self.getListValue(rowName, self.editedColumn)) + elif rowName == "LoadSize": + self.shaft.updateLoad(self.editedColumn, loadSize = self.getDoubleValue(rowName, self.editedColumn)) + elif rowName == "LoadLocation": + self.shaft.updateLoad(self.editedColumn, loadLocation = self.getDoubleValue(rowName, self.editedColumn)) + elif rowName == "StartEdgeType": + pass + elif rowName == "StartEdgeSize": + pass + elif rowName == "EndEdgeType": + pass + elif rowName == "EndEdgeSize": + pass + + def setLength(self, column, l): + self.setDoubleValue("Length", column, l) + self.shaft.updateSegment(column, length = l) + + def getLength(self, column): + return self.getDoubleValue("Length", column) + + def setDiameter(self, column, d): + self.setDoubleValue("Diameter", column, d) + self.shaft.updateSegment(column, diameter = d) + + def getDiameter(self, column): + return self.getDoubleValue("Diameter", column) + + def slotLoadType(self, new, old): + if new.text() != "Fixed": + if (self.getLoadSize is None) or (self.getLoadLocation is None): + return + self.shaft.updateLoad(self.getFocusedColumn(), loadType = new.text()) + + def setLoadType(self, column, t): + self.setListValue("LoadType", column, t) + self.shaft.updateLoad(column, loadType = t) + + def getLoadType(self, column): + return self.getListValue("LoadType", column) + + def setLoadSize(self, column, s): + self.setDoubleValue("LoadSize", column, s) + self.shaft.updateLoad(column, loadSize = s) + + def getLoadSize(self, column): + return self.getDoubleValue("LoadSize", column) + + def setLoadLocation(self, column, l): + self.setDoubleValue("LoadLocation", column, l) + self.shaft.updateLoad(column, loadLocation = l) + + def getLoadLocation(self, column): + return self.getDoubleValue("LoadLocation", column) + + def slotStartEdgeType(self, old, new): + pass + + def setStartEdgeType(self, column, t): + self.setListValue("StartEdgeType", column, t) + + def getStartEdgeType(self, column): + return self.getListValue("StartEdgeType", column) + + def setStartEdgeSize(self, column, s): + self.setDoubleValue("StartEdgeSize", column, s) + + def getStartEdgeSize(self, column): + return self.getDoubleValue("StartEdgeSize", column) + + def slotEndEdgeType(self, old, new): + pass + + def setEndEdgeType(self, column, t): + self.setListValue("EndEdgeType", column, t) + + def getEndEdgeType(self, column): + return self.getListValue("EndEdgeType", column) + + def setEndEdgeSize(self, column, s): + self.setDoubleValue("EndEdgeSize", column, s) + + def getEndEdgeSize(self, column): + return self.getDoubleValue("EndEdgeSize", column) + + def setDoubleValue(self, row, column, v): + widget = self.widget.cellWidget(self.rowDict[row], column) + # Avoid triggering a signal, because the slot will work on the focused cell, not the current one + widget.blockSignals(True) + widget.setValue(v) + widget.blockSignals(False) + + def getDoubleValue(self, row, column): + widget = self.widget.cellWidget(self.rowDict[row], column) + if widget is not None: + return widget.value() + else: + return None + + def setListValue(self, row, column, v): + widget = self.widget.cellWidget(self.rowDict[row], column) + widget.blockSignals(True) + widget.setCurrentItem(widget.findItems(v, QtCore.Qt.MatchExactly)[0]) + widget.blockSignals(False) + + def getListValue(self, row, column): + widget = self.widget.cellWidget(self.rowDict[row], column) + if widget is not None: + return widget.currentItem().text().toAscii()[0].upper() + else: + return None + + def getFocusedColumn(self): + # Make the focused cell also the current one in the table + widget = QtGui.QApplication.focusWidget() + if widget is not None: + index = self.widget.indexAt(widget.pos()) + self.widget.setCurrentCell(index.row(), index.column()) + return self.widget.currentColumn() + + def getFocusedCell(self): + # Make the focused cell also the current one in the table + widget = QtGui.QApplication.focusWidget() + if widget is not None: + index = self.widget.indexAt(widget.pos()) + self.widget.setCurrentCell(index.row(), index.column()) + return (self.widget.currentRow(), self.widget.currentColumn()) diff --git a/src/Mod/PartDesign/WizardShaft/__init__.py b/src/Mod/PartDesign/WizardShaft/__init__.py new file mode 100644 index 000000000..b3fd816df --- /dev/null +++ b/src/Mod/PartDesign/WizardShaft/__init__.py @@ -0,0 +1,27 @@ +""" +Shaft Wizard +""" + +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +# Empty file to treat the folder as a package