diff --git a/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_InternalExternalGear.svg b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_InternalExternalGear.svg new file mode 100644 index 000000000..9cc7c91eb --- /dev/null +++ b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_InternalExternalGear.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/Mod/PartDesign/InvoluteGearFeature.py b/src/Mod/PartDesign/InvoluteGearFeature.py index 3572d38e8..b79390aed 100644 --- a/src/Mod/PartDesign/InvoluteGearFeature.py +++ b/src/Mod/PartDesign/InvoluteGearFeature.py @@ -46,7 +46,7 @@ def makeInvoluteGear(name): class _CommandInvoluteGear: "the Fem InvoluteGear command definition" def GetResources(self): - return {'Pixmap' : 'PartDesign_InvoluteGear', + return {'Pixmap' : 'PartDesign_InternalExternalGear', 'MenuText': QtCore.QT_TRANSLATE_NOOP("PartDesign_InvoluteGear","Involute gear..."), 'Accel': "", 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PartDesign_InvoluteGear","Creates or edit the involute gear definition.")} @@ -72,12 +72,14 @@ class _InvoluteGear: obj.addProperty("App::PropertyInteger","NumberOfTeeth","Gear","Number of gear teeth") obj.addProperty("App::PropertyLength","Modules","Gear","Modules of the gear") obj.addProperty("App::PropertyAngle","PressureAngle","Gear","Pressure angle of gear teeth") - obj.addProperty("App::PropertyInteger","NumberOfCurves","Gear","0=2x3 1=1x4 ") + obj.addProperty("App::PropertyBool","HighPrecision","Gear","True=2 curves with each 3 control points False=1 curve with 4 control points ") + obj.addProperty("App::PropertyBool","ExternalGear","Gear","True=external Gear False=internal Gear ") obj.NumberOfTeeth = 26 obj.Modules = "2.5 mm" obj.PressureAngle = "20 deg" - obj.NumberOfCurves = 0 + obj.HighPrecision = True + obj.ExternalGear = True obj.Proxy = self @@ -85,7 +87,10 @@ class _InvoluteGear: def execute(self,obj): #print "_InvoluteGear.execute()" w = fcgear.FCWireBuilder() - involute.CreateExternalGear(w, obj.Modules.Value,obj.NumberOfTeeth, obj.PressureAngle.Value, obj.NumberOfCurves == 0) + if obj.ExternalGear: + involute.CreateExternalGear(w, obj.Modules.Value,obj.NumberOfTeeth, obj.PressureAngle.Value, obj.HighPrecision) + else: + involute.CreateInternalGear(w, obj.Modules.Value,obj.NumberOfTeeth, obj.PressureAngle.Value, obj.HighPrecision) gearw = Part.Wire([o.toShape() for o in w.wire]) obj.Shape = gearw return @@ -129,22 +134,36 @@ class _InvoluteGearTaskPanel: self.obj = obj self.form=FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/PartDesign/InvoluteGearFeature.ui") - + QtCore.QObject.connect(self.form.Quantity_Modules, QtCore.SIGNAL("valueChanged(double)"), self.modulesChanged) QtCore.QObject.connect(self.form.Quantity_PressureAngle, QtCore.SIGNAL("valueChanged(double)"), self.angleChanged) QtCore.QObject.connect(self.form.spinBox_NumberOfTeeth, QtCore.SIGNAL("valueChanged(int)"), self.numTeethChanged) + QtCore.QObject.connect(self.form.comboBox_HighPrecision, QtCore.SIGNAL("currentIndexChanged(int)"), self.numCurvesChanged) + #QtCore.QObject.connect(self.form.comboBox_ExternalGear, QtCore.SIGNAL("activated(QString)"), self.externalGearChanged) + #QtCore.QObject.connect(self.form.comboBox_ExternalGear, QtCore.SIGNAL("currentIndexChanged(int)"), self.externalGearChanged) + QtCore.QObject.connect(self.form.comboBox_ExternalGear, QtCore.SIGNAL("currentIndexChanged(int)"), self.externalGearChanged) 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.Modules = self.form.Quantity_Modules.text() self.obj.PressureAngle = self.form.Quantity_PressureAngle.text() - self.obj.NumberOfCurves = self.form.comboBox_NumberOfCurves.currentIndex() + if self.form.comboBox_HighPrecision.currentIndex() == 0: + self.obj.HighPrecision = True + else: + self.obj.HighPrecision = False + #self.obj.HighPrecision = self.form.comboBox_HighPrecision.currentIndex() + if self.form.comboBox_ExternalGear.currentIndex() == 0: + self.obj.ExternalGear = True + else: + self.obj.ExternalGear = False + #self.obj.ExternalGear = self.form.comboBox_ExternalGear.currentIndex() def transferFrom(self): @@ -152,12 +171,22 @@ class _InvoluteGearTaskPanel: self.form.spinBox_NumberOfTeeth.setValue(self.obj.NumberOfTeeth) self.form.Quantity_Modules.setText(self.obj.Modules.UserString) self.form.Quantity_PressureAngle.setText(self.obj.PressureAngle.UserString) - self.form.comboBox_NumberOfCurves.setCurrentIndex(self.obj.NumberOfCurves) + if self.obj.HighPrecision: + self.form.comboBox_HighPrecision.setCurrentIndex(0) + else: + self.form.comboBox_HighPrecision.setCurrentIndex(1) + #self.form.comboBox_HighPrecision.setCurrentIndex(self.obj.HighPrecision) + if self.obj.ExternalGear: + self.form.comboBox_ExternalGear.setCurrentIndex(0) + else: + self.form.comboBox_ExternalGear.setCurrentIndex(1) + #self.form.comboBox_ExternalGear.setCurrentIndex(self.obj.ExternalGear) def modulesChanged(self, value): #print value self.obj.Modules = value self.obj.Proxy.execute(self.obj) + FreeCAD.Gui.SendMsgToActiveView("ViewFit") def angleChanged(self, value): #print value @@ -168,6 +197,25 @@ class _InvoluteGearTaskPanel: #print value self.obj.NumberOfTeeth = value self.obj.Proxy.execute(self.obj) + FreeCAD.Gui.SendMsgToActiveView("ViewFit") + + def numCurvesChanged(self, value): + #print value + if value == 0: + v=True + else: + v=False + self.obj.HighPrecision = v + self.obj.Proxy.execute(self.obj) + + def externalGearChanged(self, value): + #print value + if value == 0: + v=True + else: + v=False + self.obj.ExternalGear = v + self.obj.Proxy.execute(self.obj) def getStandardButtons(self): return int(QtGui.QDialogButtonBox.Ok) | int(QtGui.QDialogButtonBox.Cancel)| int(QtGui.QDialogButtonBox.Apply) diff --git a/src/Mod/PartDesign/InvoluteGearFeature.ui b/src/Mod/PartDesign/InvoluteGearFeature.ui index 19b9c4a2c..ee97e90c1 100644 --- a/src/Mod/PartDesign/InvoluteGearFeature.ui +++ b/src/Mod/PartDesign/InvoluteGearFeature.ui @@ -7,7 +7,7 @@ 0 0 195 - 116 + 136 @@ -122,26 +122,53 @@ - Number of Curves: + High Precision: - + - false + true false - 2x3 + True - 1x4 + False + + + + + + + + external Gear: + + + + + + + true + + + false + + + + True + + + + + False diff --git a/src/Mod/PartDesign/fcgear/involute.py b/src/Mod/PartDesign/fcgear/involute.py index 27f070bd0..620afb052 100644 --- a/src/Mod/PartDesign/fcgear/involute.py +++ b/src/Mod/PartDesign/fcgear/involute.py @@ -124,6 +124,115 @@ def CreateExternalGear(w, m, Z, phi, split=True): w.close() return w +def CreateInternalGear(w, m, Z, phi, split=True): + """ + Create an internal gear + + w is wirebuilder object (in which the gear will be constructed) + + if split is True, each profile of a teeth will consist in 2 Bezier + curves of degree 3, otherwise it will be made of one Bezier curve + of degree 4 + """ + # ****** external gear specifications + addendum = 0.6 * m # distance from pitch circle to tip circle (ref G.M.Maitra) + dedendum = 1.25 * m # pitch circle to root, sets clearance + clearance = 0.25 * m + + # Calculate radii + Rpitch = Z * m / 2 # pitch circle radius + Rb = Rpitch*cos(phi * pi / 180) # base circle radius + Ra = Rpitch - addendum # tip (addendum) circle radius + Rroot = Rpitch + dedendum # root circle radius + fRad = 1.5 * clearance # fillet radius, max 1.5*clearance + Rf = Rroot - clearance # radius at top of fillet (end of profile) + + # ****** calculate angles (all in radians) + pitchAngle = 2 * pi / Z # angle subtended by whole tooth (rads) + baseToPitchAngle = genInvolutePolar(Rb, Rpitch) + tipToPitchAngle = baseToPitchAngle + if (Ra > Rb): # start profile at top of fillet (if its greater) + tipToPitchAngle -= genInvolutePolar(Rb, Ra) + pitchToFilletAngle = genInvolutePolar(Rb, Rf) - baseToPitchAngle; + filletAngle = 1.414*clearance/Rf # // to make fillet tangential to root + + # ****** generate Higuchi involute approximation + fe = 1 # fraction of profile length at end of approx + fs = 0.01 # fraction of length offset from base to avoid singularity + if (Ra > Rb): + fs = (Ra**2 - Rb**2) / (Rf**2 - Rb**2) # offset start to top of fillet + + if split: + # approximate in 2 sections, split 25% along the involute + fm = fs + (fe - fs) / 4 # fraction of length at junction (25% along profile) + addInv = BezCoeffs(m, Z, phi, 3, fs, fm) + dedInv = BezCoeffs(m, Z, phi, 3, fm, fe) + + # join the 2 sets of coeffs (skip duplicate mid point) + invR = addInv + dedInv[1:] + else: + invR = BezCoeffs(m, Z, phi, 4, fs, fe) + + # create the back profile of tooth (mirror image) + inv = [] + for i, pt in enumerate(invR): + # rotate involute to put center of tooth at y = 0 + ptx, pty = invR[i] = rotate(pt, pitchAngle / 4 - baseToPitchAngle) + # generate the back of tooth profile nodes, flip Y coords + inv.append((ptx, -pty)) + + # ****** calculate section junction points R=back of tooth, Next=front of next tooth) + #fillet = inv[6] # top of fillet, front of tooth #toCartesian(Rf, -pitchAngle / 4 - pitchToFilletAngle) # top of fillet + fillet = [ptx,-pty] + tip = toCartesian(Ra, -pitchAngle/4+tipToPitchAngle) # tip, front of tooth + tipR = [ tip[0], -tip[1] ] + #filletR = [fillet[0], -fillet[1]] # flip to make same point on back of tooth + rootR = toCartesian(Rroot, pitchAngle / 4 + pitchToFilletAngle + filletAngle) + rootNext = toCartesian(Rroot, 3 * pitchAngle / 4 - pitchToFilletAngle - filletAngle) + filletNext = rotate(fillet, pitchAngle) # top of fillet, front of next tooth + + # Build the shapes using FreeCAD.Part + t_inc = 2.0 * pi / float(Z) + thetas = [(x * t_inc) for x in range(Z)] + + w.move(fillet) # start at top of front profile + + + for theta in thetas: + w.theta = theta + if split: + w.curve(inv[5], inv[4], inv[3]) + w.curve(inv[2], inv[1], inv[0]) + else: + w.curve(*inv[-2::-1]) + + if (Ra < Rb): + w.line(tip) # line from fillet up to base circle + + if split: + w.arc(tipR, Ra, 0) # arc across addendum circle + else: + #w.arc(tipR[-1], Ra, 0) # arc across addendum circle + w.arc(tipR, Ra, 0) + + if (Ra < Rb): + w.line(invR[0]) # line down to topof fillet + + if split: + w.curve(invR[1], invR[2], invR[3]) + w.curve(invR[4], invR[5], invR[6]) + else: + w.curve(*invR[1:]) + + if (rootNext[1] > rootR[1]): # is there a section of root circle between fillets? + w.arc(rootR, fRad, 1) # back fillet + w.arc(rootNext, Rroot, 0) # root circle arc + + w.arc(filletNext, fRad, 1) + + + w.close() + return w def genInvolutePolar(Rb, R):