Initial addition of fcsprocket feature. This is a PartDesign tool that allows for the simple creation of ANSI standard roller chain sprockets.

This commit is contained in:
Adam Spontarelli
2020-03-28 12:57:36 -04:00
committed by Yorik van Havre
parent 45aef7b028
commit 1eef7064f8
12 changed files with 1320 additions and 4 deletions

View File

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

View File

@@ -37,6 +37,7 @@
<file>icons/PartDesign_Revolution.svg</file>
<file>icons/PartDesign_Scaled.svg</file>
<file>icons/PartDesign_ShapeBinder.svg</file>
<file>icons/PartDesign_Sprocket.svg</file>
<file>icons/PartDesign_SubShapeBinder.svg</file>
<file>icons/PartDesign_Subtractive_Box.svg</file>
<file>icons/PartDesign_Subtractive_Cone.svg</file>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 89 KiB

View File

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

View File

@@ -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:

View File

@@ -0,0 +1,232 @@
#***************************************************************************
#* Copyright (c) 2020 Adam Spontarelli <adam@vector-space.org> *
#* *
#* 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())

View File

@@ -0,0 +1,232 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SprocketParameter</class>
<widget class="QWidget" name="SprocketParameter">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>195</width>
<height>142</height>
</rect>
</property>
<property name="windowTitle">
<string>Sprocket parameter</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Number of teeth:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spinBox_NumberOfTeeth">
<property name="minimum">
<number>3</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>50</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>ANSI Size:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBox_ANSISize">
<item>
<property name="text">
<string>25</string>
</property>
</item>
<item>
<property name="text">
<string>35</string>
</property>
</item>
<item>
<property name="text">
<string>41</string>
</property>
</item>
<item>
<property name="text">
<string>40</string>
</property>
</item>
<item>
<property name="text">
<string>50</string>
</property>
</item>
<item>
<property name="text">
<string>60</string>
</property>
</item>
<item>
<property name="text">
<string>80</string>
</property>
</item>
<item>
<property name="text">
<string>100</string>
</property>
</item>
<item>
<property name="text">
<string>120</string>
</property>
</item>
<item>
<property name="text">
<string>140</string>
</property>
</item>
<item>
<property name="text">
<string>160</string>
</property>
</item>
<item>
<property name="text">
<string>180</string>
</property>
</item>
<item>
<property name="text">
<string>200</string>
</property>
</item>
<item>
<property name="text">
<string>240</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Chain Pitch:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Gui::InputField" name="Quantity_Pitch">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>80</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="unit" stdset="0">
<string notr="true">in</string>
</property>
<property name="decimals" stdset="0">
<number>3</number>
</property>
<property name="maximum" stdset="0">
<double>2000.000000000000000</double>
</property>
<property name="minimum" stdset="0">
<double>0.01</double>
</property>
<property name="singleStep" stdset="0">
<double>0.001</double>
</property>
<property name="value" stdset="0">
<double>0.375</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Roller Diameter:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="Gui::InputField" name="Quantity_RollerDiameter">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>80</width>
<height>20</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="unit" stdset="0">
<string notr="true">in</string>
</property>
<property name="decimals" stdset="0">
<number>3</number>
</property>
<property name="maximum" stdset="0">
<double>50.000000000000000</double>
</property>
<property name="minimum" stdset="0">
<double>0.01</double>
</property>
<property name="singleStep" stdset="0">
<double>0.01</double>
</property>
<property name="value" stdset="0">
<double>0.20</double>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::InputField</class>
<extends>QLineEdit</extends>
<header>Gui/InputField.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -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 <adam@vector-space.org>.
Distributed under the LGPL licence.

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,105 @@
# (c) 2020 Adam Spontarelli <adam@vector-space.org>
#
# 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

View File

@@ -0,0 +1,66 @@
# (c) 2020 Adam Spontarelli <adam@vector-space.org>
#
# 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_()

View File

@@ -0,0 +1,144 @@
# (c) 2020 Adam Spontarelli <adam@vector-space.org>
#
# 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)