From f8e40fa4cf48d5a7bc5374ec04b4ee1ee5cb8633 Mon Sep 17 00:00:00 2001 From: Lars Brubaker Date: Fri, 25 Oct 2019 17:21:01 -0700 Subject: [PATCH 01/10] moving Gear2D to MC putting in some debugging --- .../Operations/Object3DExtensions.cs | 32 - .../DesignTools/Primitives/Gear2D.cs | 659 ++++++++++++++++++ .../Primitives/TextPathObject3D.cs | 1 + .../PartPreviewWindow/MainViewWidget.cs | 9 + Submodules/agg-sharp | 2 +- 5 files changed, 670 insertions(+), 33 deletions(-) create mode 100644 MatterControlLib/DesignTools/Primitives/Gear2D.cs diff --git a/MatterControlLib/DesignTools/Operations/Object3DExtensions.cs b/MatterControlLib/DesignTools/Operations/Object3DExtensions.cs index 68ed398cb..e1d4230c0 100644 --- a/MatterControlLib/DesignTools/Operations/Object3DExtensions.cs +++ b/MatterControlLib/DesignTools/Operations/Object3DExtensions.cs @@ -132,28 +132,6 @@ namespace MatterHackers.MatterControl.DesignTools.Operations return objectToTranslate; } - private static VertexStorage CombinePaths(IVertexSource a, IVertexSource b, ClipType clipType) - { - List> aPolys = a.CreatePolygons(); - List> bPolys = b.CreatePolygons(); - - Clipper clipper = new Clipper(); - - clipper.AddPaths(aPolys, PolyType.ptSubject, true); - clipper.AddPaths(bPolys, PolyType.ptClip, true); - - List> intersectedPolys = new List>(); - clipper.Execute(clipType, intersectedPolys); - - Clipper.CleanPolygons(intersectedPolys); - - VertexStorage output = intersectedPolys.CreateVertexStorage(); - - output.Add(0, 0, ShapePath.FlagsAndCommand.Stop); - - return output; - } - public static Affine GetCenteringTransformExpandedToRadius(this IVertexSource vertexSource, double radius) { var circle = SmallestEnclosingCircle.MakeCircle(vertexSource.Vertices().Select((v) => new Vector2(v.position.X, v.position.Y))); @@ -281,16 +259,6 @@ namespace MatterHackers.MatterControl.DesignTools.Operations }; } - public static IVertexSource Minus(this IVertexSource a, IVertexSource b) - { - return CombinePaths(a, b, ClipType.ctDifference); - } - - public static IVertexSource Plus(this IVertexSource a, IVertexSource b) - { - return CombinePaths(a, b, ClipType.ctUnion); - } - public static double XSize(this IObject3D item) { return item.GetAxisAlignedBoundingBox().XSize; diff --git a/MatterControlLib/DesignTools/Primitives/Gear2D.cs b/MatterControlLib/DesignTools/Primitives/Gear2D.cs new file mode 100644 index 000000000..006652ae7 --- /dev/null +++ b/MatterControlLib/DesignTools/Primitives/Gear2D.cs @@ -0,0 +1,659 @@ +/* +Involute Spur Gear Builder (c) 2014 Dr. Rainer Hessmer +ported to C# 2018 by Lars Brubaker + +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 +{ + // using Polygons = List>; + // using Polygon = List; + + public class Gear2D : VertexSourceLegacySupport + { + private double circularPitch; + private double diametralPitch; + private double addendum; + private double clearance; + private double pressureAngle; + private double backlash; + private double profileShift; + private double shiftedAddendum; + private double outerRadius; + private double angleToothToTooth; + private int toothCount; + private GearType gearType; + private int stepsPerToothAngle; + private double pitchDiameter; + private double pitchRadius; + private Vector2 center; + private Gear2D connectedGear; + private int centerHoleDiameter; + + public enum GearType + { + External, + Internal, + Rack + } + + public Gear2D(double circularPitch = 8, + double pressureAngle = 20, + double clearance = 0.05, + double backlash = 0.05, + int toothCount = 30, + int centerHoleDiameter = 4, + double profileShift = 0, + int stepsPerToothAngle = 3, + GearType gearType = GearType.External) + { + this.circularPitch = circularPitch; + this.pressureAngle = pressureAngle; + this.clearance = clearance; + this.backlash = backlash; + this.toothCount = toothCount; + this.centerHoleDiameter = centerHoleDiameter; + this.profileShift = profileShift; + this.stepsPerToothAngle = stepsPerToothAngle; + + this.toothCount = toothCount; + this.gearType = gearType; + + this.circularPitch = circularPitch; // Distance from one face of a tooth to the corresponding face of an adjacent tooth on the same gear, measured along the pitch circle. + // 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; + + this.pressureAngle = pressureAngle; // 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) + + this.centerHoleDiameter = centerHoleDiameter; + + this.clearance = clearance; + this.backlash = backlash; + + this.center = Vector2.Zero; // center of the gear + // this.angle = 0; // angle in degrees of the complete gear (changes during rotation animation) + + // Pitch diameter: Diameter of pitch circle. + this.pitchDiameter = this.toothCount / this.diametralPitch; + this.pitchRadius = this.pitchDiameter / 2; + + // Addendum: Radial distance from pitch circle to outside circle. + this.addendum = 1 / this.diametralPitch; + this.profileShift = profileShift; + + // Typically no profile shift is used meaning that this.shiftedAddendum = this.addendum + this.shiftedAddendum = this.addendum * (1 + this.profileShift); + + //Outer Circle + this.outerRadius = this.pitchRadius + this.shiftedAddendum; + this.angleToothToTooth = 360 / this.toothCount; + } + + private VertexStorage _createRackShape() + { + IVertexSource rack = new VertexStorage(); + + // we draw one tooth in the middle and then five on either side + var toothCount = 41.0; + for (var i = 0; i < toothCount; i++) + { + var tooth = this._createRackTooth(); + tooth.translate(0, (0.5 + -toothCount / 2 + i) * this.circularPitch); + rack = rack.Union(tooth); + } + + // creating the bar backing the teeth + var rightX = -(this.addendum + this.clearance); + var width = 4 * this.addendum; + var halfHeight = toothCount * this.circularPitch / 2; + 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; + } + + private VertexStorage _createRackTooth() + { + var toothWidth = this.circularPitch / 2; + var toothDepth = this.addendum + this.clearance; + + var sinPressureAngle = Math.Sin(this.pressureAngle * Math.PI / 180); + var cosPressureAngle = Math.Cos(this.pressureAngle * Math.PI / 180); + + // 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). + var dx = this.backlash / 4 / cosPressureAngle; + + var leftDepth = this.addendum + this.clearance; + + var upperLeftCorner = new Vector2(-leftDepth, toothWidth / 2 - dx + (this.addendum + this.clearance) * sinPressureAngle); + 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; + } + + private IVertexSource _createSingleTooth() + { + // create outer circle sector covering one tooth + IVertexSource toothSectorPath = new VertexStorage(); // closed + var toothSectorArc = new Arc(Vector2.Zero, new Vector2(this.outerRadius, this.outerRadius), MathHelper.DegreesToRadians(90), MathHelper.DegreesToRadians(90 - this.angleToothToTooth)); + toothSectorPath = new JoinPaths(toothSectorPath, toothSectorArc); + + var toothCutout = this.createToothCutout(); + var tooth = toothSectorPath.Subtract(toothCutout); + + return tooth; + } + + private IVertexSource createToothCutout() + { + var angleToothToTooth = 360 / this.toothCount; + var angleStepSize = this.angleToothToTooth / this.stepsPerToothAngle; + + IVertexSource toothCutout = null; + + var toothCutterShape = this.createToothCutter(); + 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 lowerLeftCornerDistance = 0.0; + var stepCounter = 0; + while (true) + { + var angle = stepCounter * angleStepSize; + var xTranslation = new Vector2(angle * Math.PI / 180 * this.pitchRadius, 0); + + var movedLowerLeftCorner = lowerLeftCorner + xTranslation; + movedLowerLeftCorner = Vector2.Rotate(movedLowerLeftCorner, angle); + + lowerLeftCornerDistance = movedLowerLeftCorner.Length; + if (movedLowerLeftCorner.Length > this.outerRadius) { + // the cutter is now completely outside the gear and no longer influences the shape of the gear tooth + break; + } + + // we move in both directions + var movedToothCutterShape = toothCutterShape.translate(xTranslation); + movedToothCutterShape = movedToothCutterShape.rotateZ(angle); + toothCutout = toothCutout.Union(movedToothCutterShape); + + if (xTranslation[0] > 0) { + movedToothCutterShape = toothCutterShape.translate(new Vector2(-xTranslation[0], xTranslation[1])); + movedToothCutterShape = movedToothCutterShape.rotateZ(-angle); + toothCutout = toothCutout.Union(movedToothCutterShape); + } + + stepCounter++; + } + + toothCutout = this._smoothConcaveCorners(toothCutout); + + return toothCutout.rotateZ(-this.angleToothToTooth / 2); + } + + private IVertexSource createToothCutter() + { + // we create a trapezoidal cutter as described at http://lcamtuf.coredump.cx/gcnc/ch6/ under the section 'Putting it all together' + var toothWidth = this.circularPitch / 2; + + var cutterDepth = this.addendum + this.clearance; + var cutterOutsideLength = 3 * this.addendum; + + var sinPressureAngle = Math.Sin(this.pressureAngle * Math.PI / 180.0); + var cosPressureAngle = Math.Cos(this.pressureAngle * Math.PI / 180.0); + + // 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). + var dx = this.backlash / 2 / cosPressureAngle; + + 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; + } + + IVertexSource _smoothConvexCorners(IVertexSource corners) + { + // removes single convex corners located between concave corners + return this._smoothCorners(corners, true); // removeSingleConvex + } + + IVertexSource _createInternalToothCutter() + { + // To cut the internal gear teeth, the actual pinion comes close but we need to enlarge it so properly cater for clearance and backlash + var pinion = this.connectedGear; + + var enlargedPinion = new Gear2D(circularPitch: pinion.circularPitch, + pressureAngle: pinion.pressureAngle, + clearance: -pinion.clearance, + backlash: -pinion.backlash, + toothCount: pinion.toothCount, + centerHoleDiameter: 0, + profileShift: pinion.profileShift, + stepsPerToothAngle: pinion.stepsPerToothAngle + ); + + var tooth = enlargedPinion._createSingleTooth(); + return tooth.rotateZ(90 + 180 / enlargedPinion.toothCount); // we need a tooth pointing to the left + } + + IVertexSource _createInternalToothProfile() + { + 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); + + var fullSector = new VertexStorage(); + + fullSector.MoveTo(0, 0); + fullSector.LineTo(-(radius * cos), radius * sin); + fullSector.LineTo(-radius, 0); + fullSector.LineTo(-(radius * cos), -radius * sin); + + var innerRadius = radius - (2 * this.addendum + this.clearance); + var innerCircle = new Ellipse(this.center, innerRadius); + var sector = fullSector.Subtract(innerCircle); + + 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; + + //var cutter = cutterTemplate; + cutter = cutterTemplate.rotateZ(pinionRotationAngle); + cutter = cutter.Translate(-this.pitchRadius + this.connectedGear.pitchRadius, 0); + cutter = cutter.rotateZ(pinionCenterRayAngle); + + toothShape = toothShape.Subtract(cutter); + + cutter = cutterTemplate.rotateZ(-pinionRotationAngle); + cutter = cutter.Translate(-this.pitchRadius + this.connectedGear.pitchRadius, 0); + cutter = cutter.rotateZ(-pinionCenterRayAngle); + + toothShape = toothShape.Subtract(cutter); + } + + return toothShape; + } + + + IVertexSource _smoothConcaveCorners(IVertexSource corners) + { + // removes single concave corners located between convex corners + return this._smoothCorners(corners, false); // removeSingleConvex + } + + IVertexSource _smoothCorners(IVertexSource corners_in, bool removeSingleConvex) + { + var corners = corners_in as VertexStorage; + var isConvex = new List(); + var previousCorner = corners[corners.Count - 1]; + var currentCorner = corners[0]; + for (var i = 0; i < corners.Count; i++) + { + 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; + } + + // 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; + + 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; + } + + public override IEnumerable Vertices() + { + var temp = new Ellipse(0, 0, pitchRadius, pitchRadius); + return temp.Vertices(); + } + } +} + +public static class Extensions +{ + public static IVertexSource Subtract(this IVertexSource a, IVertexSource b) + { + return a.Plus(b); + } + + public static IVertexSource Union(this IVertexSource a, IVertexSource b) + { + return a.Minus(b); + } + + public static IVertexSource rotateZ(this IVertexSource a, double angle) + { + return new VertexSourceApplyTransform(a, Affine.NewRotation(angle)); + } + + public static IVertexSource translate(this IVertexSource a, Vector2 delta) + { + return new VertexSourceApplyTransform(a, Affine.NewTranslation(delta)); + } + + public static IVertexSource Minus(this IVertexSource a, IVertexSource b) + { + return CombinePaths(a, b, ClipType.ctDifference); + } + + public static IVertexSource Plus(this IVertexSource a, IVertexSource b) + { + return CombinePaths(a, b, ClipType.ctUnion); + } + + private static VertexStorage CombinePaths(IVertexSource a, IVertexSource b, ClipType clipType) + { + List> aPolys = a.CreatePolygons(); + List> bPolys = b.CreatePolygons(); + + Clipper clipper = new Clipper(); + + clipper.AddPaths(aPolys, PolyType.ptSubject, true); + clipper.AddPaths(bPolys, PolyType.ptClip, true); + + List> intersectedPolys = new List>(); + clipper.Execute(clipType, intersectedPolys); + + Clipper.CleanPolygons(intersectedPolys); + + VertexStorage output = intersectedPolys.CreateVertexStorage(); + + output.Add(0, 0, ShapePath.FlagsAndCommand.Stop); + + return output; + } +} + +/* +

Involute Spur Gear Builder (C) 2014 Dr. Rainer Hessmer

+

An open source, browser based utility 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 my blog posts part 1 and part 2. If you prefer a standalone utility and you use Windows 64, see Doug Roger's port to C++.

+

The implementation is inspired by the subtractive process that Michal Zalewski's describes in part six of his excellent Guerrilla guide to CNC machining, mold making, and resin casting.

+

Instructions

+

Specify desired values in the parameters box and then click on the 'Update' button. The tooth count n1 of gear one defines various configurations: +

    +
  • n1 > 0: A regular external gear
    Regular Spur Gear
  • +
  • n1 = 0: Rack and pinion
    Rack and Pinion
  • +
  • n1 < 0: An internal gear as used in planetary gears
    Internal Gear
  • +
+

+

The tool also supports profile shift to reduce the amount of undercut in gears with low tooth counts. + + + var g_ExpandToCAGParams = {pathradius: 0.01, resolution: 2}; + + function main(params) + { + // Main entry point; here we construct our solid: + var qualitySettings = {resolution: params.resolution, stepsPerToothAngle: params.stepsPerToothAngle}; + + var gear1 = new Gear({ + circularPitch: params.circularPitch, + pressureAngle: params.pressureAngle, + clearance: params.clearance, + backlash: params.backlash, + toothCount: params.wheel1ToothCount, + centerHoleDiameter: params.wheel1CenterHoleDiamater, + profileShift: -params.profileShift, + qualitySettings: qualitySettings + }); + var gear2 = new Gear({ + circularPitch: params.circularPitch, + pressureAngle: params.pressureAngle, + clearance: params.clearance, + backlash: params.backlash, + toothCount: params.wheel2ToothCount, + centerHoleDiameter: params.wheel2CenterHoleDiamater, + profileShift: params.profileShift, + qualitySettings: qualitySettings + }); + + var gearSet = new GearSet( + gear1, + gear2, + params.showOption); + + var shape = gearSet.createShape(); + return shape; + } + + function getParameterDefinitions() { + return [ + { name: 'circularPitch', caption: 'Circular pitch (the circumference of the pitch circle divided by the number of teeth):', type: 'float', initial: 8 }, + { name: 'pressureAngle', caption: 'Pressure Angle (common values are 14.5, 20 and 25 degrees):', type: 'float', initial: 20 }, + { name: 'clearance', caption: 'Clearance (minimal distance between the apex of a tooth and the trough of the other gear; in length units):', type: 'float', initial: 0.05 }, + { name: 'backlash', caption: 'Backlash (minimal distance between meshing gears; in length units):', type: 'float', initial: 0.05 }, + { name: 'profileShift', caption: 'Profile Shift (indicates what portion of gear one\'s addendum height should be shifted to gear two. E.g., a value of 0.1 means the adddendum of gear two is increased by a factor of 1.1 while the height of the addendum of gear one is reduced to 0.9 of its normal height.):', type: 'float', initial: 0.0 }, + { name: 'wheel1ToothCount', caption: 'Wheel 1 Tooth Count (n1 > 0: external gear; n1 = 0: rack; n1 < 0: internal gear):', type: 'int', initial: 30 }, + { name: 'wheel1CenterHoleDiamater', caption: 'Wheel 1 Center Hole Diameter (0 for no hole):', type: 'float', initial: 4 }, + { name: 'wheel2ToothCount', caption: 'Wheel 2 Tooth Count:', type: 'int', initial: 8 }, + { name: 'wheel2CenterHoleDiamater', caption: 'Wheel 2 Center Hole Diameter (0 for no hole):', type: 'float', initial: 4 }, + { name: 'showOption', caption: 'Show:', type: 'choice', values: [3, 1, 2], initial: 3, captions: ["Wheel 1 and Wheel 2", "Wheel 1 Only", "Wheel 2 Only"]}, + { name: 'stepsPerToothAngle', caption: 'Rotation steps per tooth angle when assembling the tooth profile (3 = draft, 10 = good quality). Increasing the value will result in smoother profiles at the cost of significantly higher calcucation time. Incease in small increments and check the result by zooming in.', type: 'int', initial: 3 }, + { name: 'resolution', caption: 'Number of segments per 360 degree of rotation (only used for circles and arcs); 90 is plenty:', type: 'int', initial: 30 }, + ]; + } + + // Start base class Gear + var Gear = (function () { + Gear.prototype.getZeroedShape = function() { + // return the gear shape center on the origin and rotation angle 0. + if (this.zeroedShape == null) { + this.zeroedShape = this._createZeroedShape(); + } + return this.zeroedShape; + } + Gear.prototype._createZeroedShape = function() { + if (this.gearType == GearType.Regular) { + return this._createRegularGearShape(); + } + else if (this.gearType == GearType.Internal) { + return this._createInternalGearShape(); + } + else if (this.gearType == GearType.Rack) { + return this._createRackShape(); + } + } + Gear.prototype._createRegularGearShape = function() { + 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.getOutlinePaths(); + var corners = outlinePaths[0].points; + + // first we need to find the corner that sits at the center + var centerCornerIndex; + for(var i = 0; i < corners.length; i++) { + var corner = corners[i]; + if (corner.lengthSquared() < 0.0000001) { + centerCornerIndex = i; + break; + } + } + var outerPoints = []; + var outerCorners = []; + var outterPointsCount = corners.length - 2; + for(var i = 1; i < corners.length - 1; i++) { + var corner = corners[(i + centerCornerIndex) % corners.length]; + outerCorners.push(corner); + outerPoints.push([corner.x, corner.y]); + } + + for(var i = 1; i < this.toothCount; i++) { + var angle = i * this.angleToothToTooth; + var roatationMatrix = CSG.Matrix4x4.rotationZ(angle) + for (var j = 0; j < outerCorners.length; j++) { + var rotatedCorner = outerCorners[j].transform(roatationMatrix); + outerPoints.push([rotatedCorner.x, rotatedCorner.y]); + } + } + + var gearShape = CAG.fromPointsNoCheck(outerPoints); + + if (this.centerHoleDiameter > 0) { + var centerhole = CAG.circle({center: [-0, -0], radius: this.centerHoleDiameter / 2, resolution: this.qualitySettings.resolution}); + gearShape = gearShape.subtract(centerhole); + } + + return gearShape.rotateZ(-90); + } + Gear.prototype._createInternalGearShape = function() { + var singleTooth = this._createInternalToothProfile(); + //return singleTooth; + + var outlinePaths = singleTooth.getOutlinePaths(); + var corners = outlinePaths[0].points; + + // first we need to find the corner that sits at the center + var centerCornerIndex; + var radius = this.pitchRadius + ( 1 + this.profileShift) * this.addendum + this.clearance; + + var delta = 0.0000001; + for(var i = 0; i < corners.length; i++) { + var corner = corners[i]; + if (corner.y < delta && (corner.x + radius) < delta) { + centerCornerIndex = i; + break; + } + } + var outerCorners = []; + for(var i = 2; i < corners.length - 2; i++) { + var corner = corners[(i + centerCornerIndex) % corners.length]; + outerCorners.push(corner); + } + + outerCorners.reverse(); + var cornersCount = outerCorners.length; + + for(var i = 1; i < this.toothCount; i++) { + var angle = i * this.angleToothToTooth; + var roatationMatrix = CSG.Matrix4x4.rotationZ(angle) + for (var j = 0; j < cornersCount; j++) { + var rotatedCorner = outerCorners[j].transform(roatationMatrix); + outerCorners.push(rotatedCorner); + } + } + + var outerCorners = this._smoothConcaveCorners(outerCorners); + var outerPoints = []; + outerCorners.map(function(corner) { outerPoints.push([corner.x, corner.y]); }); + + var innerRadius = this.pitchRadius + (1 - this.profileShift) * this.addendum + this.clearance; + var outerRadius = innerRadius + 4 * this.addendum; + var outerCircle = CAG.circle({center: this.center, radius: outerRadius, resolution: this.qualitySettings.resolution}); + //return outerCircle; + + var gearCutout = CAG.fromPointsNoCheck(outerPoints); + //return gearCutout; + return outerCircle.subtract(gearCutout); + } + Gear.prototype.pointsToString = function(points) { + var result = "["; + points.map(function(point) { + result += "[" + point.x + "," + point.y + "],"; + }); + return result + "]"; + } + return Gear; + })(); +*/ diff --git a/MatterControlLib/DesignTools/Primitives/TextPathObject3D.cs b/MatterControlLib/DesignTools/Primitives/TextPathObject3D.cs index 0cfb17f1f..4245dcf71 100644 --- a/MatterControlLib/DesignTools/Primitives/TextPathObject3D.cs +++ b/MatterControlLib/DesignTools/Primitives/TextPathObject3D.cs @@ -85,6 +85,7 @@ namespace MatterHackers.MatterControl.DesignTools { newContainer.Children.Add(child.Clone()); } + undoBuffer.AddAndDo(new ReplaceCommand(new[] { this }, new[] { newContainer })); } diff --git a/MatterControlLib/PartPreviewWindow/MainViewWidget.cs b/MatterControlLib/PartPreviewWindow/MainViewWidget.cs index 80b5a3550..4fd1d206c 100644 --- a/MatterControlLib/PartPreviewWindow/MainViewWidget.cs +++ b/MatterControlLib/PartPreviewWindow/MainViewWidget.cs @@ -39,6 +39,7 @@ using MatterHackers.Agg.UI; using MatterHackers.DataConverters3D; using MatterHackers.Localizations; using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.DesignTools; using MatterHackers.MatterControl.Library; using MatterHackers.MatterControl.PartPreviewWindow.PlusTab; using MatterHackers.MatterControl.PrintLibrary; @@ -382,6 +383,14 @@ namespace MatterHackers.MatterControl.PartPreviewWindow tabControl.SelectedTabKey = tabKey; } + public override void OnDraw(Graphics2D graphics2D) + { + base.OnDraw(graphics2D); + + var gear = new Gear2D(); + graphics2D.Render(gear, 300, 300, Color.Green); + } + private async void Instance_OpenNewFile(object sender, string filePath) { var history = ApplicationController.Instance.Library.PlatingHistory; diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 494c44d11..24d954939 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 494c44d1184e4cf30223bc8fa30348d287462349 +Subproject commit 24d95493956ba321855f428047d29d3984a2ca08 From 08b76177765bf43df0f103dfe548b9c72b2f9473 Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Fri, 25 Oct 2019 23:11:43 -0700 Subject: [PATCH 02/10] We can now make a rack gear --- .../DesignTools/Primitives/Gear2D.cs | 183 +++++++++--------- 1 file changed, 90 insertions(+), 93 deletions(-) diff --git a/MatterControlLib/DesignTools/Primitives/Gear2D.cs b/MatterControlLib/DesignTools/Primitives/Gear2D.cs index 006652ae7..5f21bdb53 100644 --- a/MatterControlLib/DesignTools/Primitives/Gear2D.cs +++ b/MatterControlLib/DesignTools/Primitives/Gear2D.cs @@ -43,24 +43,43 @@ namespace MatterHackers.MatterControl.DesignTools public class Gear2D : VertexSourceLegacySupport { - private double circularPitch; + private double _circularPitch = 8; + + ///

+ /// 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. + /// + public double CircularPitch + { + get => _circularPitch; + set + { + _circularPitch = value; + CalculateDependants(); + } + } + private double diametralPitch; private double addendum; - private double clearance; - private double pressureAngle; - private double backlash; - private double profileShift; + 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 double backlash = .05; + private double profileShift = 0; private double shiftedAddendum; private double outerRadius; private double angleToothToTooth; - private int toothCount; - private GearType gearType; - private int stepsPerToothAngle; + private int toothCount = 30; + private GearType gearType = GearType.External; + private int stepsPerToothAngle = 3; private double pitchDiameter; private double pitchRadius; - private Vector2 center; + private Vector2 center = Vector2.Zero; private Gear2D connectedGear; - private int centerHoleDiameter; + private int centerHoleDiameter = 4; public enum GearType { @@ -69,42 +88,17 @@ namespace MatterHackers.MatterControl.DesignTools Rack } - public Gear2D(double circularPitch = 8, - double pressureAngle = 20, - double clearance = 0.05, - double backlash = 0.05, - int toothCount = 30, - int centerHoleDiameter = 4, - double profileShift = 0, - int stepsPerToothAngle = 3, - GearType gearType = GearType.External) + public Gear2D() { - this.circularPitch = circularPitch; - this.pressureAngle = pressureAngle; - this.clearance = clearance; - this.backlash = backlash; - this.toothCount = toothCount; - this.centerHoleDiameter = centerHoleDiameter; - this.profileShift = profileShift; - this.stepsPerToothAngle = stepsPerToothAngle; + CalculateDependants(); + } - this.toothCount = toothCount; - this.gearType = gearType; - - this.circularPitch = circularPitch; // Distance from one face of a tooth to the corresponding face of an adjacent tooth on the same gear, measured along the pitch circle. + private void CalculateDependants() + { // convert circular pitch to diametral pitch - this.diametralPitch = Math.PI / this.circularPitch; // Ratio of the number of teeth to the pitch diameter + this.diametralPitch = Math.PI / this.CircularPitch; // Ratio of the number of teeth to the pitch diameter // this.circularPitch = Math.PI / this.diametralPitch; - this.pressureAngle = pressureAngle; // 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) - - this.centerHoleDiameter = centerHoleDiameter; - - this.clearance = clearance; - this.backlash = backlash; - this.center = Vector2.Zero; // center of the gear // this.angle = 0; // angle in degrees of the complete gear (changes during rotation animation) @@ -114,43 +108,41 @@ namespace MatterHackers.MatterControl.DesignTools // Addendum: Radial distance from pitch circle to outside circle. this.addendum = 1 / this.diametralPitch; - this.profileShift = profileShift; - // Typically no profile shift is used meaning that this.shiftedAddendum = this.addendum + // Typically no profile shift is used meaning that this.shiftedAddendum = this.addendum this.shiftedAddendum = this.addendum * (1 + this.profileShift); - //Outer Circle + // Outer Circle this.outerRadius = this.pitchRadius + this.shiftedAddendum; this.angleToothToTooth = 360 / this.toothCount; } - private VertexStorage _createRackShape() + private VertexStorage CreateRackShape() { IVertexSource rack = new VertexStorage(); // we draw one tooth in the middle and then five on either side - var toothCount = 41.0; for (var i = 0; i < toothCount; i++) { - var tooth = this._createRackTooth(); - tooth.translate(0, (0.5 + -toothCount / 2 + i) * this.circularPitch); + var tooth = this.CreateRackTooth(); + tooth = tooth.Translate(0, (0.5 + -toothCount / 2 + i) * this.CircularPitch); rack = rack.Union(tooth); } // creating the bar backing the teeth var rightX = -(this.addendum + this.clearance); var width = 4 * this.addendum; - var halfHeight = toothCount * this.circularPitch / 2; + var halfHeight = toothCount * this.CircularPitch / 2; var bar = new RoundedRect(rightX - width, -halfHeight, rightX, halfHeight, 0); var rackFinal = rack.Union(bar) as VertexStorage; - rackFinal.translate(this.addendum * this.profileShift, 0); + rackFinal.Translate(this.addendum * this.profileShift, 0); return rackFinal; } - private VertexStorage _createRackTooth() + private IVertexSource CreateRackTooth() { - var toothWidth = this.circularPitch / 2; + var toothWidth = this.CircularPitch / 2; var toothDepth = this.addendum + this.clearance; var sinPressureAngle = Math.Sin(this.pressureAngle * Math.PI / 180); @@ -176,27 +168,27 @@ namespace MatterHackers.MatterControl.DesignTools return tooth; } - private IVertexSource _createSingleTooth() + private IVertexSource CreateSingleTooth() { // create outer circle sector covering one tooth IVertexSource toothSectorPath = new VertexStorage(); // closed var toothSectorArc = new Arc(Vector2.Zero, new Vector2(this.outerRadius, this.outerRadius), MathHelper.DegreesToRadians(90), MathHelper.DegreesToRadians(90 - this.angleToothToTooth)); toothSectorPath = new JoinPaths(toothSectorPath, toothSectorArc); - var toothCutout = this.createToothCutout(); + var toothCutout = this.CreateToothCutout(); var tooth = toothSectorPath.Subtract(toothCutout); return tooth; } - private IVertexSource createToothCutout() + private IVertexSource CreateToothCutout() { var angleToothToTooth = 360 / this.toothCount; var angleStepSize = this.angleToothToTooth / this.stepsPerToothAngle; IVertexSource toothCutout = null; - var toothCutterShape = this.createToothCutter(); + var toothCutterShape = this.CreateToothCutter(); var bounds = toothCutterShape.GetBounds(); var lowerLeftCorner = new Vector2(bounds.Left, bounds.Bottom); @@ -215,18 +207,20 @@ namespace MatterHackers.MatterControl.DesignTools movedLowerLeftCorner = Vector2.Rotate(movedLowerLeftCorner, angle); lowerLeftCornerDistance = movedLowerLeftCorner.Length; - if (movedLowerLeftCorner.Length > this.outerRadius) { + if (movedLowerLeftCorner.Length > this.outerRadius) + { // the cutter is now completely outside the gear and no longer influences the shape of the gear tooth break; } // we move in both directions - var movedToothCutterShape = toothCutterShape.translate(xTranslation); + var movedToothCutterShape = toothCutterShape.Translate(xTranslation); movedToothCutterShape = movedToothCutterShape.rotateZ(angle); toothCutout = toothCutout.Union(movedToothCutterShape); - if (xTranslation[0] > 0) { - movedToothCutterShape = toothCutterShape.translate(new Vector2(-xTranslation[0], xTranslation[1])); + if (xTranslation[0] > 0) + { + movedToothCutterShape = toothCutterShape.Translate(new Vector2(-xTranslation[0], xTranslation[1])); movedToothCutterShape = movedToothCutterShape.rotateZ(-angle); toothCutout = toothCutout.Union(movedToothCutterShape); } @@ -239,10 +233,10 @@ namespace MatterHackers.MatterControl.DesignTools return toothCutout.rotateZ(-this.angleToothToTooth / 2); } - private IVertexSource createToothCutter() + private IVertexSource CreateToothCutter() { // we create a trapezoidal cutter as described at http://lcamtuf.coredump.cx/gcnc/ch6/ under the section 'Putting it all together' - var toothWidth = this.circularPitch / 2; + var toothWidth = this.CircularPitch / 2; var cutterDepth = this.addendum + this.clearance; var cutterOutsideLength = 3 * this.addendum; @@ -276,20 +270,23 @@ namespace MatterHackers.MatterControl.DesignTools IVertexSource _createInternalToothCutter() { - // To cut the internal gear teeth, the actual pinion comes close but we need to enlarge it so properly cater for clearance and backlash + // 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, - pressureAngle: pinion.pressureAngle, - clearance: -pinion.clearance, - backlash: -pinion.backlash, - toothCount: pinion.toothCount, - centerHoleDiameter: 0, - profileShift: pinion.profileShift, - stepsPerToothAngle: pinion.stepsPerToothAngle - ); + throw new NotImplementedException(); + var enlargedPinion = new Gear2D() + { + CircularPitch = pinion.CircularPitch, + // pressureAngle: pinion.pressureAngle, + // clearance: -pinion.clearance, + // backlash: -pinion.backlash, + // toothCount: pinion.toothCount, + // centerHoleDiameter: 0, + // profileShift: pinion.profileShift, + // stepsPerToothAngle: pinion.stepsPerToothAngle + }; - var tooth = enlargedPinion._createSingleTooth(); + var tooth = enlargedPinion.CreateSingleTooth(); return tooth.rotateZ(90 + 180 / enlargedPinion.toothCount); // we need a tooth pointing to the left } @@ -325,7 +322,7 @@ namespace MatterHackers.MatterControl.DesignTools var pinionRotationAngle = i * angleStepSize; var pinionCenterRayAngle = -pinionRotationAngle * pinion.toothCount / this.toothCount; - //var cutter = cutterTemplate; + // var cutter = cutterTemplate; cutter = cutterTemplate.rotateZ(pinionRotationAngle); cutter = cutter.Translate(-this.pitchRadius + this.connectedGear.pitchRadius, 0); cutter = cutter.rotateZ(pinionCenterRayAngle); @@ -401,7 +398,7 @@ namespace MatterHackers.MatterControl.DesignTools public override IEnumerable Vertices() { - var temp = new Ellipse(0, 0, pitchRadius, pitchRadius); + var temp = CreateRackShape(); return temp.Vertices(); } } @@ -416,7 +413,7 @@ public static class Extensions public static IVertexSource Union(this IVertexSource a, IVertexSource b) { - return a.Minus(b); + return a.Plus(b); } public static IVertexSource rotateZ(this IVertexSource a, double angle) @@ -424,7 +421,7 @@ public static class Extensions return new VertexSourceApplyTransform(a, Affine.NewRotation(angle)); } - public static IVertexSource translate(this IVertexSource a, Vector2 delta) + public static IVertexSource Translate(this IVertexSource a, Vector2 delta) { return new VertexSourceApplyTransform(a, Affine.NewTranslation(delta)); } @@ -449,12 +446,12 @@ public static class Extensions clipper.AddPaths(aPolys, PolyType.ptSubject, true); clipper.AddPaths(bPolys, PolyType.ptClip, true); - List> intersectedPolys = new List>(); - clipper.Execute(clipType, intersectedPolys); + List> outputPolys = new List>(); + clipper.Execute(clipType, outputPolys); - Clipper.CleanPolygons(intersectedPolys); + Clipper.CleanPolygons(outputPolys); - VertexStorage output = intersectedPolys.CreateVertexStorage(); + VertexStorage output = outputPolys.CreateVertexStorage(); output.Add(0, 0, ShapePath.FlagsAndCommand.Stop); @@ -467,16 +464,16 @@ public static class Extensions

An open source, browser based utility 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 my blog posts part 1 and part 2. If you prefer a standalone utility and you use Windows 64, see Doug Roger's port to C++.

The implementation is inspired by the subtractive process that Michal Zalewski's describes in part six of his excellent Guerrilla guide to CNC machining, mold making, and resin casting.

Instructions

-

Specify desired values in the parameters box and then click on the 'Update' button. The tooth count n1 of gear one defines various configurations: +

Specify desired values in the parameters box and then click on the 'Update' button. The tooth count n1 of gear one defines various configurations:

  • n1 > 0: A regular external gear
    Regular Spur Gear
  • n1 = 0: Rack and pinion
    Rack and Pinion
  • n1 < 0: An internal gear as used in planetary gears
    Internal Gear

-

The tool also supports profile shift to reduce the amount of undercut in gears with low tooth counts. +

The tool also supports profile shift to reduce the amount of undercut in gears with low tooth counts. + - var g_ExpandToCAGParams = {pathradius: 0.01, resolution: 2}; function main(params) @@ -485,7 +482,7 @@ public static class Extensions var qualitySettings = {resolution: params.resolution, stepsPerToothAngle: params.stepsPerToothAngle}; var gear1 = new Gear({ - circularPitch: params.circularPitch, + circularPitch: params.CircularPitch, pressureAngle: params.pressureAngle, clearance: params.clearance, backlash: params.backlash, @@ -495,7 +492,7 @@ public static class Extensions qualitySettings: qualitySettings }); var gear2 = new Gear({ - circularPitch: params.circularPitch, + circularPitch: params.CircularPitch, pressureAngle: params.pressureAngle, clearance: params.clearance, backlash: params.backlash, @@ -504,7 +501,7 @@ public static class Extensions profileShift: params.profileShift, qualitySettings: qualitySettings }); - + var gearSet = new GearSet( gear1, gear2, @@ -548,11 +545,11 @@ public static class Extensions return this._createInternalGearShape(); } else if (this.gearType == GearType.Rack) { - return this._createRackShape(); + return this.CreateRackShape(); } } Gear.prototype._createRegularGearShape = function() { - var tooth = this._createSingleTooth(); + 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. @@ -569,7 +566,7 @@ public static class Extensions centerCornerIndex = i; break; } - } + } var outerPoints = []; var outerCorners = []; var outterPointsCount = corners.length - 2; @@ -615,13 +612,13 @@ public static class Extensions centerCornerIndex = i; break; } - } + } var outerCorners = []; for(var i = 2; i < corners.length - 2; i++) { var corner = corners[(i + centerCornerIndex) % corners.length]; outerCorners.push(corner); } - + outerCorners.reverse(); var cornersCount = outerCorners.length; @@ -633,7 +630,7 @@ public static class Extensions outerCorners.push(rotatedCorner); } } - + var outerCorners = this._smoothConcaveCorners(outerCorners); var outerPoints = []; outerCorners.map(function(corner) { outerPoints.push([corner.x, corner.y]); }); From 4f0c248830350488981f912235bb86dd2e165222 Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Sat, 26 Oct 2019 08:45:11 -0700 Subject: [PATCH 03/10] working on regular gear shape --- .../DesignTools/Primitives/Gear2D.cs | 121 ++++++++++-------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/MatterControlLib/DesignTools/Primitives/Gear2D.cs b/MatterControlLib/DesignTools/Primitives/Gear2D.cs index 5f21bdb53..2325f2b1a 100644 --- a/MatterControlLib/DesignTools/Primitives/Gear2D.cs +++ b/MatterControlLib/DesignTools/Primitives/Gear2D.cs @@ -140,6 +140,70 @@ namespace MatterHackers.MatterControl.DesignTools return rackFinal; } + IVertexSource CreateRegularGearShape() + { + 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; + var corners = outlinePaths as VertexStorage; + + // first we need to find the corner that sits at the center + var centerCornerIndex = 0; + for (var i = 0; i < corners.Count; i++) + { + var corner = corners[i]; + if (corner.position.LengthSquared < 0.0000001) + { + centerCornerIndex = i; + break; + } + } + + var outerPoints = new VertexStorage(); + var outerCorners = new VertexStorage(); + var outterPointsCount = corners.Count - 2; + for (var i = 1; i < corners.Count - 1; i++) + { + var corner = corners[(i + centerCornerIndex) % corners.Count]; + if (i == 0) + { + outerCorners.MoveTo(corner.position); + outerPoints.MoveTo(corner.position); + } + else + { + outerCorners.LineTo(corner.position); + outerPoints.LineTo(corner.position); + } + } + + for (var i = 1; i < this.toothCount; i++) + { + var angle = i * this.angleToothToTooth; + var roatationMatrix = Affine.NewRotation(angle); + for (var j = 0; j < outerCorners.Count; j++) + { + var rotatedCorner = roatationMatrix.Transform(outerCorners[j].position); + outerPoints.MoveTo(rotatedCorner); + } + } + + var gearShape = outerPoints; + + if (this.centerHoleDiameter > 0) + { + var radius = this.centerHoleDiameter / 2; + var centerhole = new Ellipse(0, 0, radius, radius); + gearShape = gearShape.Subtract(centerhole) as VertexStorage; + } + + return gearShape.rotateZ(-90); + } + private IVertexSource CreateRackTooth() { var toothWidth = this.CircularPitch / 2; @@ -186,7 +250,7 @@ namespace MatterHackers.MatterControl.DesignTools var angleToothToTooth = 360 / this.toothCount; var angleStepSize = this.angleToothToTooth / this.stepsPerToothAngle; - IVertexSource toothCutout = null; + IVertexSource toothCutout = new VertexStorage(); var toothCutterShape = this.CreateToothCutter(); var bounds = toothCutterShape.GetBounds(); @@ -208,7 +272,7 @@ namespace MatterHackers.MatterControl.DesignTools lowerLeftCornerDistance = movedLowerLeftCorner.Length; if (movedLowerLeftCorner.Length > this.outerRadius) - { + { // the cutter is now completely outside the gear and no longer influences the shape of the gear tooth break; } @@ -219,7 +283,7 @@ namespace MatterHackers.MatterControl.DesignTools toothCutout = toothCutout.Union(movedToothCutterShape); if (xTranslation[0] > 0) - { + { movedToothCutterShape = toothCutterShape.Translate(new Vector2(-xTranslation[0], xTranslation[1])); movedToothCutterShape = movedToothCutterShape.rotateZ(-angle); toothCutout = toothCutout.Union(movedToothCutterShape); @@ -398,7 +462,8 @@ namespace MatterHackers.MatterControl.DesignTools public override IEnumerable Vertices() { - var temp = CreateRackShape(); + //var temp = CreateRackShape(); + var temp = CreateRegularGearShape(); return temp.Vertices(); } } @@ -408,7 +473,7 @@ public static class Extensions { public static IVertexSource Subtract(this IVertexSource a, IVertexSource b) { - return a.Plus(b); + return a.Minus(b); } public static IVertexSource Union(this IVertexSource a, IVertexSource b) @@ -548,52 +613,6 @@ public static class Extensions return this.CreateRackShape(); } } - Gear.prototype._createRegularGearShape = function() { - 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.getOutlinePaths(); - var corners = outlinePaths[0].points; - - // first we need to find the corner that sits at the center - var centerCornerIndex; - for(var i = 0; i < corners.length; i++) { - var corner = corners[i]; - if (corner.lengthSquared() < 0.0000001) { - centerCornerIndex = i; - break; - } - } - var outerPoints = []; - var outerCorners = []; - var outterPointsCount = corners.length - 2; - for(var i = 1; i < corners.length - 1; i++) { - var corner = corners[(i + centerCornerIndex) % corners.length]; - outerCorners.push(corner); - outerPoints.push([corner.x, corner.y]); - } - - for(var i = 1; i < this.toothCount; i++) { - var angle = i * this.angleToothToTooth; - var roatationMatrix = CSG.Matrix4x4.rotationZ(angle) - for (var j = 0; j < outerCorners.length; j++) { - var rotatedCorner = outerCorners[j].transform(roatationMatrix); - outerPoints.push([rotatedCorner.x, rotatedCorner.y]); - } - } - - var gearShape = CAG.fromPointsNoCheck(outerPoints); - - if (this.centerHoleDiameter > 0) { - var centerhole = CAG.circle({center: [-0, -0], radius: this.centerHoleDiameter / 2, resolution: this.qualitySettings.resolution}); - gearShape = gearShape.subtract(centerhole); - } - - return gearShape.rotateZ(-90); - } Gear.prototype._createInternalGearShape = function() { var singleTooth = this._createInternalToothProfile(); //return singleTooth; From 7ede20443e5b60826b0e04402cf380386187d83c Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Sun, 27 Oct 2019 16:01:49 -0700 Subject: [PATCH 04/10] regular gear is rendering --- .../DesignTools/Primitives/Gear2D.cs | 108 ++++++------------ 1 file changed, 35 insertions(+), 73 deletions(-) diff --git a/MatterControlLib/DesignTools/Primitives/Gear2D.cs b/MatterControlLib/DesignTools/Primitives/Gear2D.cs index 2325f2b1a..52d0429d8 100644 --- a/MatterControlLib/DesignTools/Primitives/Gear2D.cs +++ b/MatterControlLib/DesignTools/Primitives/Gear2D.cs @@ -117,7 +117,14 @@ namespace MatterHackers.MatterControl.DesignTools this.angleToothToTooth = 360 / this.toothCount; } - private VertexStorage CreateRackShape() + public override IEnumerable Vertices() + { + // return CreateRackShape().Vertices(); + // return CreateRegularGearShape().Vertices(); + return CreateSingleTooth().tooth.Vertices(); + } + + private IVertexSource CreateRackShape() { IVertexSource rack = new VertexStorage(); @@ -140,7 +147,7 @@ namespace MatterHackers.MatterControl.DesignTools return rackFinal; } - IVertexSource CreateRegularGearShape() + private IVertexSource CreateRegularGearShape() { var tooth = this.CreateSingleTooth(); @@ -148,51 +155,18 @@ namespace MatterHackers.MatterControl.DesignTools // 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; - var corners = outlinePaths as VertexStorage; + var outlinePaths = tooth.tooth; // first we need to find the corner that sits at the center - var centerCornerIndex = 0; - for (var i = 0; i < corners.Count; i++) - { - var corner = corners[i]; - if (corner.position.LengthSquared < 0.0000001) - { - centerCornerIndex = i; - break; - } - } - - var outerPoints = new VertexStorage(); - var outerCorners = new VertexStorage(); - var outterPointsCount = corners.Count - 2; - for (var i = 1; i < corners.Count - 1; i++) - { - var corner = corners[(i + centerCornerIndex) % corners.Count]; - if (i == 0) - { - outerCorners.MoveTo(corner.position); - outerPoints.MoveTo(corner.position); - } - else - { - outerCorners.LineTo(corner.position); - outerPoints.LineTo(corner.position); - } - } - for (var i = 1; i < this.toothCount; i++) { var angle = i * this.angleToothToTooth; - var roatationMatrix = Affine.NewRotation(angle); - for (var j = 0; j < outerCorners.Count; j++) - { - var rotatedCorner = roatationMatrix.Transform(outerCorners[j].position); - outerPoints.MoveTo(rotatedCorner); - } + var roatationMatrix = Affine.NewRotation(MathHelper.DegreesToRadians(angle)); + var rotatedCorner = new VertexSourceApplyTransform(tooth.tooth, roatationMatrix); + outlinePaths = new CombinePaths(outlinePaths, rotatedCorner); } - var gearShape = outerPoints; + var gearShape = tooth.wheel.Subtract(outlinePaths); if (this.centerHoleDiameter > 0) { @@ -201,7 +175,7 @@ namespace MatterHackers.MatterControl.DesignTools gearShape = gearShape.Subtract(centerhole) as VertexStorage; } - return gearShape.rotateZ(-90); + return gearShape;//.RotateZDegrees(-90); } private IVertexSource CreateRackTooth() @@ -232,17 +206,14 @@ namespace MatterHackers.MatterControl.DesignTools return tooth; } - private IVertexSource CreateSingleTooth() + private (IVertexSource tooth, IVertexSource wheel) CreateSingleTooth() { // create outer circle sector covering one tooth - IVertexSource toothSectorPath = new VertexStorage(); // closed - var toothSectorArc = new Arc(Vector2.Zero, new Vector2(this.outerRadius, this.outerRadius), MathHelper.DegreesToRadians(90), MathHelper.DegreesToRadians(90 - this.angleToothToTooth)); - toothSectorPath = new JoinPaths(toothSectorPath, toothSectorArc); + var toothSectorPath = new Arc(Vector2.Zero, new Vector2(this.outerRadius, this.outerRadius), MathHelper.DegreesToRadians(90), MathHelper.DegreesToRadians(90 - this.angleToothToTooth)); - var toothCutout = this.CreateToothCutout(); - var tooth = toothSectorPath.Subtract(toothCutout); + var toothCutOut = CreateToothCutout(); - return tooth; + return (toothCutOut, toothSectorPath); } private IVertexSource CreateToothCutout() @@ -260,7 +231,6 @@ namespace MatterHackers.MatterControl.DesignTools // 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 lowerLeftCornerDistance = 0.0; var stepCounter = 0; while (true) { @@ -268,9 +238,8 @@ namespace MatterHackers.MatterControl.DesignTools var xTranslation = new Vector2(angle * Math.PI / 180 * this.pitchRadius, 0); var movedLowerLeftCorner = lowerLeftCorner + xTranslation; - movedLowerLeftCorner = Vector2.Rotate(movedLowerLeftCorner, angle); + movedLowerLeftCorner = Vector2.Rotate(movedLowerLeftCorner, MathHelper.DegreesToRadians(angle)); - lowerLeftCornerDistance = movedLowerLeftCorner.Length; if (movedLowerLeftCorner.Length > this.outerRadius) { // the cutter is now completely outside the gear and no longer influences the shape of the gear tooth @@ -279,13 +248,13 @@ namespace MatterHackers.MatterControl.DesignTools // we move in both directions var movedToothCutterShape = toothCutterShape.Translate(xTranslation); - movedToothCutterShape = movedToothCutterShape.rotateZ(angle); + movedToothCutterShape = movedToothCutterShape.RotateZDegrees(angle); toothCutout = toothCutout.Union(movedToothCutterShape); if (xTranslation[0] > 0) { movedToothCutterShape = toothCutterShape.Translate(new Vector2(-xTranslation[0], xTranslation[1])); - movedToothCutterShape = movedToothCutterShape.rotateZ(-angle); + movedToothCutterShape = movedToothCutterShape.RotateZDegrees(-angle); toothCutout = toothCutout.Union(movedToothCutterShape); } @@ -294,7 +263,7 @@ namespace MatterHackers.MatterControl.DesignTools toothCutout = this._smoothConcaveCorners(toothCutout); - return toothCutout.rotateZ(-this.angleToothToTooth / 2); + return toothCutout.RotateZDegrees(-this.angleToothToTooth / 2); } private IVertexSource CreateToothCutter() @@ -326,13 +295,13 @@ namespace MatterHackers.MatterControl.DesignTools return cutterPath; } - IVertexSource _smoothConvexCorners(IVertexSource corners) + private IVertexSource _smoothConvexCorners(IVertexSource corners) { // removes single convex corners located between concave corners return this._smoothCorners(corners, true); // removeSingleConvex } - IVertexSource _createInternalToothCutter() + private IVertexSource _createInternalToothCutter() { // 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; @@ -351,10 +320,10 @@ namespace MatterHackers.MatterControl.DesignTools }; var tooth = enlargedPinion.CreateSingleTooth(); - return tooth.rotateZ(90 + 180 / enlargedPinion.toothCount); // we need a tooth pointing to the left + //return tooth.RotateZDegrees(90 + 180 / enlargedPinion.toothCount); // we need a tooth pointing to the left } - IVertexSource _createInternalToothProfile() + private IVertexSource _createInternalToothProfile() { var radius = this.pitchRadius + (1 - this.profileShift) * this.addendum + this.clearance; var angleToothToTooth = 360 / this.toothCount; @@ -387,15 +356,15 @@ namespace MatterHackers.MatterControl.DesignTools var pinionCenterRayAngle = -pinionRotationAngle * pinion.toothCount / this.toothCount; // var cutter = cutterTemplate; - cutter = cutterTemplate.rotateZ(pinionRotationAngle); + cutter = cutterTemplate.RotateZDegrees(pinionRotationAngle); cutter = cutter.Translate(-this.pitchRadius + this.connectedGear.pitchRadius, 0); - cutter = cutter.rotateZ(pinionCenterRayAngle); + cutter = cutter.RotateZDegrees(pinionCenterRayAngle); toothShape = toothShape.Subtract(cutter); - cutter = cutterTemplate.rotateZ(-pinionRotationAngle); + cutter = cutterTemplate.RotateZDegrees(-pinionRotationAngle); cutter = cutter.Translate(-this.pitchRadius + this.connectedGear.pitchRadius, 0); - cutter = cutter.rotateZ(-pinionCenterRayAngle); + cutter = cutter.RotateZDegrees(-pinionCenterRayAngle); toothShape = toothShape.Subtract(cutter); } @@ -404,13 +373,13 @@ namespace MatterHackers.MatterControl.DesignTools } - IVertexSource _smoothConcaveCorners(IVertexSource corners) + private IVertexSource _smoothConcaveCorners(IVertexSource corners) { // removes single concave corners located between convex corners return this._smoothCorners(corners, false); // removeSingleConvex } - IVertexSource _smoothCorners(IVertexSource corners_in, bool removeSingleConvex) + private IVertexSource _smoothCorners(IVertexSource corners_in, bool removeSingleConvex) { var corners = corners_in as VertexStorage; var isConvex = new List(); @@ -459,13 +428,6 @@ namespace MatterHackers.MatterControl.DesignTools return cleanedUpCorners; } - - public override IEnumerable Vertices() - { - //var temp = CreateRackShape(); - var temp = CreateRegularGearShape(); - return temp.Vertices(); - } } } @@ -481,9 +443,9 @@ public static class Extensions return a.Plus(b); } - public static IVertexSource rotateZ(this IVertexSource a, double angle) + public static IVertexSource RotateZDegrees(this IVertexSource a, double angle) { - return new VertexSourceApplyTransform(a, Affine.NewRotation(angle)); + return new VertexSourceApplyTransform(a, Affine.NewRotation(MathHelper.DegreesToRadians(angle))); } public static IVertexSource Translate(this IVertexSource a, Vector2 delta) From 1cec7420c55705fd487400fa406b792dfaaf5d5e Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Mon, 4 Nov 2019 08:36:26 -0800 Subject: [PATCH 05/10] Normal gear working mostly --- .../DesignTools/Primitives/Gear2D.cs | 292 +++++++----------- .../PartPreviewWindow/MainViewWidget.cs | 10 - Submodules/agg-sharp | 2 +- 3 files changed, 119 insertions(+), 185 deletions(-) diff --git a/MatterControlLib/DesignTools/Primitives/Gear2D.cs b/MatterControlLib/DesignTools/Primitives/Gear2D.cs index 52d0429d8..b7a3f5a59 100644 --- a/MatterControlLib/DesignTools/Primitives/Gear2D.cs +++ b/MatterControlLib/DesignTools/Primitives/Gear2D.cs @@ -1,6 +1,6 @@ /* Involute Spur Gear Builder (c) 2014 Dr. Rainer Hessmer -ported to C# 2018 by Lars Brubaker +ported to C# 2019 by Lars Brubaker Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -38,9 +38,15 @@ using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.DesignTools { - // using Polygons = List>; - // using Polygon = List; - + // 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 public class Gear2D : VertexSourceLegacySupport { private double _circularPitch = 8; @@ -72,9 +78,21 @@ namespace MatterHackers.MatterControl.DesignTools private double shiftedAddendum; private double outerRadius; private double angleToothToTooth; - private int toothCount = 30; + + private int _toothCount = 30; + public int ToothCount + { + get => _toothCount; + + set + { + _toothCount = value; + CalculateDependants(); + } + } + private GearType gearType = GearType.External; - private int stepsPerToothAngle = 3; + private int stepsPerToothAngle = 10; private double pitchDiameter; private double pitchRadius; private Vector2 center = Vector2.Zero; @@ -103,7 +121,7 @@ namespace MatterHackers.MatterControl.DesignTools // this.angle = 0; // angle in degrees of the complete gear (changes during rotation animation) // Pitch diameter: Diameter of pitch circle. - this.pitchDiameter = this.toothCount / this.diametralPitch; + this.pitchDiameter = this.ToothCount / this.diametralPitch; this.pitchRadius = this.pitchDiameter / 2; // Addendum: Radial distance from pitch circle to outside circle. @@ -114,14 +132,20 @@ namespace MatterHackers.MatterControl.DesignTools // Outer Circle this.outerRadius = this.pitchRadius + this.shiftedAddendum; - this.angleToothToTooth = 360 / this.toothCount; + this.angleToothToTooth = 360 / this.ToothCount; } public override IEnumerable Vertices() { - // return CreateRackShape().Vertices(); - // return CreateRegularGearShape().Vertices(); - return CreateSingleTooth().tooth.Vertices(); + IVertexSource shape; + // shape = CreateRackShape(); + shape = CreateRegularGearShape(); + // shape = CreateInternalGearShape(); + // shape = CreateSingleTooth().tooth; + + shape = new VertexSourceApplyTransform(shape, Affine.NewScaling(5)); + + return shape.Vertices(); } private IVertexSource CreateRackShape() @@ -129,17 +153,17 @@ namespace MatterHackers.MatterControl.DesignTools IVertexSource rack = new VertexStorage(); // we draw one tooth in the middle and then five on either side - for (var i = 0; i < toothCount; i++) + for (var i = 0; i < ToothCount; i++) { var tooth = this.CreateRackTooth(); - tooth = tooth.Translate(0, (0.5 + -toothCount / 2 + i) * this.CircularPitch); + tooth = tooth.Translate(0, (0.5 + -ToothCount / 2 + i) * this.CircularPitch); rack = rack.Union(tooth); } // creating the bar backing the teeth var rightX = -(this.addendum + this.clearance); var width = 4 * this.addendum; - var halfHeight = toothCount * this.CircularPitch / 2; + var halfHeight = ToothCount * this.CircularPitch / 2; var bar = new RoundedRect(rightX - width, -halfHeight, rightX, halfHeight, 0); var rackFinal = rack.Union(bar) as VertexStorage; @@ -158,7 +182,7 @@ namespace MatterHackers.MatterControl.DesignTools var outlinePaths = tooth.tooth; // first we need to find the corner that sits at the center - for (var i = 1; i < this.toothCount; i++) + for (var i = 1; i < this.ToothCount; i++) { var angle = i * this.angleToothToTooth; var roatationMatrix = Affine.NewRotation(MathHelper.DegreesToRadians(angle)); @@ -171,13 +195,76 @@ namespace MatterHackers.MatterControl.DesignTools if (this.centerHoleDiameter > 0) { var radius = this.centerHoleDiameter / 2; - var centerhole = new Ellipse(0, 0, radius, radius); + var centerhole = new Ellipse(0, 0, radius, radius) + { + ResolutionScale = 10 + }; gearShape = gearShape.Subtract(centerhole) as VertexStorage; } - return gearShape;//.RotateZDegrees(-90); + return gearShape;// .RotateZDegrees(-90); } + /* + private IVertexSource CreateInternalGearShape() + { + var singleTooth = this._createInternalToothProfile(); + // return singleTooth; + + var outlinePaths = singleTooth.getOutlinePaths(); + var corners = outlinePaths[0].points; + + // first we need to find the corner that sits at the center + var centerCornerIndex; + var radius = this.pitchRadius + (1 + this.profileShift) * this.addendum + this.clearance; + + var delta = 0.0000001; + for (var i = 0; i < corners.length; i++) + { + var corner = corners[i]; + if (corner.y < delta && (corner.x + radius) < delta) + { + centerCornerIndex = i; + break; + } + } + + var outerCorners = []; + for (var i = 2; i < corners.length - 2; i++) + { + var corner = corners[(i + centerCornerIndex) % corners.length]; + outerCorners.push(corner); + } + + outerCorners.reverse(); + var cornersCount = outerCorners.length; + + for (var i = 1; i < this.toothCount; i++) + { + var angle = i * this.angleToothToTooth; + var roatationMatrix = CSG.Matrix4x4.rotationZ(angle); + for (var j = 0; j < cornersCount; j++) + { + var rotatedCorner = outerCorners[j].transform(roatationMatrix); + outerCorners.push(rotatedCorner); + } + } + + var outerCorners = this._smoothConcaveCorners(outerCorners); + var outerPoints = []; + outerCorners.map(function(corner) { outerPoints.push([corner.x, corner.y]); }); + + var innerRadius = this.pitchRadius + (1 - this.profileShift) * this.addendum + this.clearance; + var outerRadius = innerRadius + 4 * this.addendum; + var outerCircle = CAG.circle({ center: this.center, radius: outerRadius, resolution: this.qualitySettings.resolution}); + // return outerCircle; + + var gearCutout = CAG.fromPointsNoCheck(outerPoints); + // return gearCutout; + return outerCircle.subtract(gearCutout); + } + */ + private IVertexSource CreateRackTooth() { var toothWidth = this.CircularPitch / 2; @@ -209,7 +296,10 @@ namespace MatterHackers.MatterControl.DesignTools private (IVertexSource tooth, IVertexSource wheel) CreateSingleTooth() { // create outer circle sector covering one tooth - var toothSectorPath = new Arc(Vector2.Zero, new Vector2(this.outerRadius, this.outerRadius), MathHelper.DegreesToRadians(90), MathHelper.DegreesToRadians(90 - this.angleToothToTooth)); + var toothSectorPath = new Arc(Vector2.Zero, new Vector2(this.outerRadius, this.outerRadius), MathHelper.DegreesToRadians(90), MathHelper.DegreesToRadians(90 - this.angleToothToTooth)) + { + ResolutionScale = 10 + }; var toothCutOut = CreateToothCutout(); @@ -218,7 +308,7 @@ namespace MatterHackers.MatterControl.DesignTools private IVertexSource CreateToothCutout() { - var angleToothToTooth = 360 / this.toothCount; + var angleToothToTooth = 360 / this.ToothCount; var angleStepSize = this.angleToothToTooth / this.stepsPerToothAngle; IVertexSource toothCutout = new VertexStorage(); @@ -320,13 +410,13 @@ namespace MatterHackers.MatterControl.DesignTools }; var tooth = enlargedPinion.CreateSingleTooth(); - //return tooth.RotateZDegrees(90 + 180 / enlargedPinion.toothCount); // we need a tooth pointing to the left + // return tooth.RotateZDegrees(90 + 180 / enlargedPinion.toothCount); // we need a tooth pointing to the left } private IVertexSource _createInternalToothProfile() { var radius = this.pitchRadius + (1 - this.profileShift) * this.addendum + this.clearance; - var angleToothToTooth = 360 / this.toothCount; + var angleToothToTooth = 360 / this.ToothCount; var sin = Math.Sin(angleToothToTooth / 2 * Math.PI / 180); var cos = Math.Cos(angleToothToTooth / 2 * Math.PI / 180); @@ -338,7 +428,11 @@ namespace MatterHackers.MatterControl.DesignTools fullSector.LineTo(-(radius * cos), -radius * sin); var innerRadius = radius - (2 * this.addendum + this.clearance); - var innerCircle = new Ellipse(this.center, innerRadius); + var innerCircle = new Ellipse(this.center, innerRadius) + { + ResolutionScale = 10 + }; + var sector = fullSector.Subtract(innerCircle); var cutterTemplate = this._createInternalToothCutter(); @@ -353,7 +447,7 @@ namespace MatterHackers.MatterControl.DesignTools for (var i = 1; i < stepsPerTooth; i++) { var pinionRotationAngle = i * angleStepSize; - var pinionCenterRayAngle = -pinionRotationAngle * pinion.toothCount / this.toothCount; + var pinionCenterRayAngle = -pinionRotationAngle * pinion.ToothCount / this.ToothCount; // var cutter = cutterTemplate; cutter = cutterTemplate.RotateZDegrees(pinionRotationAngle); @@ -484,154 +578,4 @@ public static class Extensions return output; } -} - -/* -

Involute Spur Gear Builder (C) 2014 Dr. Rainer Hessmer

-

An open source, browser based utility 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 my blog posts part 1 and part 2. If you prefer a standalone utility and you use Windows 64, see Doug Roger's port to C++.

-

The implementation is inspired by the subtractive process that Michal Zalewski's describes in part six of his excellent Guerrilla guide to CNC machining, mold making, and resin casting.

-

Instructions

-

Specify desired values in the parameters box and then click on the 'Update' button. The tooth count n1 of gear one defines various configurations: -

    -
  • n1 > 0: A regular external gear
    Regular Spur Gear
  • -
  • n1 = 0: Rack and pinion
    Rack and Pinion
  • -
  • n1 < 0: An internal gear as used in planetary gears
    Internal Gear
  • -
-

-

The tool also supports profile shift to reduce the amount of undercut in gears with low tooth counts. - - - var g_ExpandToCAGParams = {pathradius: 0.01, resolution: 2}; - - function main(params) - { - // Main entry point; here we construct our solid: - var qualitySettings = {resolution: params.resolution, stepsPerToothAngle: params.stepsPerToothAngle}; - - var gear1 = new Gear({ - circularPitch: params.CircularPitch, - pressureAngle: params.pressureAngle, - clearance: params.clearance, - backlash: params.backlash, - toothCount: params.wheel1ToothCount, - centerHoleDiameter: params.wheel1CenterHoleDiamater, - profileShift: -params.profileShift, - qualitySettings: qualitySettings - }); - var gear2 = new Gear({ - circularPitch: params.CircularPitch, - pressureAngle: params.pressureAngle, - clearance: params.clearance, - backlash: params.backlash, - toothCount: params.wheel2ToothCount, - centerHoleDiameter: params.wheel2CenterHoleDiamater, - profileShift: params.profileShift, - qualitySettings: qualitySettings - }); - - var gearSet = new GearSet( - gear1, - gear2, - params.showOption); - - var shape = gearSet.createShape(); - return shape; - } - - function getParameterDefinitions() { - return [ - { name: 'circularPitch', caption: 'Circular pitch (the circumference of the pitch circle divided by the number of teeth):', type: 'float', initial: 8 }, - { name: 'pressureAngle', caption: 'Pressure Angle (common values are 14.5, 20 and 25 degrees):', type: 'float', initial: 20 }, - { name: 'clearance', caption: 'Clearance (minimal distance between the apex of a tooth and the trough of the other gear; in length units):', type: 'float', initial: 0.05 }, - { name: 'backlash', caption: 'Backlash (minimal distance between meshing gears; in length units):', type: 'float', initial: 0.05 }, - { name: 'profileShift', caption: 'Profile Shift (indicates what portion of gear one\'s addendum height should be shifted to gear two. E.g., a value of 0.1 means the adddendum of gear two is increased by a factor of 1.1 while the height of the addendum of gear one is reduced to 0.9 of its normal height.):', type: 'float', initial: 0.0 }, - { name: 'wheel1ToothCount', caption: 'Wheel 1 Tooth Count (n1 > 0: external gear; n1 = 0: rack; n1 < 0: internal gear):', type: 'int', initial: 30 }, - { name: 'wheel1CenterHoleDiamater', caption: 'Wheel 1 Center Hole Diameter (0 for no hole):', type: 'float', initial: 4 }, - { name: 'wheel2ToothCount', caption: 'Wheel 2 Tooth Count:', type: 'int', initial: 8 }, - { name: 'wheel2CenterHoleDiamater', caption: 'Wheel 2 Center Hole Diameter (0 for no hole):', type: 'float', initial: 4 }, - { name: 'showOption', caption: 'Show:', type: 'choice', values: [3, 1, 2], initial: 3, captions: ["Wheel 1 and Wheel 2", "Wheel 1 Only", "Wheel 2 Only"]}, - { name: 'stepsPerToothAngle', caption: 'Rotation steps per tooth angle when assembling the tooth profile (3 = draft, 10 = good quality). Increasing the value will result in smoother profiles at the cost of significantly higher calcucation time. Incease in small increments and check the result by zooming in.', type: 'int', initial: 3 }, - { name: 'resolution', caption: 'Number of segments per 360 degree of rotation (only used for circles and arcs); 90 is plenty:', type: 'int', initial: 30 }, - ]; - } - - // Start base class Gear - var Gear = (function () { - Gear.prototype.getZeroedShape = function() { - // return the gear shape center on the origin and rotation angle 0. - if (this.zeroedShape == null) { - this.zeroedShape = this._createZeroedShape(); - } - return this.zeroedShape; - } - Gear.prototype._createZeroedShape = function() { - if (this.gearType == GearType.Regular) { - return this._createRegularGearShape(); - } - else if (this.gearType == GearType.Internal) { - return this._createInternalGearShape(); - } - else if (this.gearType == GearType.Rack) { - return this.CreateRackShape(); - } - } - Gear.prototype._createInternalGearShape = function() { - var singleTooth = this._createInternalToothProfile(); - //return singleTooth; - - var outlinePaths = singleTooth.getOutlinePaths(); - var corners = outlinePaths[0].points; - - // first we need to find the corner that sits at the center - var centerCornerIndex; - var radius = this.pitchRadius + ( 1 + this.profileShift) * this.addendum + this.clearance; - - var delta = 0.0000001; - for(var i = 0; i < corners.length; i++) { - var corner = corners[i]; - if (corner.y < delta && (corner.x + radius) < delta) { - centerCornerIndex = i; - break; - } - } - var outerCorners = []; - for(var i = 2; i < corners.length - 2; i++) { - var corner = corners[(i + centerCornerIndex) % corners.length]; - outerCorners.push(corner); - } - - outerCorners.reverse(); - var cornersCount = outerCorners.length; - - for(var i = 1; i < this.toothCount; i++) { - var angle = i * this.angleToothToTooth; - var roatationMatrix = CSG.Matrix4x4.rotationZ(angle) - for (var j = 0; j < cornersCount; j++) { - var rotatedCorner = outerCorners[j].transform(roatationMatrix); - outerCorners.push(rotatedCorner); - } - } - - var outerCorners = this._smoothConcaveCorners(outerCorners); - var outerPoints = []; - outerCorners.map(function(corner) { outerPoints.push([corner.x, corner.y]); }); - - var innerRadius = this.pitchRadius + (1 - this.profileShift) * this.addendum + this.clearance; - var outerRadius = innerRadius + 4 * this.addendum; - var outerCircle = CAG.circle({center: this.center, radius: outerRadius, resolution: this.qualitySettings.resolution}); - //return outerCircle; - - var gearCutout = CAG.fromPointsNoCheck(outerPoints); - //return gearCutout; - return outerCircle.subtract(gearCutout); - } - Gear.prototype.pointsToString = function(points) { - var result = "["; - points.map(function(point) { - result += "[" + point.x + "," + point.y + "],"; - }); - return result + "]"; - } - return Gear; - })(); -*/ +} \ No newline at end of file diff --git a/MatterControlLib/PartPreviewWindow/MainViewWidget.cs b/MatterControlLib/PartPreviewWindow/MainViewWidget.cs index 4fd1d206c..8effdbc58 100644 --- a/MatterControlLib/PartPreviewWindow/MainViewWidget.cs +++ b/MatterControlLib/PartPreviewWindow/MainViewWidget.cs @@ -38,8 +38,6 @@ using MatterHackers.Agg.Platform; using MatterHackers.Agg.UI; using MatterHackers.DataConverters3D; using MatterHackers.Localizations; -using MatterHackers.MatterControl.CustomWidgets; -using MatterHackers.MatterControl.DesignTools; using MatterHackers.MatterControl.Library; using MatterHackers.MatterControl.PartPreviewWindow.PlusTab; using MatterHackers.MatterControl.PrintLibrary; @@ -383,14 +381,6 @@ namespace MatterHackers.MatterControl.PartPreviewWindow tabControl.SelectedTabKey = tabKey; } - public override void OnDraw(Graphics2D graphics2D) - { - base.OnDraw(graphics2D); - - var gear = new Gear2D(); - graphics2D.Render(gear, 300, 300, Color.Green); - } - private async void Instance_OpenNewFile(object sender, string filePath) { var history = ApplicationController.Instance.Library.PlatingHistory; diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 24d954939..588a0dae3 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 24d95493956ba321855f428047d29d3984a2ca08 +Subproject commit 588a0dae3789edbc5f4895c5091901b1c4f22b14 From 2ff1220d2c8e90676a578d22230dda05b681f643 Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Fri, 8 Nov 2019 22:52:33 -0800 Subject: [PATCH 06/10] have the internal shape building --- .../DesignTools/Primitives/Gear2D.cs | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/MatterControlLib/DesignTools/Primitives/Gear2D.cs b/MatterControlLib/DesignTools/Primitives/Gear2D.cs index b7a3f5a59..4c7afce88 100644 --- a/MatterControlLib/DesignTools/Primitives/Gear2D.cs +++ b/MatterControlLib/DesignTools/Primitives/Gear2D.cs @@ -115,10 +115,10 @@ namespace MatterHackers.MatterControl.DesignTools { // 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; + // this.circularPitch = Math.PI / this.diametralPitch; this.center = Vector2.Zero; // center of the gear - // this.angle = 0; // angle in degrees of the complete gear (changes during rotation animation) + // this.angle = 0; // angle in degrees of the complete gear (changes during rotation animation) // Pitch diameter: Diameter of pitch circle. this.pitchDiameter = this.ToothCount / this.diametralPitch; @@ -142,6 +142,7 @@ namespace MatterHackers.MatterControl.DesignTools shape = CreateRegularGearShape(); // shape = CreateInternalGearShape(); // shape = CreateSingleTooth().tooth; + // shape = CreateInternalGearShape(); shape = new VertexSourceApplyTransform(shape, Affine.NewScaling(5)); @@ -205,65 +206,66 @@ namespace MatterHackers.MatterControl.DesignTools return gearShape;// .RotateZDegrees(-90); } - /* private IVertexSource CreateInternalGearShape() { var singleTooth = this._createInternalToothProfile(); // return singleTooth; - var outlinePaths = singleTooth.getOutlinePaths(); - var corners = outlinePaths[0].points; + var outlinePaths = singleTooth; + var corners = outlinePaths as VertexStorage; // first we need to find the corner that sits at the center - var centerCornerIndex; + var centerCornerIndex = 0; var radius = this.pitchRadius + (1 + this.profileShift) * this.addendum + this.clearance; var delta = 0.0000001; - for (var i = 0; i < corners.length; i++) + for (var i = 0; i < corners.Count; i++) { var corner = corners[i]; - if (corner.y < delta && (corner.x + radius) < delta) + if (corner.Y < delta && (corner.X + radius) < delta) { centerCornerIndex = i; break; } } - var outerCorners = []; - for (var i = 2; i < corners.length - 2; i++) + var outerCorners = new VertexStorage(); + for (var i = 2; i < corners.Count - 2; i++) { - var corner = corners[(i + centerCornerIndex) % corners.length]; - outerCorners.push(corner); + var corner = corners[(i + centerCornerIndex) % corners.Count]; + outerCorners.add(corner.position); } - outerCorners.reverse(); - var cornersCount = outerCorners.length; + var reversedOuterCorners = new VertexStorage(); + foreach (var vertex in new ReversePath(outerCorners).Vertices()) + { + reversedOuterCorners.add(vertex.position); + } + outerCorners = reversedOuterCorners; - for (var i = 1; i < this.toothCount; i++) + var cornersCount = outerCorners.Count; + + for (var i = 1; i < this.ToothCount; i++) { var angle = i * this.angleToothToTooth; - var roatationMatrix = CSG.Matrix4x4.rotationZ(angle); + var roatationMatrix = Affine.NewRotation(MathHelper.DegreesToRadians(angle)); for (var j = 0; j < cornersCount; j++) { - var rotatedCorner = outerCorners[j].transform(roatationMatrix); - outerCorners.push(rotatedCorner); + var rotatedCorner = roatationMatrix.Transform(outerCorners[j].position); + outerCorners.add(rotatedCorner); } } - var outerCorners = this._smoothConcaveCorners(outerCorners); - var outerPoints = []; - outerCorners.map(function(corner) { outerPoints.push([corner.x, corner.y]); }); + outerCorners = this._smoothConcaveCorners(outerCorners) as VertexStorage; var innerRadius = this.pitchRadius + (1 - this.profileShift) * this.addendum + this.clearance; var outerRadius = innerRadius + 4 * this.addendum; - var outerCircle = CAG.circle({ center: this.center, radius: outerRadius, resolution: this.qualitySettings.resolution}); + var outerCircle = new Ellipse(this.center, outerRadius, outerRadius); // return outerCircle; - var gearCutout = CAG.fromPointsNoCheck(outerPoints); // return gearCutout; - return outerCircle.subtract(gearCutout); + return outerCircle.Subtract(outerCorners); } - */ private IVertexSource CreateRackTooth() { @@ -396,7 +398,6 @@ namespace MatterHackers.MatterControl.DesignTools // 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; - throw new NotImplementedException(); var enlargedPinion = new Gear2D() { CircularPitch = pinion.CircularPitch, @@ -410,7 +411,7 @@ namespace MatterHackers.MatterControl.DesignTools }; var tooth = enlargedPinion.CreateSingleTooth(); - // return tooth.RotateZDegrees(90 + 180 / enlargedPinion.toothCount); // we need a tooth pointing to the left + return tooth.tooth.RotateZDegrees(90 + 180 / enlargedPinion.ToothCount); // we need a tooth pointing to the left } private IVertexSource _createInternalToothProfile() @@ -562,12 +563,12 @@ public static class Extensions List> aPolys = a.CreatePolygons(); List> bPolys = b.CreatePolygons(); - Clipper clipper = new Clipper(); + var clipper = new Clipper(); clipper.AddPaths(aPolys, PolyType.ptSubject, true); clipper.AddPaths(bPolys, PolyType.ptClip, true); - List> outputPolys = new List>(); + var outputPolys = new List>(); clipper.Execute(clipType, outputPolys); Clipper.CleanPolygons(outputPolys); From e950a0f3c5a390ccd4182307126e42941650ae0a Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Sun, 10 Nov 2019 21:16:08 -0800 Subject: [PATCH 07/10] fixing problems with selection leaving tree in bad shape --- .../DesignTools/Operations/OperationSourceObject3D.cs | 6 ++++++ .../PartPreviewWindow/View3D/View3DWidget.cs | 10 ++++++++++ Submodules/agg-sharp | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/MatterControlLib/DesignTools/Operations/OperationSourceObject3D.cs b/MatterControlLib/DesignTools/Operations/OperationSourceObject3D.cs index c235cfc25..a2d20570c 100644 --- a/MatterControlLib/DesignTools/Operations/OperationSourceObject3D.cs +++ b/MatterControlLib/DesignTools/Operations/OperationSourceObject3D.cs @@ -243,6 +243,12 @@ namespace MatterHackers.MatterControl.DesignTools.Operations } // and select this + var rootItem = this.Ancestors().Where(i => scene.Children.Contains(i)).FirstOrDefault(); + if (rootItem != null) + { + scene.SelectedItem = rootItem; + } + scene.SelectedItem = this; this.Invalidate(InvalidateType.Children); diff --git a/MatterControlLib/PartPreviewWindow/View3D/View3DWidget.cs b/MatterControlLib/PartPreviewWindow/View3D/View3DWidget.cs index 555a11537..baf444c00 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/View3DWidget.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/View3DWidget.cs @@ -1620,6 +1620,12 @@ namespace MatterHackers.MatterControl.PartPreviewWindow private void Scene_Invalidated(object sender, InvalidateArgs e) { + if (Scene.Descendants().Count() != lastSceneDescendantsCount) + { + rebuildTreePending = true; + UiThread.RunOnIdle(this.RebuildTree); + } + if (e.InvalidateType.HasFlag(InvalidateType.Children) && !rebuildTreePending) { @@ -1636,10 +1642,13 @@ namespace MatterHackers.MatterControl.PartPreviewWindow rebuildTreePending = true; UiThread.RunOnIdle(this.RebuildTree); } + Scene.SelectedItem = null; Scene.SelectedItem = lastSelectedItem; } + lastSceneDescendantsCount = Scene.Descendants().Count(); + // Invalidate widget on scene invalidate this.Invalidate(); } @@ -1704,6 +1713,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow private bool assigningTreeNode; private FlowLayoutWidget treeNodeContainer; + private int lastSceneDescendantsCount; private InlineStringEdit workspaceName; public InteractiveScene Scene => sceneContext.Scene; diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 588a0dae3..0356160f1 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 588a0dae3789edbc5f4895c5091901b1c4f22b14 +Subproject commit 0356160f1d7611e314f1603808b7cf97ef020dbc From d51b4dec96c1a947bdbc4bef3ce6c790ad91e3b3 Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Sun, 10 Nov 2019 21:16:49 -0800 Subject: [PATCH 08/10] fixed a problem with int truncation --- MatterControlLib/DesignTools/Primitives/Gear2D.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MatterControlLib/DesignTools/Primitives/Gear2D.cs b/MatterControlLib/DesignTools/Primitives/Gear2D.cs index 4c7afce88..1435c0f2a 100644 --- a/MatterControlLib/DesignTools/Primitives/Gear2D.cs +++ b/MatterControlLib/DesignTools/Primitives/Gear2D.cs @@ -97,7 +97,7 @@ namespace MatterHackers.MatterControl.DesignTools private double pitchRadius; private Vector2 center = Vector2.Zero; private Gear2D connectedGear; - private int centerHoleDiameter = 4; + private double centerHoleDiameter = 4; public enum GearType { @@ -132,7 +132,7 @@ namespace MatterHackers.MatterControl.DesignTools // Outer Circle this.outerRadius = this.pitchRadius + this.shiftedAddendum; - this.angleToothToTooth = 360 / this.ToothCount; + this.angleToothToTooth = 360.0 / this.ToothCount; } public override IEnumerable Vertices() @@ -310,7 +310,7 @@ namespace MatterHackers.MatterControl.DesignTools private IVertexSource CreateToothCutout() { - var angleToothToTooth = 360 / this.ToothCount; + var angleToothToTooth = 360.0 / this.ToothCount; var angleStepSize = this.angleToothToTooth / this.stepsPerToothAngle; IVertexSource toothCutout = new VertexStorage(); From ae424640f9837de847b5c1c696ce54b6f4ed8074 Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Sun, 10 Nov 2019 21:17:04 -0800 Subject: [PATCH 09/10] refactoring errors --- .../DesignTools/Operations/TransformWrapperObject3D.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MatterControlLib/DesignTools/Operations/TransformWrapperObject3D.cs b/MatterControlLib/DesignTools/Operations/TransformWrapperObject3D.cs index 4c374293f..64738333e 100644 --- a/MatterControlLib/DesignTools/Operations/TransformWrapperObject3D.cs +++ b/MatterControlLib/DesignTools/Operations/TransformWrapperObject3D.cs @@ -99,6 +99,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations list.AddRange(ItemWithTransform.Children); }); } + Invalidate(InvalidateType.Children); } From ac30947d0a7025f3562f9a621eb497b0e0d76c1d Mon Sep 17 00:00:00 2001 From: Lars Brubaker Date: Mon, 11 Nov 2019 18:03:07 -0800 Subject: [PATCH 10/10] Making gear size correct --- MatterControlLib/DesignTools/Primitives/Gear2D.cs | 2 -- Submodules/MatterSlice | 2 +- Submodules/agg-sharp | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/MatterControlLib/DesignTools/Primitives/Gear2D.cs b/MatterControlLib/DesignTools/Primitives/Gear2D.cs index 1435c0f2a..c7bbe785d 100644 --- a/MatterControlLib/DesignTools/Primitives/Gear2D.cs +++ b/MatterControlLib/DesignTools/Primitives/Gear2D.cs @@ -144,8 +144,6 @@ namespace MatterHackers.MatterControl.DesignTools // shape = CreateSingleTooth().tooth; // shape = CreateInternalGearShape(); - shape = new VertexSourceApplyTransform(shape, Affine.NewScaling(5)); - return shape.Vertices(); } diff --git a/Submodules/MatterSlice b/Submodules/MatterSlice index 09299400c..8551764ee 160000 --- a/Submodules/MatterSlice +++ b/Submodules/MatterSlice @@ -1 +1 @@ -Subproject commit 09299400c077dcf7ee494dd95823daaa7c156bd9 +Subproject commit 8551764ee5116ad0d479442bfcba36385bc16594 diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 0356160f1..24360177b 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 0356160f1d7611e314f1603808b7cf97ef020dbc +Subproject commit 24360177b57b10f7d3cdc6dd97e57e76e8f95ade