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 ;
2020-06-16 15:55:04 -07:00
using System.Linq ;
2019-10-25 17:21:01 -07:00
using ClipperLib ;
using MatterHackers.Agg ;
using MatterHackers.Agg.Transform ;
using MatterHackers.Agg.VertexSource ;
using MatterHackers.DataConverters2D ;
using MatterHackers.VectorMath ;
2020-06-15 09:05:39 -07:00
using Polygon = System . Collections . Generic . List < ClipperLib . IntPoint > ;
using Polygons = System . Collections . Generic . List < System . Collections . Generic . List < ClipperLib . IntPoint > > ;
2019-10-25 17:21:01 -07:00
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 ;
2020-06-12 20:21:52 -07:00
private double AngleToothToTooth = > 360.0 / this . ToothCount ;
2019-11-19 22:17:53 -08:00
private Vector2 center = Vector2 . Zero ;
private double diametralPitch ;
2020-05-23 17:03:52 -07:00
public double OuterRadius { get ; private set ; }
2019-11-19 22:17:53 -08:00
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
}
2020-06-15 09:05:39 -07:00
private IntPoint ScalledPoint ( double x , double y , double scale )
{
return new IntPoint ( x * scale , y * scale ) ;
}
2020-06-16 15:55:04 -07:00
private IntPoint ScalledPoint ( Vector2 position , double scale )
{
return new IntPoint ( position . X * scale , position . Y * scale ) ;
}
2020-06-15 09:05:39 -07:00
private Polygon Rectangle ( double left , double bottom , double right , double top , double scale )
{
var output = new Polygon ( 4 ) ;
output . Add ( ScalledPoint ( left , bottom , scale ) ) ;
output . Add ( ScalledPoint ( right , bottom , scale ) ) ;
output . Add ( ScalledPoint ( right , top , scale ) ) ;
output . Add ( ScalledPoint ( left , top , scale ) ) ;
return output ;
}
2020-06-15 18:46:48 -07:00
private Polygon Circle ( double x , double y , double radius , double scale = 1 , int steps = 100 )
{
var output = new Polygon ( 100 ) ;
for ( int i = 0 ; i < steps ; i + + )
{
var angle = 2 * Math . PI * i / steps ;
output . Add ( new IntPoint ( Math . Cos ( angle ) * radius * scale , Math . Sin ( angle ) * radius * scale ) ) ;
}
return output ;
}
2019-11-19 22:17:53 -08:00
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 ;
2020-05-26 14:11:25 -07:00
2019-11-19 22:17:53 -08:00
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 ;
2020-05-26 14:11:25 -07:00
2021-01-12 21:03:27 -08:00
public double OuterEdgeWidth { get ; set ; }
2019-12-06 10:18:58 -08:00
public int InternalToothCount
{
get = > _internalToothCount ;
set
{
_internalToothCount = value ;
CalculateDependants ( ) ;
}
}
2020-06-14 07:46:03 -07:00
public bool Debug { get ; set ; } = false ;
2020-06-23 08:28:21 -07:00
2020-06-15 18:46:48 -07:00
private List < Polygons > debugData = new List < Polygons > ( ) ;
2020-06-14 07:46:03 -07:00
2019-11-19 22:17:53 -08:00
public override IEnumerable < VertexData > Vertices ( )
2019-11-12 10:53:58 -08:00
{
2020-06-15 09:05:39 -07:00
Polygons 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 :
2020-06-15 18:46:48 -07:00
shape = CreateExternalGearShape ( ) ;
2019-11-19 22:17:53 -08:00
break ;
case GearTypes . Internal :
2020-06-18 09:10:40 -07:00
shape = CreateInternalGearShape ( ) ;
2019-11-19 22:17:53 -08:00
break ;
case GearTypes . Rack :
shape = CreateRackShape ( ) ;
break ;
2019-11-12 10:53:58 -08:00
}
2019-10-25 17:21:01 -07:00
2020-06-14 07:46:03 -07:00
if ( Debug & & debugData . Count > 0 )
{
2020-06-16 15:55:04 -07:00
var output = new Polygons ( ) ;
output . AddRange ( debugData [ 0 ] ) ;
2020-06-17 08:05:05 -07:00
var top = debugData [ 0 ] . GetBounds ( ) . Top ;
2020-06-14 07:46:03 -07:00
for ( int i = 1 ; i < debugData . Count ; i + + )
{
2020-06-17 08:05:05 -07:00
var offset = top - debugData [ i ] . GetBounds ( ) . Bottom + 2000 ;
var offsetPolys = debugData [ i ] . Translate ( 0 , offset ) ;
output . AddRange ( offsetPolys ) ;
top = offsetPolys . GetBounds ( ) . Top ;
2020-06-14 07:46:03 -07:00
}
2020-06-16 17:58:26 -07:00
shape = output ;
2020-06-14 07:46:03 -07:00
}
2020-06-15 18:46:48 -07:00
if ( shape = = null )
2020-06-15 09:05:39 -07:00
{
2020-06-15 18:46:48 -07:00
yield return new VertexData ( ShapePath . FlagsAndCommand . MoveTo , 0 , 0 ) ;
yield return new VertexData ( ShapePath . FlagsAndCommand . LineTo , 20 , 0 ) ;
yield return new VertexData ( ShapePath . FlagsAndCommand . LineTo , 0 , 20 ) ;
}
else
{
foreach ( var poly in shape )
2020-06-15 09:05:39 -07:00
{
2020-06-15 18:46:48 -07:00
var command = ShapePath . FlagsAndCommand . MoveTo ;
foreach ( var point in poly )
{
yield return new VertexData ( command , point . X / 1000.0 , point . Y / 1000.0 ) ;
command = ShapePath . FlagsAndCommand . LineTo ;
}
2020-06-15 09:05:39 -07:00
}
}
2019-10-25 17:21:01 -07:00
}
2020-06-20 09:08:27 -07:00
private Polygon CreateInternalToothCutter ( Gear2D pinion )
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 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 ( ) ;
2020-06-21 09:34:53 -07:00
var tooth = enlargedPinion . CreateToothCutout ( ) ;
debugData . Add ( new Polygons ( ) { tooth } ) ;
// we need a tooth pointing to the left
return tooth . Rotate ( MathHelper . DegreesToRadians ( 90 + 180 / enlargedPinion . ToothCount ) ) ;
2019-10-25 23:11:43 -07:00
}
2020-06-21 09:34:53 -07:00
private Polygons 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 ;
2020-06-18 09:10:40 -07:00
var toothToToothRadians = MathHelper . Tau / this . ToothCount ;
var sin = Math . Sin ( toothToToothRadians ) ;
var cos = Math . Cos ( toothToToothRadians ) ;
2019-10-25 17:21:01 -07:00
2020-06-16 15:55:04 -07:00
var fullSector = new Polygon ( ) ;
2019-10-25 17:21:01 -07:00
2020-06-16 15:55:04 -07:00
fullSector . Add ( ScalledPoint ( 0 , 0 , 1000 ) ) ;
fullSector . Add ( ScalledPoint ( - ( radius * cos ) , radius * sin , 1000 ) ) ;
fullSector . Add ( ScalledPoint ( - radius , 0 , 1000 ) ) ;
fullSector . Add ( ScalledPoint ( - ( radius * cos ) , - radius * sin , 1000 ) ) ;
2020-06-21 09:34:53 -07:00
debugData . Add ( new Polygons ( ) { fullSector } ) ;
2020-06-14 07:46:03 -07:00
2020-06-20 09:08:27 -07:00
var pinion = CreateInternalPinion ( ) ;
var cutterTemplate = this . CreateInternalToothCutter ( pinion ) ;
2020-06-16 17:58:26 -07:00
debugData . Add ( new Polygons ( ) { cutterTemplate } ) ;
2019-11-19 22:17:53 -08:00
var stepsPerTooth = this . stepsPerToothAngle ;
2020-06-18 09:10:40 -07:00
var stepSizeRadians = toothToToothRadians / stepsPerTooth ;
2019-11-19 22:17:53 -08:00
2020-06-21 09:34:53 -07:00
var expandedCutter = new Polygons ( ) ;
2020-06-18 09:10:40 -07:00
for ( var i = 0 ; i < stepsPerTooth ; i + + )
2019-11-19 22:17:53 -08:00
{
2020-06-18 09:10:40 -07:00
var pinionRadians = i * stepSizeRadians ;
var pinionCenterRayRadians = - pinionRadians * pinion . ToothCount / this . ToothCount ;
2019-11-19 22:17:53 -08:00
2020-06-18 09:10:40 -07:00
var cutter = cutterTemplate . Rotate ( pinionRadians ) ;
2020-06-20 09:08:27 -07:00
cutter = cutter . Translate ( - this . pitchRadius + pinion . pitchRadius , 0 , 1000 ) ;
2020-06-18 09:10:40 -07:00
cutter = cutter . Rotate ( pinionCenterRayRadians ) ;
2019-11-19 22:17:53 -08:00
2020-06-21 09:34:53 -07:00
expandedCutter . Add ( cutter ) ;
2019-11-19 22:17:53 -08:00
2020-06-18 09:10:40 -07:00
cutter = cutterTemplate . Rotate ( - pinionRadians ) ;
2020-06-20 09:08:27 -07:00
cutter = cutter . Translate ( - this . pitchRadius + pinion . pitchRadius , 0 , 1000 ) ;
2020-06-18 09:10:40 -07:00
cutter = cutter . Rotate ( - pinionCenterRayRadians ) ;
2019-11-19 22:17:53 -08:00
2020-06-21 09:34:53 -07:00
expandedCutter . Add ( cutter ) ;
2019-11-19 22:17:53 -08:00
}
2020-06-21 09:34:53 -07:00
expandedCutter = expandedCutter . Union ( expandedCutter , PolyFillType . pftNonZero ) ;
debugData . Add ( expandedCutter ) ;
var toothShape = fullSector . Subtract ( expandedCutter ) ;
2020-06-18 09:10:40 -07:00
debugData . Add ( toothShape ) ;
2020-06-16 15:55:04 -07:00
2020-06-21 09:34:53 -07:00
var innerRadius = radius - ( 2 * this . addendum + this . Clearance ) ;
var innerCircle = Circle ( this . center . X , center . Y , innerRadius , 1000 ) ;
toothShape = toothShape . Subtract ( innerCircle ) ;
debugData . Add ( toothShape ) ;
return toothShape ;
2019-10-25 17:21:01 -07:00
}
2020-06-16 15:55:04 -07:00
private Polygon SmoothConcaveCorners ( Polygon 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
}
2020-06-16 15:55:04 -07:00
private Polygon SmoothConvexCorners ( Polygon 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
2020-06-16 15:55:04 -07:00
private Polygon SmoothCorners ( Polygon corners , bool removeSingleConvex )
2019-11-19 22:17:53 -08:00
{
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 ] ;
2020-06-16 15:55:04 -07:00
var v1 = previousCorner - currentCorner ;
var v2 = nextCorner - currentCorner ;
2019-11-19 22:17:53 -08:00
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
2020-06-16 15:55:04 -07:00
var cleanedUpCorners = new Polygon ( ) ;
2020-06-12 20:21:52 -07:00
for ( var currentIndex = 0 ; currentIndex < corners . Count ; currentIndex + + )
2019-11-19 22:17:53 -08:00
{
var corner = corners [ currentIndex ] ;
2020-06-12 20:21:52 -07:00
var nextIndex = ( currentIndex + 1 ) % corners . Count ;
var previousIndex = ( currentIndex + corners . Count - 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 ] ;
if ( removeSingleConvex & & isSingleConvex )
{
continue ;
}
if ( ! removeSingleConvex & & isSingleConcave )
{
continue ;
}
2020-06-16 15:55:04 -07:00
cleanedUpCorners . Add ( corner ) ;
2019-11-19 22:17:53 -08:00
}
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
2020-05-23 17:03:52 -07:00
this . OuterRadius = this . pitchRadius + this . shiftedAddendum ;
2020-06-20 09:08:27 -07:00
}
2019-12-06 10:18:58 -08:00
2020-06-20 09:08:27 -07:00
private Gear2D CreateInternalPinion ( )
{
return new Gear2D ( )
2019-12-06 10:18:58 -08:00
{
2020-06-20 09:08:27 -07:00
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
}
2020-06-16 15:55:04 -07:00
private Polygons CreateInternalGearShape ( )
2019-11-04 08:36:26 -08:00
{
2020-06-21 09:34:53 -07:00
this . CenterHoleDiameter = 0 ;
var innerRadius = this . pitchRadius + ( 1 - this . profileShift ) * this . addendum + this . Clearance ;
var outerRadius = innerRadius + OuterEdgeWidth ;
var outerCircle = Circle ( this . center . X , center . Y , outerRadius , 1000 ) ;
2021-01-12 21:03:27 -08:00
var simpleInnerGear = false ;
if ( simpleInnerGear )
{
var externalGear = CreateExternalGearShape ( ) ;
var internalGear = outerCircle . Subtract ( externalGear ) ;
debugData . Add ( internalGear ) ;
return internalGear ;
}
2020-06-21 09:34:53 -07:00
2019-11-19 22:17:53 -08:00
var singleTooth = this . CreateInternalToothProfile ( ) ;
2020-06-21 09:34:53 -07:00
debugData . Add ( singleTooth ) ;
2019-11-04 08:36:26 -08:00
2020-06-18 09:10:40 -07:00
var outerCorners = new Polygons ( ) ;
2019-11-08 22:52:33 -08:00
2020-06-14 10:03:49 -07:00
for ( var i = 0 ; i < this . ToothCount ; i + + )
2019-11-04 08:36:26 -08:00
{
2020-06-12 20:21:52 -07:00
var angle = i * this . AngleToothToTooth ;
2020-06-16 15:55:04 -07:00
var radians = MathHelper . DegreesToRadians ( angle ) ;
2020-06-21 09:34:53 -07:00
outerCorners . AddRange ( singleTooth . Rotate ( radians ) ) ;
2019-11-04 08:36:26 -08:00
}
2020-06-18 09:10:40 -07:00
outerCorners = outerCorners . Union ( outerCorners , PolyFillType . pftNonZero ) ;
2019-11-04 08:36:26 -08:00
2020-06-18 09:10:40 -07:00
debugData . Add ( outerCorners ) ;
2020-06-14 10:03:49 -07:00
2019-12-06 10:18:58 -08:00
// return outerCorners;
2020-06-14 10:03:49 -07:00
var finalShape = outerCircle . Subtract ( outerCorners ) ;
2020-06-21 09:34:53 -07:00
debugData . Add ( finalShape ) ;
2020-06-14 10:03:49 -07:00
return finalShape ;
2019-10-26 08:45:11 -07:00
}
2020-06-15 09:05:39 -07:00
private Polygons CreateRackShape ( )
2019-11-19 22:17:53 -08:00
{
2020-06-15 09:05:39 -07:00
var rack = new Polygons ( ) ;
2019-11-19 22:17:53 -08:00
for ( var i = 0 ; i < ToothCount ; i + + )
{
var tooth = this . CreateRackTooth ( ) ;
2020-06-15 09:05:39 -07:00
tooth = tooth . Translate ( 0 , ( 0.5 + - ToothCount / 2.0 + i ) * this . CircularPitch , 1000 ) ;
2020-06-16 15:55:04 -07:00
rack = rack . Union ( tooth ) ;
2019-11-19 22:17:53 -08:00
}
// 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 ;
2020-06-15 09:05:39 -07:00
var bar = Rectangle ( rightX - width , - halfHeight , rightX , halfHeight , 1000 ) ;
2019-11-19 22:17:53 -08:00
2020-06-16 15:55:04 -07:00
var rackFinal = rack . Union ( bar ) ;
2020-06-15 09:05:39 -07:00
return rackFinal . Translate ( this . addendum * this . profileShift , 0 , 1000 ) ;
2019-11-19 22:17:53 -08:00
}
2020-06-15 09:05:39 -07:00
private Polygon CreateRackTooth ( )
2019-10-25 17:21:01 -07:00
{
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 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
2020-06-15 09:05:39 -07:00
var upperLeftCorner = ScalledPoint ( - leftDepth , toothWidth / 2 - dx + ( this . addendum + this . Clearance ) * sinPressureAngle , 1000 ) ;
var upperRightCorner = ScalledPoint ( this . addendum , toothWidth / 2 - dx - this . addendum * sinPressureAngle , 1000 ) ;
2020-06-15 18:46:48 -07:00
var lowerRightCorner = ScalledPoint ( upperRightCorner . X , - upperRightCorner . Y , 1 ) ;
var lowerLeftCorner = ScalledPoint ( upperLeftCorner . X , - upperLeftCorner . Y , 1 ) ;
2019-10-25 17:21:01 -07:00
2020-06-15 09:05:39 -07:00
var tooth = new Polygon ( ) ;
tooth . Add ( upperLeftCorner ) ;
tooth . Add ( upperRightCorner ) ;
tooth . Add ( lowerRightCorner ) ;
tooth . Add ( lowerLeftCorner ) ;
2019-10-25 17:21:01 -07:00
return tooth ;
}
2020-06-15 18:46:48 -07:00
private Polygons CreateExternalGearShape ( )
2019-11-19 22:17:53 -08:00
{
2020-06-21 09:34:53 -07:00
var tooth = this . CreateToothCutout ( ) ;
2019-11-19 22:17:53 -08:00
2020-06-21 09:34:53 -07:00
// we now take the tooth cutout, rotate it tooth count times and union the various slices together into a complete gear.
2020-06-17 08:05:05 -07:00
debugData . Add ( new Polygons ( ) { tooth } ) ;
var gearShape = new Polygons ( ) ;
for ( var i = 0 ; i < this . ToothCount ; i + + )
2019-11-19 22:17:53 -08:00
{
2020-06-12 20:21:52 -07:00
var angle = i * this . AngleToothToTooth ;
2020-06-16 15:55:04 -07:00
var radians = MathHelper . DegreesToRadians ( angle ) ;
2020-06-17 08:05:05 -07:00
var rotatedCorner = tooth . Rotate ( radians ) ;
gearShape . Add ( rotatedCorner ) ;
2019-11-19 22:17:53 -08:00
}
2020-06-17 08:05:05 -07:00
gearShape = gearShape . Union ( gearShape , PolyFillType . pftNonZero ) ;
2020-06-12 20:21:52 -07:00
2020-06-17 08:05:05 -07:00
debugData . Add ( gearShape ) ;
2020-06-18 09:10:40 -07:00
2020-06-21 09:34:53 -07:00
// create outer circle covering the teeth
var wheel = Circle ( 0 , 0 , this . OuterRadius , 1000 ) ;
gearShape = wheel . Subtract ( gearShape ) ;
2019-11-19 22:17:53 -08:00
2020-06-16 17:58:26 -07:00
debugData . Add ( gearShape ) ;
2020-06-12 20:21:52 -07:00
2019-11-19 22:17:53 -08:00
if ( this . CenterHoleDiameter > 0 )
{
var radius = this . CenterHoleDiameter / 2 ;
2020-06-16 15:55:04 -07:00
var centerhole = Circle ( 0 , 0 , radius , 1000 ) ;
gearShape = gearShape . Subtract ( centerhole ) ;
2020-06-17 08:05:05 -07:00
debugData . Add ( gearShape ) ;
2019-11-19 22:17:53 -08:00
}
2020-06-12 20:21:52 -07:00
return gearShape ;
2019-11-19 22:17:53 -08:00
}
2020-06-16 17:58:26 -07:00
private Polygon CreateToothCutout ( )
2019-10-25 17:21:01 -07:00
{
2020-06-21 09:34:53 -07:00
var angleStepRadians = MathHelper . DegreesToRadians ( this . AngleToothToTooth / this . stepsPerToothAngle ) ;
2019-10-25 17:21:01 -07:00
2020-06-16 15:55:04 -07:00
var toothCutout = new Polygons ( ) ;
2019-10-25 17:21:01 -07:00
2020-06-21 09:34:53 -07:00
var trapesoidalCutter = this . CreateTrapezoidalCutter ( ) ;
debugData . Add ( new Polygons ( ) { trapesoidalCutter } ) ;
2020-06-15 18:46:48 -07:00
2020-06-21 09:34:53 -07:00
var bounds = trapesoidalCutter . GetBounds ( ) ;
2019-10-25 17:21:01 -07:00
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 )
{
2020-06-21 09:34:53 -07:00
var radians = stepCounter * angleStepRadians ;
// multiply by 1000 as that is what our clipping space is in (scaled by 1000)
var xTranslation = radians * this . pitchRadius * 1000 ;
2019-10-25 17:21:01 -07:00
2020-06-21 09:34:53 -07:00
if ( Vector2 . Rotate ( lowerLeftCorner + new Vector2 ( xTranslation , 0 ) , radians ) . Length > this . OuterRadius * 1000 )
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
2020-06-21 09:34:53 -07:00
var movedToothCutterShape = trapesoidalCutter . Translate ( xTranslation , 0 ) ;
2020-06-16 17:58:26 -07:00
movedToothCutterShape = movedToothCutterShape . Rotate ( radians ) ;
2019-10-25 17:21:01 -07:00
toothCutout = toothCutout . Union ( movedToothCutterShape ) ;
2020-06-21 09:34:53 -07:00
if ( xTranslation > 0 )
2019-10-26 08:45:11 -07:00
{
2020-06-21 09:34:53 -07:00
movedToothCutterShape = trapesoidalCutter . Translate ( - xTranslation , 0 ) ;
2020-06-16 17:58:26 -07:00
movedToothCutterShape = movedToothCutterShape . Rotate ( - radians ) ;
2019-10-25 17:21:01 -07:00
toothCutout = toothCutout . Union ( movedToothCutterShape ) ;
}
stepCounter + + ;
}
2020-06-21 09:34:53 -07:00
var smoothedCutout = this . SmoothConcaveCorners ( toothCutout [ 0 ] ) ;
2019-10-25 17:21:01 -07:00
2020-06-21 09:34:53 -07:00
return smoothedCutout . Rotate ( MathHelper . DegreesToRadians ( - this . AngleToothToTooth / 2 ) ) ;
2019-10-25 17:21:01 -07:00
}
2020-06-21 09:34:53 -07:00
private Polygon CreateTrapezoidalCutter ( )
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 ] ) ;
2020-06-16 15:55:04 -07:00
var cutterPath = new Polygon ( ) ;
cutterPath . Add ( ScalledPoint ( lowerLeftCorner , 1000 ) ) ;
2020-06-16 17:58:26 -07:00
cutterPath . Add ( ScalledPoint ( upperLeftCorner , 1000 ) ) ;
cutterPath . Add ( ScalledPoint ( upperRightCorner , 1000 ) ) ;
cutterPath . Add ( ScalledPoint ( lowerRightCorner , 1000 ) ) ;
2019-10-25 17:21:01 -07:00
return cutterPath ;
}
}
}