10 Commits

Author SHA1 Message Date
lorenz
e1c80bd963 add spicy dependency 2022-06-11 18:25:41 +02:00
lorenz
109df352a9 InvoluteGearRack: properties_from_tool default = False 2022-05-03 14:48:05 +02:00
looooo
165f1a0aea add package.xml 2022-02-24 08:39:28 +01:00
Jonas Bähr
8258477645 add head parameter for cycloide gear
This is a backport of commit 9ddd493b from develop, with two changes:
- unrelated changes have not been back ported
- backwards compatibility was added

Note that the actual usage of the head value in pygear is wrong, though:
As it is added to the diameter (not the radius) it should have been added
twice. However, as the clearance suffers from the same bug, I choose to
stay consistent. A fix should address both, head and clearance, but this
is out of scope here.
2022-01-04 20:08:35 +01:00
Jonas Bähr
6ce203ea8f Make gear creation "undoable"
Right now, the tool tip is used as transaction name, which matches the
wording "Create a new sketch". There doesn't seem to be consistency
though FreeCAD's other workbenches, though.
2021-10-23 01:43:24 +02:00
Jonas Bähr
69ff30d65e gitignore VIM swap files 2021-10-23 01:43:24 +02:00
lorenz
3c86d12dc7 fix lgtm allert 2021-09-30 18:12:29 +02:00
Jonas Bähr
d489dfb841 Fix crown gear preview mode in PartDesign Bodies
Previously, the `preview_mode` of the crown gear returned a compound of
the base and the cut-outs. This caused problems in PD::Bodies where a
single solid is requried.
The solution in this commit changes the preview_mode to only output the
base, not generating the cutout shapes at all. This is consistent with
the involute gears having "simple=true" and saves again 0.5 Seconds
processsing time on my system using defaults (15 teeth, 4 loft profiles).

In addition, "preview = false" is also speed up by first collecting all
cut-outs, and then passing them all at once to a single cut operation.
This reduced the cutting time from 3.0 Seconds to 2.2 Seconds here.
So preview now generats the shape immediately (0.0008s vs 0.5s) and the
actual crown is generated in 2.7s instead of 3.5s (again, using the
defaut parameters, measued via Python's time.perf_counter).
2021-07-25 16:33:58 +02:00
Jonas Bähr
1b19d16264 Extend the "additiveness" to all gears when used in PD:Bodies
There are still some issues when the generated Shape is not a solid,
e.g. in the preview mode of the Crown Gear.
2021-07-25 16:33:58 +02:00
Jonas Bähr
d5e12ef116 First proof of concept of "additive gears" in PartDesign bodies
In this PoC only the involute gears work, and there is still a lot of
cleanup pending.
What does work, however, is that those gears now play nicely with
PartDesign's concept of stacking features onto each other, i.e. that the
result of a feature is the fusion of all previous ones.

Special Thanks goes to DeepSOIC for his tutorial in the forum at [1] as
well as this Part-o-Matic which showed me how this works in real live [2]
[1]: https://forum.freecadweb.org/viewtopic.php?f=22&t=21097#p163340
[2]: https://github.com/DeepSOIC/Part-o-magic/blob/master/PartOMagic/Features/PartDesign/PDShapeFeature.py
2021-07-25 16:33:58 +02:00
5 changed files with 117 additions and 67 deletions

3
.gitignore vendored
View File

@@ -59,3 +59,6 @@ target/
.ipynb_checkpoints/ .ipynb_checkpoints/
results/ results/
*.vtk *.vtk
# VIM
*.swp

View File

@@ -41,10 +41,13 @@ class BaseCommand(object):
return True return True
def Activated(self): def Activated(self):
doc = FreeCAD.ActiveDocument
Gui.doCommandGui("import freecad.gears.commands") Gui.doCommandGui("import freecad.gears.commands")
doc.openTransaction(self.ToolTip)
Gui.doCommandGui("freecad.gears.commands.{}.create()".format( Gui.doCommandGui("freecad.gears.commands.{}.create()".format(
self.__class__.__name__)) self.__class__.__name__))
FreeCAD.ActiveDocument.recompute() doc.commitTransaction()
doc.recompute()
Gui.SendMsgToActiveView("ViewFit") Gui.SendMsgToActiveView("ViewFit")
@classmethod @classmethod
@@ -63,7 +66,7 @@ class BaseCommand(object):
cls.GEAR_FUNCTION(obj) cls.GEAR_FUNCTION(obj)
if body: if body:
body.Group += [obj] body.addObject(obj)
elif part: elif part:
part.Group += [obj] part.Group += [obj]
else: else:

View File

@@ -87,12 +87,29 @@ class BaseGear(object):
obj.addExtension('Part::AttachExtensionPython') obj.addExtension('Part::AttachExtensionPython')
else: else:
obj.addExtension('Part::AttachExtensionPython', obj) obj.addExtension('Part::AttachExtensionPython', obj)
# unveil the "Placement" property, which seems hidden by default in PartDesign
obj.setEditorMode('Placement', 0) #non-readonly non-hidden
def execute(self, fp): def execute(self, fp):
# checksbackwardcompatibility: # checksbackwardcompatibility:
if not hasattr(fp, "positionBySupport"): if not hasattr(fp, "positionBySupport"):
self.make_attachable(fp) self.make_attachable(fp)
fp.positionBySupport() fp.positionBySupport()
gear_shape = self.generate_gear_shape(fp)
if hasattr(fp, "BaseFeature") and fp.BaseFeature != None:
# we're inside a PartDesign Body, thus need to fuse with the base feature
gear_shape.Placement = fp.Placement # ensure the gear is placed correctly before fusing
result_shape = fp.BaseFeature.Shape.fuse(gear_shape)
result_shape.transformShape(fp.Placement.inverse().toMatrix(), True) # account for setting fp.Shape below moves the shape to fp.Placement, ignoring its previous placement
fp.Shape = result_shape
else:
fp.Shape = gear_shape
def generate_gear_shape(self, fp):
"""
This method has to return the TopoShape of the gear.
"""
raise NotImplementedError("generate_gear_shape not implemented")
class InvoluteGear(BaseGear): class InvoluteGear(BaseGear):
@@ -165,8 +182,7 @@ class InvoluteGear(BaseGear):
obj.addProperty("App::PropertyLength", "df", obj.addProperty("App::PropertyLength", "df",
"computed", "root diameter", 1) "computed", "root diameter", 1)
def execute(self, fp): def generate_gear_shape(self, fp):
super(InvoluteGear, self).execute(fp)
fp.gear.double_helix = fp.double_helix fp.gear.double_helix = fp.double_helix
fp.gear.m_n = fp.module.Value fp.gear.m_n = fp.module.Value
fp.gear.z = fp.teeth fp.gear.z = fp.teeth
@@ -182,6 +198,16 @@ class InvoluteGear(BaseGear):
if "properties_from_tool" in fp.PropertiesList: if "properties_from_tool" in fp.PropertiesList:
fp.gear.properties_from_tool = fp.properties_from_tool fp.gear.properties_from_tool = fp.properties_from_tool
fp.gear._update() fp.gear._update()
# computed properties
fp.dw = "{}mm".format(fp.gear.dw)
fp.transverse_pitch = "{}mm".format(fp.gear.pitch)
# checksbackwardcompatibility:
if not "da" in fp.PropertiesList:
self.add_limiting_diameter_properties(fp)
fp.da = "{}mm".format(fp.gear.da)
fp.df = "{}mm".format(fp.gear.df)
pts = fp.gear.points(num=fp.numpoints) pts = fp.gear.points(num=fp.numpoints)
rotated_pts = pts rotated_pts = pts
rot = rotation(-fp.gear.phipart) rot = rotation(-fp.gear.phipart)
@@ -198,25 +224,16 @@ class InvoluteGear(BaseGear):
wi.append(out.toShape()) wi.append(out.toShape())
wi = Wire(wi) wi = Wire(wi)
if fp.height.Value == 0: if fp.height.Value == 0:
fp.Shape = wi return wi
elif fp.beta.Value == 0: elif fp.beta.Value == 0:
sh = Face(wi) sh = Face(wi)
fp.Shape = sh.extrude(App.Vector(0, 0, fp.height.Value)) return sh.extrude(App.Vector(0, 0, fp.height.Value))
else: else:
fp.Shape = helicalextrusion( return helicalextrusion(
wi, fp.height.Value, fp.height.Value * np.tan(fp.gear.beta) * 2 / fp.gear.d, fp.double_helix) wi, fp.height.Value, fp.height.Value * np.tan(fp.gear.beta) * 2 / fp.gear.d, fp.double_helix)
else: else:
rw = fp.gear.dw / 2 rw = fp.gear.dw / 2
fp.Shape = Part.makeCylinder(rw, fp.height.Value) return Part.makeCylinder(rw, fp.height.Value)
# computed properties
fp.dw = "{}mm".format(fp.gear.dw)
fp.transverse_pitch = "{}mm".format(fp.gear.pitch)
# checksbackwardcompatibility:
if not "da" in fp.PropertiesList:
self.add_limiting_diameter_properties(fp)
fp.da = "{}mm".format(fp.gear.da)
fp.df = "{}mm".format(fp.gear.df)
def __getstate__(self): def __getstate__(self):
return None return None
@@ -270,14 +287,13 @@ class InvoluteGearRack(BaseGear):
obj.beta = '0. deg' obj.beta = '0. deg'
obj.clearance = 0.25 obj.clearance = 0.25
obj.head = 0. obj.head = 0.
obj.properties_from_tool = True obj.properties_from_tool = False
obj.add_endings = True obj.add_endings = True
obj.simplified = False obj.simplified = False
self.obj = obj self.obj = obj
obj.Proxy = self obj.Proxy = self
def execute(self, fp): def generate_gear_shape(self, fp):
super(InvoluteGearRack, self).execute(fp)
fp.rack.m = fp.module.Value fp.rack.m = fp.module.Value
fp.rack.z = fp.teeth fp.rack.z = fp.teeth
fp.rack.pressure_angle = fp.pressure_angle.Value * np.pi / 180. fp.rack.pressure_angle = fp.pressure_angle.Value * np.pi / 180.
@@ -294,13 +310,18 @@ class InvoluteGearRack(BaseGear):
if "simplified" in fp.PropertiesList: if "simplified" in fp.PropertiesList:
fp.rack.simplified = fp.simplified fp.rack.simplified = fp.simplified
fp.rack._update() fp.rack._update()
# computed properties
if "transverse_pitch" in fp.PropertiesList:
fp.transverse_pitch = "{} mm".format(fp.rack.compute_properties()[2])
pts = fp.rack.points() pts = fp.rack.points()
pol = Wire(makePolygon(list(map(fcvec, pts)))) pol = Wire(makePolygon(list(map(fcvec, pts))))
if fp.height.Value == 0: if fp.height.Value == 0:
fp.Shape = pol return pol
elif fp.beta.Value == 0: elif fp.beta.Value == 0:
face = Face(Wire(pol)) face = Face(Wire(pol))
fp.Shape = face.extrude(fcvec([0., 0., fp.height.Value])) return face.extrude(fcvec([0., 0., fp.height.Value]))
elif fp.double_helix: elif fp.double_helix:
beta = fp.beta.Value * np.pi / 180. beta = fp.beta.Value * np.pi / 180.
pol2 = Part.Wire(pol) pol2 = Part.Wire(pol)
@@ -308,16 +329,13 @@ class InvoluteGearRack(BaseGear):
fcvec([0., np.tan(beta) * fp.height.Value / 2, fp.height.Value / 2])) fcvec([0., np.tan(beta) * fp.height.Value / 2, fp.height.Value / 2]))
pol3 = Part.Wire(pol) pol3 = Part.Wire(pol)
pol3.translate(fcvec([0., 0., fp.height.Value])) pol3.translate(fcvec([0., 0., fp.height.Value]))
fp.Shape = makeLoft([pol, pol2, pol3], True, True) return makeLoft([pol, pol2, pol3], True, True)
else: else:
beta = fp.beta.Value * np.pi / 180. beta = fp.beta.Value * np.pi / 180.
pol2 = Part.Wire(pol) pol2 = Part.Wire(pol)
pol2.translate( pol2.translate(
fcvec([0., np.tan(beta) * fp.height.Value, fp.height.Value])) fcvec([0., np.tan(beta) * fp.height.Value, fp.height.Value]))
fp.Shape = makeLoft([pol, pol2], True) return makeLoft([pol, pol2], True)
# computed properties
if "transverse_pitch" in fp.PropertiesList:
fp.transverse_pitch = "{} mm".format(fp.rack.compute_properties()[2])
def __getstate__(self): def __getstate__(self):
return None return None
@@ -396,8 +414,7 @@ class CrownGear(BaseGear):
pts.append(pts[0]) pts.append(pts[0])
return pts return pts
def execute(self, fp): def generate_gear_shape(self, fp):
super(CrownGear, self).execute(fp)
inner_diameter = fp.module.Value * fp.teeth inner_diameter = fp.module.Value * fp.teeth
outer_diameter = inner_diameter + fp.height.Value * 2 outer_diameter = inner_diameter + fp.height.Value * 2
inner_circle = Part.Wire(Part.makeCircle(inner_diameter / 2.)) inner_circle = Part.Wire(Part.makeCircle(inner_diameter / 2.))
@@ -405,6 +422,8 @@ class CrownGear(BaseGear):
inner_circle.reverse() inner_circle.reverse()
face = Part.Face([outer_circle, inner_circle]) face = Part.Face([outer_circle, inner_circle])
solid = face.extrude(App.Vector([0., 0., -fp.thickness.Value])) solid = face.extrude(App.Vector([0., 0., -fp.thickness.Value]))
if fp.preview_mode:
return solid
# cutting obj # cutting obj
alpha_w = np.deg2rad(fp.pressure_angle.Value) alpha_w = np.deg2rad(fp.pressure_angle.Value)
@@ -426,17 +445,11 @@ class CrownGear(BaseGear):
loft = makeLoft(polies, True) loft = makeLoft(polies, True)
rot = App.Matrix() rot = App.Matrix()
rot.rotateZ(2 * np.pi / t) rot.rotateZ(2 * np.pi / t)
if fp.preview_mode: cut_shapes = []
cut_shapes = [solid] for _ in range(t):
for _ in range(t): loft = loft.transformGeometry(rot)
loft = loft.transformGeometry(rot) cut_shapes.append(loft)
cut_shapes.append(loft) return solid.cut(cut_shapes)
fp.Shape = Part.Compound(cut_shapes)
else:
for i in range(t):
loft = loft.transformGeometry(rot)
solid = solid.cut(loft)
fp.Shape = solid
def __getstate__(self): def __getstate__(self):
pass pass
@@ -465,6 +478,7 @@ class CycloidGear(BaseGear):
"App::PropertyBool", "double_helix", "gear_parameter", "double helix") "App::PropertyBool", "double_helix", "gear_parameter", "double helix")
obj.addProperty( obj.addProperty(
"App::PropertyFloat", "clearance", "gear_parameter", "clearance") "App::PropertyFloat", "clearance", "gear_parameter", "clearance")
self._add_head_property(obj)
obj.addProperty("App::PropertyInteger", "numpoints", obj.addProperty("App::PropertyInteger", "numpoints",
"precision", "number of points for spline") "precision", "number of points for spline")
obj.addProperty("App::PropertyAngle", "beta", "gear_parameter", "beta") obj.addProperty("App::PropertyAngle", "beta", "gear_parameter", "beta")
@@ -485,15 +499,24 @@ class CycloidGear(BaseGear):
obj.double_helix = False obj.double_helix = False
obj.Proxy = self obj.Proxy = self
def execute(self, fp): def _add_head_property(self, obj):
super(CycloidGear, self).execute(fp) obj.addProperty("App::PropertyFloat", "head", "gear_parameter",
"head * modul = additional length of addendum")
obj.head = 0.0
def generate_gear_shape(self, fp):
fp.gear.m = fp.module.Value fp.gear.m = fp.module.Value
fp.gear.z = fp.teeth fp.gear.z = fp.teeth
fp.gear.z1 = fp.inner_diameter.Value fp.gear.z1 = fp.inner_diameter.Value
fp.gear.z2 = fp.outer_diameter.Value fp.gear.z2 = fp.outer_diameter.Value
fp.gear.clearance = fp.clearance fp.gear.clearance = fp.clearance
# check backward compatibility:
if not "head" in fp.PropertiesList:
self._add_head_property(fp)
fp.gear.head = fp.head
fp.gear.backlash = fp.backlash.Value fp.gear.backlash = fp.backlash.Value
fp.gear._update() fp.gear._update()
pts = fp.gear.points(num=fp.numpoints) pts = fp.gear.points(num=fp.numpoints)
rotated_pts = pts rotated_pts = pts
rot = rotation(-fp.gear.phipart) rot = rotation(-fp.gear.phipart)
@@ -509,12 +532,12 @@ class CycloidGear(BaseGear):
wi.append(out.toShape()) wi.append(out.toShape())
wi = Wire(wi) wi = Wire(wi)
if fp.height.Value == 0: if fp.height.Value == 0:
fp.Shape = wi return wi
elif fp.beta.Value == 0: elif fp.beta.Value == 0:
sh = Face(wi) sh = Face(wi)
fp.Shape = sh.extrude(App.Vector(0, 0, fp.height.Value)) return sh.extrude(App.Vector(0, 0, fp.height.Value))
else: else:
fp.Shape = helicalextrusion( return helicalextrusion(
wi, fp.height.Value, fp.height.Value * np.tan(fp.beta.Value * np.pi / 180) * 2 / fp.gear.d, fp.double_helix) wi, fp.height.Value, fp.height.Value * np.tan(fp.beta.Value * np.pi / 180) * 2 / fp.gear.d, fp.double_helix)
def __getstate__(self): def __getstate__(self):
@@ -569,8 +592,7 @@ class BevelGear(BaseGear):
self.obj = obj self.obj = obj
obj.Proxy = self obj.Proxy = self
def execute(self, fp): def generate_gear_shape(self, fp):
super(BevelGear, self).execute(fp)
fp.gear.z = fp.teeth fp.gear.z = fp.teeth
fp.gear.module = fp.module.Value fp.gear.module = fp.module.Value
fp.gear.pressure_angle = (90 - fp.pressure_angle.Value) * np.pi / 180. fp.gear.pressure_angle = (90 - fp.pressure_angle.Value) * np.pi / 180.
@@ -618,8 +640,8 @@ class BevelGear(BaseGear):
mat.A33 = -1 mat.A33 = -1
mat.move(fcvec([0, 0, scale_1])) mat.move(fcvec([0, 0, scale_1]))
shape = shape.transformGeometry(mat) shape = shape.transformGeometry(mat)
fp.Shape = shape return shape
# fp.Shape = self.create_teeth(pts, pos1, fp.teeth) # return self.create_teeth(pts, pos1, fp.teeth)
def create_tooth(self): def create_tooth(self):
w = [] w = []
@@ -692,8 +714,7 @@ class WormGear(BaseGear):
self.obj = obj self.obj = obj
obj.Proxy = self obj.Proxy = self
def execute(self, fp): def generate_gear_shape(self, fp):
super(WormGear, self).execute(fp)
m = fp.module.Value m = fp.module.Value
d = fp.diameter.Value d = fp.diameter.Value
t = fp.teeth t = fp.teeth
@@ -761,12 +782,12 @@ class WormGear(BaseGear):
full_wire = Part.Wire(Part.Wire(w_all)) full_wire = Part.Wire(Part.Wire(w_all))
if h == 0: if h == 0:
fp.Shape = full_wire return full_wire
else: else:
shape = helicalextrusion(full_wire, shape = helicalextrusion(full_wire,
h, h,
h * np.tan(beta) * 2 / d) h * np.tan(beta) * 2 / d)
fp.Shape = shape return shape
def __getstate__(self): def __getstate__(self):
return None return None
@@ -823,8 +844,7 @@ class TimingGear(BaseGear):
self.obj = obj self.obj = obj
obj.Proxy = self obj.Proxy = self
def execute(self, fp): def generate_gear_shape(self, fp):
super(TimingGear, self).execute(fp)
# m ... center of arc/circle # m ... center of arc/circle
# r ... radius of arc/circle # r ... radius of arc/circle
# x ... end-point of arc # x ... end-point of arc
@@ -902,9 +922,9 @@ class TimingGear(BaseGear):
wi = Part.Wire(wires) wi = Part.Wire(wires)
if fp.height.Value == 0: if fp.height.Value == 0:
fp.Shape = wi return wi
else: else:
fp.Shape = Part.Face(wi).extrude(App.Vector(0, 0, fp.height)) return Part.Face(wi).extrude(App.Vector(0, 0, fp.height))
def __getstate__(self): def __getstate__(self):
pass pass
@@ -939,8 +959,7 @@ class LanternGear(BaseGear):
self.obj = obj self.obj = obj
obj.Proxy = self obj.Proxy = self
def execute(self, fp): def generate_gear_shape(self, fp):
super(LanternGear, self).execute(fp)
m = fp.module.Value m = fp.module.Value
teeth = fp.teeth teeth = fp.teeth
r_r = fp.bolt_radius.Value r_r = fp.bolt_radius.Value
@@ -995,9 +1014,9 @@ class LanternGear(BaseGear):
wi = Part.Wire(wires) wi = Part.Wire(wires)
if fp.height.Value == 0: if fp.height.Value == 0:
fp.Shape = wi return wi
else: else:
fp.Shape = Part.Face(wi).extrude(App.Vector(0, 0, fp.height)) return Part.Face(wi).extrude(App.Vector(0, 0, fp.height))
def __getstate__(self): def __getstate__(self):
pass pass
@@ -1090,8 +1109,7 @@ class HypoCycloidGear(BaseGear):
x, y = self.to_rect(r, a) x, y = self.to_rect(r, a)
return x, y return x, y
def execute(self,fp): def generate_gear_shape(self, fp):
super(HypoCycloidGear, self).execute(fp)
b = fp.pin_circle_radius b = fp.pin_circle_radius
d = fp.roller_diameter d = fp.roller_diameter
e = fp.eccentricity e = fp.eccentricity
@@ -1198,7 +1216,7 @@ class HypoCycloidGear(BaseGear):
to_be_fused.append(pins); to_be_fused.append(pins);
if to_be_fused: if to_be_fused:
fp.Shape = Part.makeCompound(to_be_fused) return Part.makeCompound(to_be_fused)
def __getstate__(self): def __getstate__(self):
pass pass

25
package.xml Normal file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
<name>freecad.gears workbench</name>
<description>A gear workbench for FreeCAD</description>
<version>1.0</version>
<date>2022-02-07</date>
<maintainer email="sppedflyer@gmail.com">looooo</maintainer>
<license file="LICENSE">GPL 3</license>
<dep>scipy</dep>
<url type="repository" branch="develop">https://github.com/looooo/freecad.gears</url>
<url type="bugtracker">https://github.com/looooo/freecad.gears/issues</url>
<url type="documentation">https://wiki.freecad.org/FCGear_Workbench</url>
<icon>freecad/gears/icons/gearworkbench.svg</icon>
<content>
<workbench>
<classname>GearWorkbench</classname>
<subdirectory>./</subdirectory>
<freecadmin>0.19</freecadmin>
<tag>gear</tag>
<tag>gears</tag>
</workbench>
</content>
</package>

View File

@@ -25,10 +25,11 @@ from ._functions import rotation, reflection
class CycloidTooth(): class CycloidTooth():
def __init__(self, z1=5, z2=5, z=14, m=5, clearance=0.12, backlash=0.00): def __init__(self, z1=5, z2=5, z=14, m=5, clearance=0.12, backlash=0.00, head=0.0):
self.m = m self.m = m
self.z = z self.z = z
self.clearance = clearance self.clearance = clearance
self.head = head
self.backlash = backlash self.backlash = backlash
self.z1 = z1 self.z1 = z1
self.z2 = z2 self.z2 = z2
@@ -39,7 +40,7 @@ class CycloidTooth():
self.d2 = self.z2 * self.m self.d2 = self.z2 * self.m
self.phi = self.m * pi self.phi = self.m * pi
self.d = self.z * self.m self.d = self.z * self.m
self.da = self.d + 2*self.m self.da = self.d + 2*self.m + self.head * self.m
self.di = self.d - 2*self.m - self.clearance * self.m self.di = self.d - 2*self.m - self.clearance * self.m
self.phipart = 2 * pi / self.z self.phipart = 2 * pi / self.z
@@ -103,5 +104,5 @@ class CycloidTooth():
def _update(self): def _update(self):
self.__init__(m=self.m, z=self.z, z1=self.z1, z2=self.z2, self.__init__(m=self.m, z=self.z, z1=self.z1, z2=self.z2,
clearance=self.clearance, backlash=self.backlash) clearance=self.clearance, backlash=self.backlash, head=self.head)