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 @@
+
+
+
+
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
+
+
+
+
+
+
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)
+
+
+