2019-10-25 17:21:01 -07:00
/ *
Involute Spur Gear Builder ( c ) 2014 Dr . Rainer Hessmer
2019-11-04 08:36:26 -08:00
ported to C # 2019 by Lars Brubaker
2019-10-25 17:21:01 -07:00
Redistribution and use in source and binary forms , with or without
modification , are permitted provided that the following conditions are met :
1. Redistributions of source code must retain the above copyright notice , this
list of conditions and the following disclaimer .
2. Redistributions in binary form must reproduce the above copyright notice ,
this list of conditions and the following disclaimer in the documentation
and / or other materials provided with the distribution .
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ;
LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies ,
either expressed or implied , of the FreeBSD Project .
* /
using System ;
using System.Collections.Generic ;
using ClipperLib ;
using MatterHackers.Agg ;
using MatterHackers.Agg.Transform ;
using MatterHackers.Agg.VertexSource ;
using MatterHackers.DataConverters2D ;
using MatterHackers.VectorMath ;
namespace MatterHackers.MatterControl.DesignTools
{
2019-11-04 08:36:26 -08:00
// Involute Spur Gear Builder
// For calculating and drawing involute spur gears.
// As an improvement over the majority of other freely available scripts and utilities it fully accounts for undercuts.
// For additional information please head over to:
// http://www.hessmer.org/blog/2014/01/01/online-involute-spur-gear-builder part 1
// http://www.hessmer.org/blog/2015/07/13/online-involute-spur-gear-builder-part-2/ part 2
// The implementation is inspired by the subtractive process that Michal Zalewski's describes in
// http://lcamtuf.coredump.cx/gcnc/ch6/#6.2 part six of his excellent
// http://lcamtuf.coredump.cx/gcnc/ Guerrilla guide to CNC machining, mold making, and resin casting
2019-10-25 17:21:01 -07:00
public class Gear2D : VertexSourceLegacySupport
{
2019-11-19 22:17:53 -08:00
private double _backlash = . 05 ;
private double _centerHoleDiameter = 4 ;
2019-10-25 23:11:43 -07:00
private double _circularPitch = 8 ;
2019-11-19 22:17:53 -08:00
private double _clearance = . 05 ;
// Most common stock gears have a 20° pressure angle, with 14½° and 25° pressure angle gears being much less
// common. Increasing the pressure angle increases the width of the base of the gear tooth, leading to greater strength and load carrying capacity. Decreasing
// the pressure angle provides lower backlash, smoother operation and less sensitivity to manufacturing errors. (reference: http://en.wikipedia.org/wiki/Involute_gear)
private double _pressureAngle = 20 ;
private int _toothCount = 30 ;
private double addendum ;
private double angleToothToTooth ;
private Vector2 center = Vector2 . Zero ;
private Gear2D connectedGear ;
private double diametralPitch ;
private double outerRadius ;
private double pitchDiameter ;
private double pitchRadius ;
private double profileShift = 0 ;
private double shiftedAddendum ;
private int stepsPerToothAngle = 10 ;
public Gear2D ( )
{
CalculateDependants ( ) ;
}
public enum GearTypes
{
External ,
Internal ,
Rack
}
public double Backlash
{
get = > _backlash ;
set
{
_backlash = value ;
CalculateDependants ( ) ;
}
}
public double CenterHoleDiameter
{
get = > _centerHoleDiameter ;
set
{
_centerHoleDiameter = value ;
CalculateDependants ( ) ;
}
}
2019-10-25 23:11:43 -07:00
/// <summary>
/// Gets or sets distance from one face of a tooth to the corresponding face of an adjacent tooth on the same gear, measured along the pitch circle.
/// </summary>
public double CircularPitch
{
get = > _circularPitch ;
set
{
_circularPitch = value ;
CalculateDependants ( ) ;
}
}
2019-11-12 10:53:58 -08:00
public double Clearance
{
get = > _clearance ;
set
{
_clearance = value ;
CalculateDependants ( ) ;
}
}
2019-10-25 23:11:43 -07:00
2019-11-19 22:17:53 -08:00
private GearTypes _gearType = GearTypes . External ;
public GearTypes GearType
2019-11-12 10:53:58 -08:00
{
2019-11-19 22:17:53 -08:00
get = > _gearType ;
2019-11-12 10:53:58 -08:00
set
{
2019-11-19 22:17:53 -08:00
_gearType = value ;
2019-11-12 10:53:58 -08:00
CalculateDependants ( ) ;
}
}
2019-10-25 23:11:43 -07:00
2019-11-19 22:17:53 -08:00
public double PressureAngle
2019-11-12 10:53:58 -08:00
{
2019-11-19 22:17:53 -08:00
get = > _pressureAngle ;
2019-11-12 10:53:58 -08:00
set
{
2019-11-19 22:17:53 -08:00
_pressureAngle = value ;
2019-11-12 10:53:58 -08:00
CalculateDependants ( ) ;
}
}
2019-11-04 08:36:26 -08:00
public int ToothCount
{
get = > _toothCount ;
set
{
_toothCount = value ;
CalculateDependants ( ) ;
}
}
2019-12-06 10:18:58 -08:00
private int _internalToothCount ;
public int InternalToothCount
{
get = > _internalToothCount ;
set
{
_internalToothCount = value ;
CalculateDependants ( ) ;
}
}
2019-11-19 22:17:53 -08:00
public override IEnumerable < VertexData > Vertices ( )
2019-11-12 10:53:58 -08:00
{
2019-11-19 22:17:53 -08:00
IVertexSource shape = null ;
2019-11-12 10:53:58 -08:00
2019-11-19 22:17:53 -08:00
switch ( GearType )
2019-11-12 10:53:58 -08:00
{
2019-11-19 22:17:53 -08:00
case GearTypes . External :
shape = CreateExternalGearShape ( ) ;
break ;
case GearTypes . Internal :
shape = CreateInternalGearShape ( ) ;
break ;
case GearTypes . Rack :
shape = CreateRackShape ( ) ;
break ;
2019-11-12 10:53:58 -08:00
}
2019-10-25 17:21:01 -07:00
2019-11-19 22:17:53 -08:00
return shape . Vertices ( ) ;
2019-10-25 17:21:01 -07:00
}
2019-11-19 22:17:53 -08:00
private IVertexSource CreateInternalToothCutter ( )
2019-10-25 23:11:43 -07:00
{
2019-11-19 22:17:53 -08:00
// To cut the internal gear teeth, the actual pinion comes close but we need to enlarge it so properly caters for clearance and backlash
var pinion = this . connectedGear ;
var enlargedPinion = new Gear2D ( )
{
CircularPitch = pinion . CircularPitch ,
2019-11-20 08:10:31 -08:00
PressureAngle = pinion . PressureAngle ,
Clearance = - pinion . Clearance ,
Backlash = - pinion . Backlash ,
ToothCount = pinion . ToothCount ,
CenterHoleDiameter = 0 ,
profileShift = pinion . profileShift ,
stepsPerToothAngle = pinion . stepsPerToothAngle
2019-11-19 22:17:53 -08:00
} ;
2019-11-20 08:10:31 -08:00
enlargedPinion . CalculateDependants ( ) ;
2019-11-19 22:17:53 -08:00
var tooth = enlargedPinion . CreateSingleTooth ( ) ;
return tooth . tooth . RotateZDegrees ( 90 + 180 / enlargedPinion . ToothCount ) ; // we need a tooth pointing to the left
2019-10-25 23:11:43 -07:00
}
2019-11-19 22:17:53 -08:00
private IVertexSource CreateInternalToothProfile ( )
2019-10-25 17:21:01 -07:00
{
2019-11-19 22:17:53 -08:00
var radius = this . pitchRadius + ( 1 - this . profileShift ) * this . addendum + this . Clearance ;
var angleToothToTooth = 360 / this . ToothCount ;
var sin = Math . Sin ( angleToothToTooth / 2 * Math . PI / 180 ) ;
var cos = Math . Cos ( angleToothToTooth / 2 * Math . PI / 180 ) ;
2019-10-25 17:21:01 -07:00
2019-11-19 22:17:53 -08:00
var fullSector = new VertexStorage ( ) ;
2019-10-25 17:21:01 -07:00
2019-11-19 22:17:53 -08:00
fullSector . MoveTo ( 0 , 0 ) ;
fullSector . LineTo ( - ( radius * cos ) , radius * sin ) ;
fullSector . LineTo ( - radius , 0 ) ;
fullSector . LineTo ( - ( radius * cos ) , - radius * sin ) ;
2019-10-25 17:21:01 -07:00
2019-11-19 22:17:53 -08:00
var innerRadius = radius - ( 2 * this . addendum + this . Clearance ) ;
var innerCircle = new Ellipse ( this . center , innerRadius )
{
ResolutionScale = 10
} ;
2019-10-25 17:21:01 -07:00
2019-11-19 22:17:53 -08:00
var sector = fullSector . Subtract ( innerCircle ) ;
2019-10-25 17:21:01 -07:00
2019-11-19 22:17:53 -08:00
var cutterTemplate = this . CreateInternalToothCutter ( ) ;
var pinion = this . connectedGear ;
var stepsPerTooth = this . stepsPerToothAngle ;
var angleStepSize = angleToothToTooth / stepsPerTooth ;
var toothShape = sector ;
var cutter = cutterTemplate . Translate ( - this . pitchRadius + this . connectedGear . pitchRadius , 0 ) ;
toothShape = toothShape . Subtract ( cutter ) ;
for ( var i = 1 ; i < stepsPerTooth ; i + + )
{
var pinionRotationAngle = i * angleStepSize ;
var pinionCenterRayAngle = - pinionRotationAngle * pinion . ToothCount / this . ToothCount ;
cutter = cutterTemplate . RotateZDegrees ( pinionRotationAngle ) ;
cutter = cutter . Translate ( - this . pitchRadius + this . connectedGear . pitchRadius , 0 ) ;
cutter = cutter . RotateZDegrees ( pinionCenterRayAngle ) ;
toothShape = toothShape . Subtract ( cutter ) ;
cutter = cutterTemplate . RotateZDegrees ( - pinionRotationAngle ) ;
cutter = cutter . Translate ( - this . pitchRadius + this . connectedGear . pitchRadius , 0 ) ;
cutter = cutter . RotateZDegrees ( - pinionCenterRayAngle ) ;
toothShape = toothShape . Subtract ( cutter ) ;
}
return toothShape ;
2019-10-25 17:21:01 -07:00
}
2019-11-19 22:17:53 -08:00
private IVertexSource SmoothConcaveCorners ( IVertexSource corners )
2019-10-27 16:01:49 -07:00
{
2019-11-19 22:17:53 -08:00
// removes single concave corners located between convex corners
return this . SmoothCorners ( corners , false ) ; // removeSingleConvex
2019-10-27 16:01:49 -07:00
}
2019-11-19 22:17:53 -08:00
private IVertexSource SmoothConvexCorners ( IVertexSource corners )
2019-10-25 17:21:01 -07:00
{
2019-11-19 22:17:53 -08:00
// removes single convex corners located between concave corners
return this . SmoothCorners ( corners , true ) ; // removeSingleConvex
}
2019-10-25 17:21:01 -07:00
2019-11-19 22:17:53 -08:00
private IVertexSource SmoothCorners ( IVertexSource corners_in , bool removeSingleConvex )
{
var corners = corners_in as VertexStorage ;
var isConvex = new List < bool > ( ) ;
var previousCorner = corners [ corners . Count - 1 ] ;
var currentCorner = corners [ 0 ] ;
for ( var i = 0 ; i < corners . Count ; i + + )
2019-10-25 17:21:01 -07:00
{
2019-11-19 22:17:53 -08:00
var nextCorner = corners [ ( i + 1 ) % corners . Count ] ;
var v1 = previousCorner . position - currentCorner . position ;
var v2 = nextCorner . position - currentCorner . position ;
var crossProduct = v1 . Cross ( v2 ) ;
isConvex . Add ( crossProduct < 0 ) ;
previousCorner = currentCorner ;
currentCorner = nextCorner ;
2019-10-25 17:21:01 -07:00
}
2019-11-19 22:17:53 -08:00
// we want to remove any concave corners that are located between two convex corners
var cleanedUpCorners = new VertexStorage ( ) ;
var previousIndex = corners . Count - 1 ;
var currentIndex = 0 ;
for ( var i = 0 ; i < corners . Count ; i + + )
{
var corner = corners [ currentIndex ] ;
var nextIndex = ( i + 1 ) % corners . Count ;
2019-10-25 17:21:01 -07:00
2019-11-19 22:17:53 -08:00
var isSingleConcave = ! isConvex [ currentIndex ] & & isConvex [ previousIndex ] & & isConvex [ nextIndex ] ;
var isSingleConvex = isConvex [ currentIndex ] & & ! isConvex [ previousIndex ] & & ! isConvex [ nextIndex ] ;
previousIndex = currentIndex ;
currentIndex = nextIndex ;
if ( removeSingleConvex & & isSingleConvex )
{
continue ;
}
if ( ! removeSingleConvex & & isSingleConcave )
{
continue ;
}
cleanedUpCorners . Add ( corner . X , corner . Y , corner . command ) ;
}
return cleanedUpCorners ;
2019-10-25 17:21:01 -07:00
}
2019-11-19 22:17:53 -08:00
private void CalculateDependants ( )
2019-10-26 08:45:11 -07:00
{
2019-11-19 22:17:53 -08:00
// convert circular pitch to diametral pitch
this . diametralPitch = Math . PI / this . CircularPitch ; // Ratio of the number of teeth to the pitch diameter
// this.circularPitch = Math.PI / this.diametralPitch;
2019-10-26 08:45:11 -07:00
2019-11-19 22:17:53 -08:00
this . center = Vector2 . Zero ; // center of the gear
// this.angle = 0; // angle in degrees of the complete gear (changes during rotation animation)
2019-10-26 08:45:11 -07:00
2019-11-19 22:17:53 -08:00
// Pitch diameter: Diameter of pitch circle.
this . pitchDiameter = this . ToothCount / this . diametralPitch ;
this . pitchRadius = this . pitchDiameter / 2 ;
2019-10-26 08:45:11 -07:00
2019-11-19 22:17:53 -08:00
// Addendum: Radial distance from pitch circle to outside circle.
this . addendum = 1 / this . diametralPitch ;
2019-10-26 08:45:11 -07:00
2019-11-19 22:17:53 -08:00
// Typically no profile shift is used meaning that this.shiftedAddendum = this.addendum
this . shiftedAddendum = this . addendum * ( 1 + this . profileShift ) ;
2019-10-26 08:45:11 -07:00
2019-11-19 22:17:53 -08:00
// Outer Circle
this . outerRadius = this . pitchRadius + this . shiftedAddendum ;
this . angleToothToTooth = 360.0 / this . ToothCount ;
2019-12-06 10:18:58 -08:00
if ( InternalToothCount > 0 )
{
connectedGear = new Gear2D ( )
{
ToothCount = this . InternalToothCount ,
CircularPitch = this . CircularPitch ,
CenterHoleDiameter = this . CenterHoleDiameter ,
PressureAngle = this . PressureAngle ,
Backlash = this . Backlash ,
Clearance = this . Clearance ,
GearType = this . GearType ,
} ;
}
2019-11-04 08:36:26 -08:00
}
private IVertexSource CreateInternalGearShape ( )
{
2019-11-19 22:17:53 -08:00
var singleTooth = this . CreateInternalToothProfile ( ) ;
2019-11-04 08:36:26 -08:00
// return singleTooth;
2019-12-06 10:18:58 -08:00
var corners = singleTooth as VertexStorage ;
2019-11-04 08:36:26 -08:00
// first we need to find the corner that sits at the center
2019-11-08 22:52:33 -08:00
var centerCornerIndex = 0 ;
2019-11-12 10:53:58 -08:00
var radius = this . pitchRadius + ( 1 + this . profileShift ) * this . addendum + this . Clearance ;
2019-11-04 08:36:26 -08:00
var delta = 0.0000001 ;
2019-11-08 22:52:33 -08:00
for ( var i = 0 ; i < corners . Count ; i + + )
2019-11-04 08:36:26 -08:00
{
var corner = corners [ i ] ;
2019-11-08 22:52:33 -08:00
if ( corner . Y < delta & & ( corner . X + radius ) < delta )
2019-11-04 08:36:26 -08:00
{
centerCornerIndex = i ;
break ;
}
}
2019-11-08 22:52:33 -08:00
var outerCorners = new VertexStorage ( ) ;
2019-12-06 10:18:58 -08:00
var command = ShapePath . FlagsAndCommand . MoveTo ;
2019-11-08 22:52:33 -08:00
for ( var i = 2 ; i < corners . Count - 2 ; i + + )
2019-11-04 08:36:26 -08:00
{
2019-11-08 22:52:33 -08:00
var corner = corners [ ( i + centerCornerIndex ) % corners . Count ] ;
2019-12-06 10:18:58 -08:00
outerCorners . Add ( corner . position . X , corner . position . Y , command ) ;
command = ShapePath . FlagsAndCommand . LineTo ;
2019-11-04 08:36:26 -08:00
}
2019-11-08 22:52:33 -08:00
var reversedOuterCorners = new VertexStorage ( ) ;
foreach ( var vertex in new ReversePath ( outerCorners ) . Vertices ( ) )
{
2019-12-06 10:18:58 -08:00
reversedOuterCorners . Add ( vertex . position . X , vertex . position . Y , ShapePath . FlagsAndCommand . LineTo ) ;
2019-11-08 22:52:33 -08:00
}
2019-11-19 22:17:53 -08:00
2019-11-08 22:52:33 -08:00
outerCorners = reversedOuterCorners ;
2019-11-04 08:36:26 -08:00
2019-11-08 22:52:33 -08:00
var cornersCount = outerCorners . Count ;
for ( var i = 1 ; i < this . ToothCount ; i + + )
2019-11-04 08:36:26 -08:00
{
var angle = i * this . angleToothToTooth ;
2019-11-08 22:52:33 -08:00
var roatationMatrix = Affine . NewRotation ( MathHelper . DegreesToRadians ( angle ) ) ;
2019-11-04 08:36:26 -08:00
for ( var j = 0 ; j < cornersCount ; j + + )
{
2019-11-08 22:52:33 -08:00
var rotatedCorner = roatationMatrix . Transform ( outerCorners [ j ] . position ) ;
2019-12-06 10:18:58 -08:00
outerCorners . Add ( rotatedCorner . X , rotatedCorner . Y , ShapePath . FlagsAndCommand . LineTo ) ;
2019-11-04 08:36:26 -08:00
}
}
2019-11-19 22:17:53 -08:00
outerCorners = this . SmoothConcaveCorners ( outerCorners ) as VertexStorage ;
2019-11-04 08:36:26 -08:00
2019-11-12 10:53:58 -08:00
var innerRadius = this . pitchRadius + ( 1 - this . profileShift ) * this . addendum + this . Clearance ;
2019-11-04 08:36:26 -08:00
var outerRadius = innerRadius + 4 * this . addendum ;
2019-11-08 22:52:33 -08:00
var outerCircle = new Ellipse ( this . center , outerRadius , outerRadius ) ;
2019-11-04 08:36:26 -08:00
2019-12-06 10:18:58 -08:00
// return outerCorners;
2019-11-08 22:52:33 -08:00
return outerCircle . Subtract ( outerCorners ) ;
2019-10-26 08:45:11 -07:00
}
2019-11-19 22:17:53 -08:00
private IVertexSource CreateRackShape ( )
{
IVertexSource rack = new VertexStorage ( ) ;
for ( var i = 0 ; i < ToothCount ; i + + )
{
var tooth = this . CreateRackTooth ( ) ;
2019-11-19 22:26:17 -08:00
tooth = tooth . Translate ( 0 , ( 0.5 + - ToothCount / 2.0 + i ) * this . CircularPitch ) ;
2019-11-19 22:17:53 -08:00
rack = rack . Union ( tooth ) ;
}
// creating the bar backing the teeth
var rightX = - ( this . addendum + this . Clearance ) ;
var width = 4 * this . addendum ;
2019-11-19 22:26:17 -08:00
var halfHeight = ToothCount * this . CircularPitch / 2.0 ;
2019-11-19 22:17:53 -08:00
var bar = new RoundedRect ( rightX - width , - halfHeight , rightX , halfHeight , 0 ) ;
var rackFinal = rack . Union ( bar ) as VertexStorage ;
rackFinal . Translate ( this . addendum * this . profileShift , 0 ) ;
return rackFinal ;
}
2019-10-25 23:11:43 -07:00
private IVertexSource CreateRackTooth ( )
2019-10-25 17:21:01 -07:00
{
2019-10-25 23:11:43 -07:00
var toothWidth = this . CircularPitch / 2 ;
2019-11-12 10:53:58 -08:00
var toothDepth = this . addendum + this . Clearance ;
2019-10-25 17:21:01 -07:00
2019-11-12 10:53:58 -08:00
var sinPressureAngle = Math . Sin ( this . PressureAngle * Math . PI / 180 ) ;
var cosPressureAngle = Math . Cos ( this . PressureAngle * Math . PI / 180 ) ;
2019-10-25 17:21:01 -07:00
// if a positive backlash is defined then we widen the trapezoid accordingly.
// Each side of the tooth needs to widened by a fourth of the backlash (vertical to cutter faces).
2019-11-12 10:53:58 -08:00
var dx = this . Backlash / 4 / cosPressureAngle ;
2019-10-25 17:21:01 -07:00
2019-11-12 10:53:58 -08:00
var leftDepth = this . addendum + this . Clearance ;
2019-10-25 17:21:01 -07:00
2019-11-12 10:53:58 -08:00
var upperLeftCorner = new Vector2 ( - leftDepth , toothWidth / 2 - dx + ( this . addendum + this . Clearance ) * sinPressureAngle ) ;
2019-10-25 17:21:01 -07:00
var upperRightCorner = new Vector2 ( this . addendum , toothWidth / 2 - dx - this . addendum * sinPressureAngle ) ;
var lowerRightCorner = new Vector2 ( upperRightCorner [ 0 ] , - upperRightCorner [ 1 ] ) ;
var lowerLeftCorner = new Vector2 ( upperLeftCorner [ 0 ] , - upperLeftCorner [ 1 ] ) ;
var tooth = new VertexStorage ( ) ;
tooth . MoveTo ( upperLeftCorner ) ;
tooth . LineTo ( upperRightCorner ) ;
tooth . LineTo ( lowerRightCorner ) ;
tooth . LineTo ( lowerLeftCorner ) ;
return tooth ;
}
2019-11-19 22:17:53 -08:00
private IVertexSource CreateExternalGearShape ( )
{
var tooth = this . CreateSingleTooth ( ) ;
// we could now take the tooth cutout, rotate it tooth count times and union the various slices together into a complete gear.
// However, the union operations become more and more complex as the complete gear is built up.
// So instead we capture the outer path of the tooth and concatenate rotated versions of this path into a complete outer gear path.
// Concatenating paths is inexpensive resulting in significantly faster execution.
var outlinePaths = tooth . tooth ;
// first we need to find the corner that sits at the center
for ( var i = 1 ; i < this . ToothCount ; i + + )
{
var angle = i * this . angleToothToTooth ;
var roatationMatrix = Affine . NewRotation ( MathHelper . DegreesToRadians ( angle ) ) ;
var rotatedCorner = new VertexSourceApplyTransform ( tooth . tooth , roatationMatrix ) ;
outlinePaths = new CombinePaths ( outlinePaths , rotatedCorner ) ;
}
var gearShape = tooth . wheel . Subtract ( outlinePaths ) ;
if ( this . CenterHoleDiameter > 0 )
{
var radius = this . CenterHoleDiameter / 2 ;
var centerhole = new Ellipse ( 0 , 0 , radius , radius )
{
ResolutionScale = 10
} ;
gearShape = gearShape . Subtract ( centerhole ) as VertexStorage ;
}
return gearShape ; // .RotateZDegrees(-90);
}
2019-10-27 16:01:49 -07:00
private ( IVertexSource tooth , IVertexSource wheel ) CreateSingleTooth ( )
2019-10-25 17:21:01 -07:00
{
// create outer circle sector covering one tooth
2019-11-04 08:36:26 -08:00
var toothSectorPath = new Arc ( Vector2 . Zero , new Vector2 ( this . outerRadius , this . outerRadius ) , MathHelper . DegreesToRadians ( 90 ) , MathHelper . DegreesToRadians ( 90 - this . angleToothToTooth ) )
{
ResolutionScale = 10
} ;
2019-10-25 17:21:01 -07:00
2019-10-27 16:01:49 -07:00
var toothCutOut = CreateToothCutout ( ) ;
2019-10-25 17:21:01 -07:00
2019-10-27 16:01:49 -07:00
return ( toothCutOut , toothSectorPath ) ;
2019-10-25 17:21:01 -07:00
}
2019-10-25 23:11:43 -07:00
private IVertexSource CreateToothCutout ( )
2019-10-25 17:21:01 -07:00
{
2019-11-10 21:16:49 -08:00
var angleToothToTooth = 360.0 / this . ToothCount ;
2019-10-25 17:21:01 -07:00
var angleStepSize = this . angleToothToTooth / this . stepsPerToothAngle ;
2019-10-26 08:45:11 -07:00
IVertexSource toothCutout = new VertexStorage ( ) ;
2019-10-25 17:21:01 -07:00
2019-10-25 23:11:43 -07:00
var toothCutterShape = this . CreateToothCutter ( ) ;
2019-10-25 17:21:01 -07:00
var bounds = toothCutterShape . GetBounds ( ) ;
var lowerLeftCorner = new Vector2 ( bounds . Left , bounds . Bottom ) ;
// To create the tooth profile we move the (virtual) infinite gear and then turn the resulting cutter position back.
// For illustration see http://lcamtuf.coredump.cx/gcnc/ch6/, section 'Putting it all together'
// We continue until the moved tooth cutter's lower left corner is outside of the outer circle of the gear.
// Going any further will no longer influence the shape of the tooth
var stepCounter = 0 ;
while ( true )
{
var angle = stepCounter * angleStepSize ;
var xTranslation = new Vector2 ( angle * Math . PI / 180 * this . pitchRadius , 0 ) ;
var movedLowerLeftCorner = lowerLeftCorner + xTranslation ;
2019-10-27 16:01:49 -07:00
movedLowerLeftCorner = Vector2 . Rotate ( movedLowerLeftCorner , MathHelper . DegreesToRadians ( angle ) ) ;
2019-10-25 17:21:01 -07:00
2019-10-25 23:11:43 -07:00
if ( movedLowerLeftCorner . Length > this . outerRadius )
2019-10-26 08:45:11 -07:00
{
2019-10-25 17:21:01 -07:00
// the cutter is now completely outside the gear and no longer influences the shape of the gear tooth
break ;
}
// we move in both directions
2019-10-25 23:11:43 -07:00
var movedToothCutterShape = toothCutterShape . Translate ( xTranslation ) ;
2019-10-27 16:01:49 -07:00
movedToothCutterShape = movedToothCutterShape . RotateZDegrees ( angle ) ;
2019-10-25 17:21:01 -07:00
toothCutout = toothCutout . Union ( movedToothCutterShape ) ;
2019-10-25 23:11:43 -07:00
if ( xTranslation [ 0 ] > 0 )
2019-10-26 08:45:11 -07:00
{
2019-10-25 23:11:43 -07:00
movedToothCutterShape = toothCutterShape . Translate ( new Vector2 ( - xTranslation [ 0 ] , xTranslation [ 1 ] ) ) ;
2019-10-27 16:01:49 -07:00
movedToothCutterShape = movedToothCutterShape . RotateZDegrees ( - angle ) ;
2019-10-25 17:21:01 -07:00
toothCutout = toothCutout . Union ( movedToothCutterShape ) ;
}
stepCounter + + ;
}
2019-11-19 22:17:53 -08:00
toothCutout = this . SmoothConcaveCorners ( toothCutout ) ;
2019-10-25 17:21:01 -07:00
2019-10-27 16:01:49 -07:00
return toothCutout . RotateZDegrees ( - this . angleToothToTooth / 2 ) ;
2019-10-25 17:21:01 -07:00
}
2019-10-25 23:11:43 -07:00
private IVertexSource CreateToothCutter ( )
2019-10-25 17:21:01 -07:00
{
// we create a trapezoidal cutter as described at http://lcamtuf.coredump.cx/gcnc/ch6/ under the section 'Putting it all together'
2019-10-25 23:11:43 -07:00
var toothWidth = this . CircularPitch / 2 ;
2019-10-25 17:21:01 -07:00
2019-11-12 10:53:58 -08:00
var cutterDepth = this . addendum + this . Clearance ;
2019-10-25 17:21:01 -07:00
var cutterOutsideLength = 3 * this . addendum ;
2019-11-12 10:53:58 -08:00
var sinPressureAngle = Math . Sin ( this . PressureAngle * Math . PI / 180.0 ) ;
var cosPressureAngle = Math . Cos ( this . PressureAngle * Math . PI / 180.0 ) ;
2019-10-25 17:21:01 -07:00
// if a positive backlash is defined then we widen the trapezoid accordingly.
// Each side of the tooth needs to widened by a fourth of the backlash (vertical to cutter faces).
2019-11-12 10:53:58 -08:00
var dx = this . Backlash / 2 / cosPressureAngle ;
2019-10-25 17:21:01 -07:00
var lowerRightCorner = new Vector2 ( toothWidth / 2 + dx - cutterDepth * sinPressureAngle , this . pitchRadius + this . profileShift * this . addendum - cutterDepth ) ;
var upperRightCorner = new Vector2 ( toothWidth / 2 + dx + cutterOutsideLength * sinPressureAngle , this . pitchRadius + this . profileShift * this . addendum + cutterOutsideLength ) ;
var upperLeftCorner = new Vector2 ( - upperRightCorner [ 0 ] , upperRightCorner [ 1 ] ) ;
var lowerLeftCorner = new Vector2 ( - lowerRightCorner [ 0 ] , lowerRightCorner [ 1 ] ) ;
var cutterPath = new VertexStorage ( ) ;
cutterPath . MoveTo ( lowerLeftCorner ) ;
cutterPath . LineTo ( upperLeftCorner ) ;
cutterPath . LineTo ( upperRightCorner ) ;
cutterPath . LineTo ( lowerRightCorner ) ;
return cutterPath ;
}
}
}
public static class Extensions
{
2019-11-19 22:17:53 -08:00
public static IVertexSource Minus ( this IVertexSource a , IVertexSource b )
2019-10-25 17:21:01 -07:00
{
2019-11-19 22:17:53 -08:00
return CombinePaths ( a , b , ClipType . ctDifference ) ;
2019-10-25 17:21:01 -07:00
}
2019-11-19 22:17:53 -08:00
public static IVertexSource Plus ( this IVertexSource a , IVertexSource b )
2019-10-25 17:21:01 -07:00
{
2019-11-19 22:17:53 -08:00
return CombinePaths ( a , b , ClipType . ctUnion ) ;
2019-10-25 17:21:01 -07:00
}
2019-10-27 16:01:49 -07:00
public static IVertexSource RotateZDegrees ( this IVertexSource a , double angle )
2019-10-25 17:21:01 -07:00
{
2019-10-27 16:01:49 -07:00
return new VertexSourceApplyTransform ( a , Affine . NewRotation ( MathHelper . DegreesToRadians ( angle ) ) ) ;
2019-10-25 17:21:01 -07:00
}
2019-11-19 22:17:53 -08:00
public static IVertexSource Subtract ( this IVertexSource a , IVertexSource b )
2019-10-25 17:21:01 -07:00
{
2019-11-19 22:17:53 -08:00
return a . Minus ( b ) ;
2019-10-25 17:21:01 -07:00
}
2019-11-19 22:17:53 -08:00
public static IVertexSource Translate ( this IVertexSource a , Vector2 delta )
2019-10-25 17:21:01 -07:00
{
2019-11-19 22:17:53 -08:00
return new VertexSourceApplyTransform ( a , Affine . NewTranslation ( delta ) ) ;
2019-10-25 17:21:01 -07:00
}
2019-11-19 22:17:53 -08:00
public static IVertexSource Union ( this IVertexSource a , IVertexSource b )
2019-10-25 17:21:01 -07:00
{
2019-11-19 22:17:53 -08:00
return a . Plus ( b ) ;
2019-10-25 17:21:01 -07:00
}
private static VertexStorage CombinePaths ( IVertexSource a , IVertexSource b , ClipType clipType )
{
List < List < IntPoint > > aPolys = a . CreatePolygons ( ) ;
List < List < IntPoint > > bPolys = b . CreatePolygons ( ) ;
2019-11-08 22:52:33 -08:00
var clipper = new Clipper ( ) ;
2019-10-25 17:21:01 -07:00
clipper . AddPaths ( aPolys , PolyType . ptSubject , true ) ;
clipper . AddPaths ( bPolys , PolyType . ptClip , true ) ;
2019-11-08 22:52:33 -08:00
var outputPolys = new List < List < IntPoint > > ( ) ;
2019-10-25 23:11:43 -07:00
clipper . Execute ( clipType , outputPolys ) ;
2019-10-25 17:21:01 -07:00
2019-10-25 23:11:43 -07:00
Clipper . CleanPolygons ( outputPolys ) ;
2019-10-25 17:21:01 -07:00
2019-10-25 23:11:43 -07:00
VertexStorage output = outputPolys . CreateVertexStorage ( ) ;
2019-10-25 17:21:01 -07:00
output . Add ( 0 , 0 , ShapePath . FlagsAndCommand . Stop ) ;
return output ;
}
2019-11-04 08:36:26 -08:00
}