diff --git a/src/Mod/PartDesign/CMakeLists.txt b/src/Mod/PartDesign/CMakeLists.txt index 177550408a..435ce86d35 100644 --- a/src/Mod/PartDesign/CMakeLists.txt +++ b/src/Mod/PartDesign/CMakeLists.txt @@ -16,6 +16,8 @@ if(BUILD_GUI) TestPartDesignGui.py InvoluteGearFeature.py InvoluteGearFeature.ui + SprocketFeature.py + SprocketFeature.ui ) endif(BUILD_GUI) @@ -60,6 +62,13 @@ set(PartDesign_GearScripts fcgear/svggear.py ) +set(PartDesign_SprocketScripts + fcsprocket/__init__.py + fcsprocket/fcsprocket.py + fcsprocket/fcsprocketdialog.py + fcsprocket/sprocket.py +) + set(PartDesign_WizardShaft WizardShaft/__init__.py WizardShaft/WizardShaft.svg @@ -76,6 +85,7 @@ add_custom_target(PartDesignScripts ALL SOURCES ${PartDesign_OtherScripts} ${PartDesign_TestScripts} ${PartDesign_GearScripts} + ${PartDesign_SprocketScripts} ) fc_target_copy_resource(PartDesignScripts @@ -85,6 +95,7 @@ fc_target_copy_resource(PartDesignScripts ${PartDesign_OtherScripts} ${PartDesign_TestScripts} ${PartDesign_GearScripts} + ${PartDesign_SprocketScripts} ) INSTALL( @@ -113,7 +124,13 @@ INSTALL( ${PartDesign_GearScripts} DESTINATION Mod/PartDesign/fcgear - +) + +INSTALL( + FILES + ${PartDesign_SprocketScripts} + DESTINATION + Mod/PartDesign/fcsprocket ) if(BUILD_FEM) diff --git a/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc b/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc index e8db9a825e..34550529ba 100644 --- a/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc +++ b/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc @@ -37,6 +37,7 @@ icons/PartDesign_Revolution.svg icons/PartDesign_Scaled.svg icons/PartDesign_ShapeBinder.svg + icons/PartDesign_Sprocket.svg icons/PartDesign_SubShapeBinder.svg icons/PartDesign_Subtractive_Box.svg icons/PartDesign_Subtractive_Cone.svg diff --git a/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Sprocket.svg b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Sprocket.svg new file mode 100644 index 0000000000..15bc199d19 --- /dev/null +++ b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Sprocket.svg @@ -0,0 +1,492 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp index 350c14dbe3..b8489d36fe 100644 --- a/src/Mod/PartDesign/Gui/Workbench.cpp +++ b/src/Mod/PartDesign/Gui/Workbench.cpp @@ -509,9 +509,10 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "PartDesign_Boolean" << "Separator" //<< "PartDesign_Hole" - << "PartDesign_InvoluteGear" - << "Separator" - << "PartDesign_Migrate"; + << "PartDesign_Migrate" + << "PartDesign_Sprocket" + << "PartDesign_InvoluteGear"; + // For 0.13 a couple of python packages like numpy, matplotlib and others // are not deployed with the installer on Windows. Thus, the WizardShaft is diff --git a/src/Mod/PartDesign/InitGui.py b/src/Mod/PartDesign/InitGui.py index f25bd79358..c935ffb033 100644 --- a/src/Mod/PartDesign/InitGui.py +++ b/src/Mod/PartDesign/InitGui.py @@ -51,6 +51,7 @@ class PartDesignWorkbench ( Workbench ): import PartDesign try: from PartDesign import InvoluteGearFeature + from PartDesign import SprocketFeature except ImportError: print("Involute gear module cannot be loaded") #try: diff --git a/src/Mod/PartDesign/SprocketFeature.py b/src/Mod/PartDesign/SprocketFeature.py new file mode 100644 index 0000000000..d9824c49cd --- /dev/null +++ b/src/Mod/PartDesign/SprocketFeature.py @@ -0,0 +1,232 @@ +#*************************************************************************** +#* Copyright (c) 2020 Adam Spontarelli * +#* * +#* This program is free software; you can redistribute it and/or modify * +#* it under the terms of the GNU Lesser General Public License (LGPL) * +#* as published by the Free Software Foundation; either version 2 of * +#* the License, or (at your option) any later version. * +#* for detail see the LICENCE text file. * +#* * +#* This program is distributed in the hope that it will be useful, * +#* but WITHOUT ANY WARRANTY; without even the implied warranty of * +#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +#* GNU Library General Public License for more details. * +#* * +#* You should have received a copy of the GNU Library General Public * +#* License along with this program; if not, write to the Free Software * +#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +#* USA * +#* * +#*************************************************************************** + +import FreeCAD, Part +from fcsprocket import fcsprocket +from fcsprocket import sprocket + +if FreeCAD.GuiUp: + import FreeCADGui + from PySide import QtCore, QtGui + from FreeCADGui import PySideUic as uic + +__title__="PartDesign SprocketObject management" +__author__ = "Adam Spontarelli" +__url__ = "http://www.freecadweb.org" + + +def makeSprocket(name): + '''makeSprocket(name): makes a Sprocket''' + obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",name) + _Sprocket(obj) + if FreeCAD.GuiUp: + _ViewProviderSprocket(obj.ViewObject) + #FreeCAD.ActiveDocument.recompute() + if FreeCAD.GuiUp: + body=FreeCADGui.ActiveDocument.ActiveView.getActiveObject("pdbody") + part=FreeCADGui.ActiveDocument.ActiveView.getActiveObject("part") + if body: + body.Group=body.Group+[obj] + elif part: + part.Group=part.Group+[obj] + return obj + +class _CommandSprocket: + "the Fem Sprocket command definition" + def GetResources(self): + return {'Pixmap' : 'PartDesign_Sprocket', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PartDesign_Sprocket","Sprocket..."), + 'Accel': "", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PartDesign_Sprocket","Creates or edit the sprocket definition.")} + + def Activated(self): + + FreeCAD.ActiveDocument.openTransaction("Create Sprocket") + FreeCADGui.addModule("SprocketFeature") + FreeCADGui.doCommand("SprocketFeature.makeSprocket('Sprocket')") + FreeCADGui.doCommand("Gui.activeDocument().setEdit(App.ActiveDocument.ActiveObject.Name,0)") + + def IsActive(self): + if FreeCAD.ActiveDocument: + return True + else: + return False + + +class _Sprocket: + "The Sprocket object" + def __init__(self,obj): + self.Type = "Sprocket" + obj.addProperty("App::PropertyInteger","NumberOfTeeth","Sprocket","Number of gear teeth") + obj.addProperty("App::PropertyLength","Pitch","Sprocket","Chain Pitch") + obj.addProperty("App::PropertyLength","RollerDiameter","Sprocket","Roller Diameter") + obj.addProperty("App::PropertyString","ANSISize","Sprocket","ANSI Size") + + obj.NumberOfTeeth = 50 + obj.Pitch = "0.375 in" + obj.RollerDiameter = "0.20 in" + obj.ANSISize = "35" + + obj.Proxy = self + + + def execute(self,obj): + w = fcsprocket.FCWireBuilder() + sprocket.CreateSprocket(w, obj.Pitch.Value, obj.NumberOfTeeth, obj.RollerDiameter.Value) + + sprocketw = Part.Wire([o.toShape() for o in w.wire]) + obj.Shape = sprocketw + obj.positionBySupport(); + return + + +class _ViewProviderSprocket: + "A View Provider for the Sprocket object" + + def __init__(self,vobj): + vobj.Proxy = self + + def getIcon(self): + return ":/icons/PartDesign_Sprocket.svg" + + def attach(self, vobj): + self.ViewObject = vobj + self.Object = vobj.Object + + + def setEdit(self,vobj,mode): + taskd = _SprocketTaskPanel(self.Object,mode) + taskd.obj = vobj.Object + taskd.update() + FreeCADGui.Control.showDialog(taskd) + return True + + def unsetEdit(self,vobj,mode): + FreeCADGui.Control.closeDialog() + return + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + +class _SprocketTaskPanel: + '''The editmode TaskPanel for Sprocket objects''' + def __init__(self,obj,mode): + self.obj = obj + + self.form=FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/PartDesign/SprocketFeature.ui") + self.form.setWindowIcon(QtGui.QIcon(":/icons/PartDesign_Sprocket.svg")) + + QtCore.QObject.connect(self.form.Quantity_Pitch, QtCore.SIGNAL("valueChanged(double)"), self.pitchChanged) + QtCore.QObject.connect(self.form.Quantity_RollerDiameter, QtCore.SIGNAL("valueChanged(double)"), self.rollerDiameterChanged) + QtCore.QObject.connect(self.form.spinBox_NumberOfTeeth, QtCore.SIGNAL("valueChanged(int)"), self.numTeethChanged) + QtCore.QObject.connect(self.form.comboBox_ANSISize, QtCore.SIGNAL("currentTextChanged(const QString)"), self.ANSISizeChanged) + + self.update() + + if mode == 0: # fresh created + self.obj.Proxy.execute(self.obj) # calculate once + FreeCAD.Gui.SendMsgToActiveView("ViewFit") + + def transferTo(self): + "Transfer from the dialog to the object" + self.obj.NumberOfTeeth = self.form.spinBox_NumberOfTeeth.value() + self.obj.Pitch = self.form.Quantity_Pitch.text() + self.obj.RollerDiameter = self.form.Quantity_RollerDiameter.text() + self.obj.ANSISize = self.form.comboBox_ANSISize.currentText() + + def transferFrom(self): + "Transfer from the object to the dialog" + self.form.spinBox_NumberOfTeeth.setValue(self.obj.NumberOfTeeth) + self.form.Quantity_Pitch.setText(self.obj.Pitch.UserString) + self.form.Quantity_RollerDiameter.setText(self.obj.RollerDiameter.UserString) + self.form.comboBox_ANSISize.setCurrentText(self.obj.ANSISize) + + def pitchChanged(self, value): + self.obj.Pitch = value + self.obj.Proxy.execute(self.obj) + FreeCAD.Gui.SendMsgToActiveView("ViewFit") + + def ANSISizeChanged(self, size): + """ + ANSI B29.1-2011 standard roller chain sizes in USCS units (inches) + {size: [Pitch, Roller Diameter]} + """ + ANSIRollerTable = {"25": [0.250, 0.130], + "35": [0.375, 0.200], + "41": [0.500, 0.306], + "40": [0.500, 0.312], + "50": [0.625, 0.400], + "60": [0.750, 0.469], + "80": [1.000, 0.625], + "100":[1.250, 0.750], + "120":[1.500, 0.875], + "140":[1.750, 1.000], + "160":[2.000, 1.125], + "180":[2.250, 1.460], + "200":[2.500, 1.562], + "240":[3.000, 1.875]} + + self.obj.Pitch = str(ANSIRollerTable[size][0]) + " in" + self.obj.RollerDiameter = str(ANSIRollerTable[size][1]) + " in" + self.form.Quantity_Pitch.setText(self.obj.Pitch.UserString) + self.form.Quantity_RollerDiameter.setText(self.obj.RollerDiameter.UserString) + + self.obj.Proxy.execute(self.obj) + FreeCAD.Gui.SendMsgToActiveView("ViewFit") + + def rollerDiameterChanged(self, value): + self.obj.RollerDiameter = value + self.obj.Proxy.execute(self.obj) + + def numTeethChanged(self, value): + self.obj.NumberOfTeeth = value + self.obj.Proxy.execute(self.obj) + FreeCAD.Gui.SendMsgToActiveView("ViewFit") + + def getStandardButtons(self): + return int(QtGui.QDialogButtonBox.Ok) | int(QtGui.QDialogButtonBox.Cancel)| int(QtGui.QDialogButtonBox.Apply) + + def clicked(self,button): + if button == QtGui.QDialogButtonBox.Apply: + self.transferTo() + self.obj.Proxy.execute(self.obj) + + def update(self): + 'fills the widgets' + self.transferFrom() + + def accept(self): + self.transferTo() + FreeCAD.ActiveDocument.recompute() + FreeCADGui.ActiveDocument.resetEdit() + + def reject(self): + FreeCADGui.ActiveDocument.resetEdit() + FreeCAD.ActiveDocument.abortTransaction() + + + +if FreeCAD.GuiUp: + FreeCADGui.addCommand('PartDesign_Sprocket',_CommandSprocket()) diff --git a/src/Mod/PartDesign/SprocketFeature.ui b/src/Mod/PartDesign/SprocketFeature.ui new file mode 100644 index 0000000000..c1960a8837 --- /dev/null +++ b/src/Mod/PartDesign/SprocketFeature.ui @@ -0,0 +1,232 @@ + + + SprocketParameter + + + + 0 + 0 + 195 + 142 + + + + Sprocket parameter + + + + + + + Number of teeth: + + + + + + + 3 + + + 9999 + + + 50 + + + + + + + + ANSI Size: + + + + + + + + + 25 + + + + + 35 + + + + + 41 + + + + + 40 + + + + + 50 + + + + + 60 + + + + + 80 + + + + + 100 + + + + + 120 + + + + + 140 + + + + + 160 + + + + + 180 + + + + + 200 + + + + + 240 + + + + + + + + + + Chain Pitch: + + + + + + + + + + 0 + 0 + + + + + 80 + 20 + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + in + + + 3 + + + 2000.000000000000000 + + + 0.01 + + + + 0.001 + + + 0.375 + + + + + + + + + Roller Diameter: + + + + + + + + 0 + 0 + + + + + 80 + 20 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + in + + + 3 + + + 50.000000000000000 + + + 0.01 + + + + 0.01 + + + 0.20 + + + + + + + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
+ + +
diff --git a/src/Mod/PartDesign/fcsprocket/README.md b/src/Mod/PartDesign/fcsprocket/README.md new file mode 100644 index 0000000000..3759e676ce --- /dev/null +++ b/src/Mod/PartDesign/fcsprocket/README.md @@ -0,0 +1,24 @@ +================================================ + FCSprocket: a Sprocket Generator for FreeCAD +================================================ + +This is a simple sprocket generation tool. Sprockets are used in combination +with roller chain to transmit power. The tooth profiles are drawn according +to ANSI standards from the methods outlined in: + + Standard handbook of chains : chains for power transmission and material + handling. Boca Raton: Taylor & Francis, 2006. Print. + + AND + + Oberg, Erik, et al. Machinery's handbook : a reference book for the + mechanical engineer, designer, manufacturing engineer, draftsman, + toolmaker, and machinist. New York: Industrial Press, 2016. Print. + + +This code is based on the work of David Douard and his implementation of the +gear generator (fcgear) found in FreeCAD. + + +Copyright 2020 Adam Spontarelli . +Distributed under the LGPL licence. diff --git a/src/Mod/PartDesign/fcsprocket/__init__.py b/src/Mod/PartDesign/fcsprocket/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/Mod/PartDesign/fcsprocket/__init__.py @@ -0,0 +1 @@ + diff --git a/src/Mod/PartDesign/fcsprocket/fcsprocket.py b/src/Mod/PartDesign/fcsprocket/fcsprocket.py new file mode 100644 index 0000000000..b27309c470 --- /dev/null +++ b/src/Mod/PartDesign/fcsprocket/fcsprocket.py @@ -0,0 +1,105 @@ +# (c) 2020 Adam Spontarelli +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License (LGPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# for detail see the LICENCE text file. +# +# FCGear is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with FCGear; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + +from math import cos, sin, pi, acos, asin, atan, sqrt + +import FreeCAD, Part +from FreeCAD import Base, Console +from . import sprocket +rotate = sprocket.rotate + +def makeSprocket(P, N, Dr): + if FreeCAD.ActiveDocument is None: + FreeCAD.newDocument("Sprocket") + doc = FreeCAD.ActiveDocument + w = FCWireBuilder() + sprocket.CreateSprocket(w, P, N, Dr) + sprocketw = Part.Wire([o.toShape() for o in w.wire]) + sprocket = doc.addObject("Part::Feature", "Sprocket") + sprocket.Shape = sprocketw + return sprocket + +class FCWireBuilder(object): + """A helper class to prepare a Part.Wire object""" + def __init__(self): + self.pos = None + self.theta = 0.0 + self.wire = [] + + def move(self, p): + """set current position""" + self.pos = Base.Vector(*p) + + def line(self, p): + """Add a segment between self.pos and p""" + p = rotate(p, self.theta) + end = Base.Vector(*p) + self.wire.append(Part.LineSegment(self.pos, end)) + self.pos = end + + def arc(self, p, r, sweep): + """"Add an arc from self.pos to p which radius is r + sweep (0 or 1) determine the orientation of the arc + """ + p = rotate(p, self.theta) + end = Base.Vector(*p) + mid = Base.Vector(*(midpoints(p, self.pos, r)[sweep])) + self.wire.append(Part.Arc(self.pos, mid, end)) + self.pos = end + + def curve(self, *points): + """Add a Bezier curve from self.pos to points[-1] + every other points are the control points of the Bezier curve (which + will thus be of degree len(points) ) + """ + points = [Base.Vector(*rotate(p, self.theta)) for p in points] + bz = Part.BezierCurve() + bz.setPoles([self.pos] + points) + self.wire.append(bz) + self.pos = points[-1] + + def close(self): + pass + +def midpoints(p1, p2, r): + """A very ugly function that returns the midpoint of a p1 and p2 + on the circle which radius is r and which pass through p1 and + p2 + + Return the 2 possible solutions + """ + vx, vy = p2[0]-p1[0], p2[1]-p1[1] + b = (vx**2 + vy**2)**.5 + v = (vx/b, vy/b) + cosA = b**2 / (2*b*r) + A = acos(cosA) + + vx, vy = rotate(v, A) + c1 = (p1[0]+r*vx, p1[1]+r*vy) + m1x, m1y = ((p1[0]+p2[0])/2 - c1[0], (p1[1]+p2[1])/2 - c1[1]) + dm1 = (m1x**2+m1y**2)**.5 + m1x, m1y = (c1[0] + r*m1x/dm1, c1[1] + r*m1y/dm1) + m1 = (m1x, m1y) + + vx, vy = rotate(v, -A) + c2 = (p1[0]+r*vx, p1[1]+r*vy) + m2x, m2y = ((p1[0]+p2[0])/2 - c2[0], (p1[1]+p2[1])/2 - c2[1]) + dm2 = (m2x**2+m2y**2)**.5 + m2x, m2y = (c2[0] + r*m2x/dm2, c2[1] + r*m2y/dm2) + m2 = (m2x, m2y) + + return m1, m2 diff --git a/src/Mod/PartDesign/fcsprocket/fcsprocketdialog.py b/src/Mod/PartDesign/fcsprocket/fcsprocketdialog.py new file mode 100644 index 0000000000..7360a7235d --- /dev/null +++ b/src/Mod/PartDesign/fcsprocket/fcsprocketdialog.py @@ -0,0 +1,66 @@ +# (c) 2020 Adam Spontarelli +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License (LGPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# for detail see the LICENCE text file. +# +# FCGear is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with FCGear; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + +from PySide import QtGui as qt +import fcsprocket +import FreeCAD, FreeCADGui + +class SprocketCreationFrame(qt.QFrame): + def __init__(self, parent=None): + super(SprocketCreationFrame, self).__init__(parent) + self.P = qt.QSpinBox(value=0.375) + self.N = qt.QDoubleSpinBox(value=45) + self.Dr = qt.QDoubleSpinBox(value=0.20) + + l = qt.QFormLayout(self) + l.setFieldGrowthPolicy(l.ExpandingFieldsGrow) + l.addRow('Number of teeth:', self.N) + l.addRow('Chain Pitch (in):', self.P) + l.addRow('Roller Diameter (in):', self.Dr) + + +class SprocketDialog(qt.QDialog): + def __init__(self, parent=None): + super(SprocketDialog, self).__init__(parent) + self.gc = SprocketCreationFrame() + + btns = qt.QDialogButtonBox.Ok | qt.QDialogButtonBox.Cancel + buttonBox = qt.QDialogButtonBox(btns, + accepted=self.accept, + rejected=self.reject) + l = qt.QVBoxLayout(self) + l.addWidget(self.gc) + l.addWidget(buttonBox) + self.setWindowTitle('Sprocket creation dialog') + + def accept(self): + if FreeCAD.ActiveDocument is None: + FreeCAD.newDocument("Sprocket") + + gear = fcgear.makeSprocket(self.gc.m.value(), + self.gc.Z.value(), + self.gc.angle.value(), + not self.gc.split.currentIndex()) + FreeCADGui.SendMsgToActiveView("ViewFit") + return super(SprocketDialog, self).accept() + + +if __name__ == '__main__': + a = qt.QApplication([]) + w = SprocketDialog() + w.show() + a.exec_() diff --git a/src/Mod/PartDesign/fcsprocket/sprocket.py b/src/Mod/PartDesign/fcsprocket/sprocket.py new file mode 100644 index 0000000000..a35bb48e70 --- /dev/null +++ b/src/Mod/PartDesign/fcsprocket/sprocket.py @@ -0,0 +1,144 @@ +# (c) 2020 Adam Spontarelli +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License (LGPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# for detail see the LICENCE text file. +# +# FCGear is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with FCGear; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + +from math import cos, sin, tan, sqrt, radians, acos, atan, asin, degrees +import math + +import sys +if sys.version_info.major >= 3: + xrange = range + + +def CreateSprocket(w, P, N, Dr): + """ + Create a sprocket + + w is the wirebuilder object (in which the sprocket will be constructed) + P is the chain pitch + N is the number of teeth + Dr is the roller diameter + + Remaining variables can be found in Standard Handbook of Chains + """ + Ds = 1.005 * Dr + (0.003 * 25.4) + R = Ds / 2 + alpha = 35 + 60/N + beta = 18 - 56 / N + M = 0.8 * Dr * cos(radians(35) + radians(60/N)) + T = 0.8 * Dr * sin(radians(35) + radians(60/N)) + E = 1.3025 * Dr + (0.0015 * 25.4) + W = 1.4 * Dr * cos(radians(180/N)) + V = 1.4 * Dr * sin(radians(180/N)) + F = Dr * (0.8 * cos(radians(18) - radians(56)/N) + 1.4 * + cos(radians(17) - radians(64) / N) - 1.3025) - (0.0015 * 25.4) + PD = P / (sin(radians(180)/N)) + H = sqrt(F**2 - (1.4 * Dr - P/2)**2) + OD = P * (0.6 + 1/tan(radians(180/N))) + + # The sprocket tooth gullet consists of four segments + x0 = 0 + y0 = PD/2 - R + + # ---- Segment 1 ----- + alpha = 35 + 60/N + x1 = -R * cos(radians(alpha)) + y1 = PD/2 - R * sin(radians(alpha)) + arc_end = [x1, y1] + + # ---- Segment 2 ----- + alpha = 35 + 60/N + beta = 18 - 56 / N + x2 = M - E * cos(radians(alpha-beta)) + y2 = T - E * sin(radians(alpha-beta)) + PD/2 + + # # ---- Segment 3 ----- + y2o = y2 - PD/2 + hyp = sqrt((-W-x2)**2 + (-V-y2o)**2) + AP = sqrt(hyp**2 - F**2) + gamma = atan((y2o + V)/(x2 + W)) + alpha = asin(AP / hyp) + beta = 180 - (90 - degrees(alpha)) - (90 - degrees(gamma)) + x3o = AP * sin(radians(beta)) + y3o = AP * cos(radians(beta)) + x3 = x2 - x3o + y3 = y2 + y3o + + # ---- Segment 4 ----- + alpha = 180/N + m = -1/tan(radians(alpha)) + yf = PD/2 - V + A = 1 + m**2 + B = 2*m*yf - 2*W + C = W**2 + yf**2 - F**2 + # x4a = (-B - sqrt(B**2 - 4 * A * C)) / (2*A) + x4b = (-B + sqrt(B**2 - 4 * A * C)) / (2*A) + x4 = -x4b + y4 = m * x4 + + p0 = [x0,y0] + p1 = [x1,y1] + p2 = [x2,y2] + p3 = [x3,y3] + p4 = [x4,y4] + p5 = [-x1,y1] + p6 = [-x2,y2] + p7 = [-x3,y3] + p8 = [-x4,y4] + + w.move(p4) # vectors are lists [x,y] + w.arc(p3, F, 0) + w.line(p2) + w.arc(p1, E, 1) + w.arc(p0, R, 1) + + # ---- Mirror ----- + w.arc(p5, R, 1) + w.arc(p6, E, 1) + w.line(p7) + w.arc(p8, F, 0) + + # ---- Polar Array ---- + alpha = -radians(360/N) + for n in range(1,N): + # falling gullet slope + w.arc(rotate(p3, alpha*n), F, 0) + w.line(rotate(p2, alpha*n)) + w.arc(rotate(p1, alpha*n), E, 1) + w.arc(rotate(p0, alpha*n), R, 1) + + # rising gullet slope + w.arc(rotate(p5, alpha*n), R, 1) + w.line(rotate(p6, alpha*n)) + w.arc(rotate(p7, alpha*n), E, 0) + w.arc(rotate(p8, alpha*n), F, 0) + + w.close() + + return w + + +def rotate(pt, rads): + """ + rotate pt by rads radians about origin + """ + sinA = sin(rads) + cosA = cos(rads) + return (pt[0] * cosA - pt[1] * sinA, + pt[0] * sinA + pt[1] * cosA) + + +