Open CASCADE sprocket demo

The code below demonstrates how to generate a sprocket.


The generated sprocket looks like this:


A 3D sprocket

The tooth profile is constructed as follows (see DIN 8196):


Tooth profile construction 1

Tooth profile construction 2
#include <iostream>
#include <cmath>
#include <gp.hxx>

#include <gp_Ax1.hxx>
#include <gp_Circ.hxx>
#include <gp_Pln.hxx>

#include <gp_Pnt2d.hxx>
#include <gp_Vec2d.hxx>
#include <gp_Dir2d.hxx>
#include <gp_Ax2d.hxx>
#include <gp_Lin2d.hxx>
#include <gp_Circ2d.hxx>
#include <gp_Vec.hxx>

#include <Geom2d_Line.hxx>
#include <GCE2d_MakeLine.hxx>

#include <GC_MakeCircle.hxx>
#include <Geom_Circle.hxx>
#include <Handle_Geom_Circle.hxx>

#include <GCE2d_MakeCircle.hxx>
#include <Geom2d_Circle.hxx>
#include <Handle_Geom2d_Circle.hxx>

#include <GCE2d_MakeArcOfCircle.hxx>
#include <Geom2d_TrimmedCurve.hxx>
#include <Handle_Geom2d_TrimmedCurve.hxx>
#include <GCE2d_MakeSegment.hxx>

#include <GeomAPI.hxx>

#include <GC_MakePlane.hxx>
#include <Geom_Plane.hxx>
#include <Handle_Geom_Plane.hxx>

#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepPrimAPI_MakePrism.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <BRepPrimAPI_MakeCone.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <BRepPrimAPI_MakeRevol.hxx>

#include <BRepFeat_MakeCylindricalHole.hxx>

#include <BRepAlgoAPI_Fuse.hxx>
#include <BRepAlgoAPI_Cut.hxx>

#include <BRepTools.hxx>
#include <BRepLib.hxx>

#include <Geom2dAPI_InterCurveCurve.hxx>

#include <TopoDS_Vertex.hxx>
#include <TopoDS_Edge.hxx>

#include <gp_Trsf.hxx>

#include <GccAna_Circ2d2TanRad.hxx>

#include <BRepFilletAPI_MakeFillet2d.hxx>
#include <TopExp_Explorer.hxx>
#include <BRepTools_WireExplorer.hxx>

using namespace std;

// The basic inputs needed to build a sprocket
Standard_Real roller_diameter = 10.2;
Standard_Real pitch = 15.875;
Standard_Integer num_teeth = 40;
Standard_Real chain_width = 6.35;

// Dimensions derived from the provided inputs
Standard_Real roller_radius = roller_diameter / 2.0;
Standard_Real tooth_angle = (2 * M_PI) / num_teeth;
Standard_Real pitch_circle_diameter = pitch / sin(tooth_angle / 2.0);
Standard_Real pitch_circle_radius = pitch_circle_diameter / 2.0;

Standard_Real roller_contact_angle_min =
            (M_PI * 120 / 180) - ((M_PI / 2) / num_teeth);
Standard_Real roller_contact_angle_max =
            (M_PI * 140 / 180) - ((M_PI / 2) / num_teeth);
Standard_Real roller_contact_angle =
            (roller_contact_angle_min + roller_contact_angle_max) / 2.0;

Standard_Real tooth_radius_min = 0.505 * roller_diameter;
Standard_Real tooth_radius_max =
              tooth_radius_min + (0.069 * pow(roller_diameter, 1.0/3.0));
Standard_Real tooth_radius = (tooth_radius_min + tooth_radius_max) / 2.0;

Standard_Real profile_radius = 0.12 * roller_diameter * (num_teeth + 2);
Standard_Real top_diameter =
        pitch_circle_diameter + ((1-(1.6/num_teeth)) * pitch) - roller_diameter;
Standard_Real top_radius = top_diameter / 2.0;

Standard_Real thickness = chain_width * 0.95;

// Center hole data
Standard_Real center_radius = 125.0 / 2.0;

// Mounting hole data
Standard_Integer mounting_hole_count = 6;
Standard_Real mounting_radius = 153.0 / 2.0;
Standard_Real hole_radius = 8.5 / 2.0;

/*
 * Build a single tooth
 */

 Sprocket step 1  

TopoDS_Shape BuildTooth()          
{
    // Create a 2D arc to form the base of the tooth
    gp_Pnt2d base_center(pitch_circle_radius + (tooth_radius - roller_radius),
                         0);
    gp_Circ2d base_circle = gp_Circ2d(gp_Ax2d(base_center, gp_Dir2d()),
                                      tooth_radius);
    Handle(Geom2d_TrimmedCurve) trimmed_base =
        GCE2d_MakeArcOfCircle(base_circle,
                              M_PI - (roller_contact_angle / 2.0),
                              M_PI);

    trimmed_base->Reverse();
    gp_Pnt2d p0 = trimmed_base->StartPoint();
    gp_Pnt2d p1 = trimmed_base->EndPoint();

    // Determine the center of the profile circle
    Standard_Real x_distance =
            cos(roller_contact_angle / 2) * (profile_radius + tooth_radius);
    Standard_Real y_distance =
            sin(roller_contact_angle / 2) * (profile_radius + tooth_radius);
    gp_Pnt2d profile_center(pitch_circle_radius - x_distance, y_distance);

    // Construct the profile circle
    gp_Circ2d profile_circle = gp_Circ2d(gp_Ax2d(profile_center, gp_Dir2d()),
                                         profile_center.Distance(p1));
    Handle(Geom2d_Circle) geom_profile_circle =
            GCE2d_MakeCircle(profile_circle);

    // Construct the outer circle
    gp_Circ2d outer_circle = gp_Circ2d(gp_Ax2d(gp_Pnt2d(0, 0), gp_Dir2d()),
                                       top_radius);
    Handle(Geom2d_Circle) geom_outer_circle = GCE2d_MakeCircle(outer_circle);

    // Calculate the intersection point(s) of the profile circle
    // and the outer circle.  If there are two points, pick the one closest
    // to the center of the profile circle
    Geom2dAPI_InterCurveCurve inter(geom_profile_circle, geom_outer_circle);
    Standard_Integer num_points = inter.NbPoints();
    gp_Pnt2d p2;
    if (num_points == 2)
    {
        if (p1.Distance(inter.Point(1)) < p1.Distance(inter.Point(2)))
            p2 = inter.Point(1);
        else
            p2 = inter.Point(2);
    }
    else if (num_points == 1)
    {
        p2 = inter.Point(1);
    }
    else
        throw;

    // Trim the profile circle and mirror
    Handle(Geom2d_TrimmedCurve) trimmed_profile =
            GCE2d_MakeArcOfCircle(profile_circle, p1, p2);

    // Calculate the outermost point
    gp_Pnt2d p3 = gp_Pnt2d(cos(tooth_angle / 2) * top_radius,
                           sin(tooth_angle / 2) * top_radius);

    // and use it to create the third arc
    Handle(Geom2d_TrimmedCurve) trimmed_outer =
            GCE2d_MakeArcOfCircle(outer_circle, p2, p3);

    // Mirror and reverse the three arcs
    gp_Ax2d mirror_axis(gp::Origin2d(), gp::DX2d().Rotated(tooth_angle / 2.0));

    Handle(Geom2d_TrimmedCurve) mirror_base =
            Handle(Geom2d_TrimmedCurve)::DownCast(trimmed_base->Copy());
    Handle(Geom2d_TrimmedCurve) mirror_profile =
            Handle(Geom2d_TrimmedCurve)::DownCast(trimmed_profile->Copy());
    Handle(Geom2d_TrimmedCurve) mirror_outer =
            Handle(Geom2d_TrimmedCurve)::DownCast(trimmed_outer->Copy());

    mirror_base->Mirror(mirror_axis);
    mirror_profile->Mirror(mirror_axis);
    mirror_outer->Mirror(mirror_axis);

    mirror_base->Reverse();
    mirror_profile->Reverse();
    mirror_outer->Reverse();

    // Replace the two outer arcs with a single one
    gp_Pnt2d outer_start = trimmed_outer->StartPoint();
    gp_Pnt2d outer_mid = trimmed_outer->EndPoint();
    gp_Pnt2d outer_end = mirror_outer->EndPoint();
    Handle(Geom2d_TrimmedCurve) outer_arc =
            GCE2d_MakeArcOfCircle(outer_start, outer_mid, outer_end);

    // Create an arc for the inside of the wedge
    gp_Circ2d inner_circle(gp_Ax2d(gp_Pnt2d(0, 0), gp_Dir2d()),
                           top_radius - roller_diameter);
    gp_Pnt2d inner_start(top_radius - roller_diameter, 0);
    Handle(Geom2d_TrimmedCurve) inner_arc =
            GCE2d_MakeArcOfCircle(inner_circle, inner_start , tooth_angle);
    inner_arc->Reverse();

    //Convert the 2D arcs and two extra lines to 3D edges
    gp_Pln plane = gp_Pln(gp::Origin(), gp::DZ());
    BRepBuilderAPI_MakeEdge arc1(GeomAPI::To3d(trimmed_base, plane));
    BRepBuilderAPI_MakeEdge arc2(GeomAPI::To3d(trimmed_profile, plane));
    BRepBuilderAPI_MakeEdge arc3(GeomAPI::To3d(outer_arc, plane));
    BRepBuilderAPI_MakeEdge arc4(GeomAPI::To3d(mirror_profile, plane));
    BRepBuilderAPI_MakeEdge arc5(GeomAPI::To3d(mirror_base, plane));
    gp_Pnt2d p4 = mirror_base->EndPoint();
    gp_Pnt2d p5 = inner_arc->StartPoint();
    BRepBuilderAPI_MakeEdge lin1(gp_Pnt(p4.X(), p4.Y(), 0),
                                 gp_Pnt(p5.X(), p5.Y(), 0));
    BRepBuilderAPI_MakeEdge arc6(GeomAPI::To3d(inner_arc, plane));
    gp_Pnt2d p6 = inner_arc->EndPoint();
    BRepBuilderAPI_MakeEdge lin2(gp_Pnt(p6.X(), p6.Y(), 0),
                                 gp_Pnt(p0.X(), p0.Y(), 0));

    // Combine the edges in a wire
    BRepBuilderAPI_MakeWire wire(arc1);
    wire.Add(arc2);
    wire.Add(arc3);
    wire.Add(arc4);
    wire.Add(arc5);
    wire.Add(lin1);
    wire.Add(arc6);
    wire.Add(lin2);

    // Convert the wire into a face
    BRepBuilderAPI_MakeFace face(wire.Wire());

    // Finally, extrude the face
    BRepPrimAPI_MakePrism wedge(face.Shape(), gp_Vec(0.0, 0.0, thickness));
    return wedge.Shape();
}

/*
 *  Round off the edge of the single tooth
 */

 Sprocket step 2 

TopoDS_Shape RoundTooth(TopoDS_Shape wedge)
{
    Standard_Real round_x = 2.6;
    Standard_Real round_z = 0.06 * pitch;
    Standard_Real round_radius = pitch;

    // Determine where the circle used for rounding has to start and stop
    gp_Pnt2d p2d_1(top_radius - round_x, 0);
    gp_Pnt2d p2d_2(top_radius, round_z);

    // Construct the rounding circle
    GccAna_Circ2d2TanRad round_circle(p2d_1, p2d_2, round_radius, 0.01);
    if (round_circle.NbSolutions() != 2)
        throw;

    gp_Circ2d round_circle_2d_1 = round_circle.ThisSolution(1);
    gp_Circ2d round_circle_2d_2 = round_circle.ThisSolution(2);
    gp_Circ2d round_circle_2d;

    if (round_circle_2d_1.Position().Location().Coord().Y() >= 0)
        round_circle_2d = round_circle_2d_1;
    else
        round_circle_2d = round_circle_2d_2;

    // Remove the arc used for rounding
    Handle(Geom2d_TrimmedCurve) trimmed_circle =
            GCE2d_MakeArcOfCircle(round_circle_2d, p2d_1, p2d_2);

    // Calculate extra points used to construct lines
    gp_Pnt p1(p2d_1.X(), 0, p2d_1.Y());
    gp_Pnt p2(p2d_2.X(), 0, p2d_2.Y());
    gp_Pnt p3(p2d_2.X() + 1, 0, p2d_2.Y());
    gp_Pnt p4(p2d_2.X() + 1, 0, p2d_1.Y() - 1);
    gp_Pnt p5(p2d_1.X(), 0, p2d_1.Y() - 1);

    //Convert the arc and four extra lines into 3D edges
    gp_Pln plane = gp_Pln(gp_Ax3(gp::Origin(), gp::DY().Reversed(), gp::DX()));
    BRepBuilderAPI_MakeEdge arc1(GeomAPI::To3d(trimmed_circle, plane));
    BRepBuilderAPI_MakeEdge lin1(p2, p3);
    BRepBuilderAPI_MakeEdge lin2(p3, p4);
    BRepBuilderAPI_MakeEdge lin3(p4, p5);
    BRepBuilderAPI_MakeEdge lin4(p5, p1);

    // Make a wire composed of the edges
    BRepBuilderAPI_MakeWire round_wire(arc1);
    round_wire.Add(lin1);
    round_wire.Add(lin2);
    round_wire.Add(lin3);
    round_wire.Add(lin4);

    // Turn the wire into a face
    BRepBuilderAPI_MakeFace round_face(round_wire);

    // Revolve the face around the Z axis over the tooth angle
    TopoDS_Shape rounding_cut_1 =
            BRepPrimAPI_MakeRevol(round_face, gp::OZ(), tooth_angle).Shape();

    // Construct a mirrored copy of the first cutting shape
    gp_Trsf mirror;
    mirror.SetMirror(gp::XOY());
    TopoDS_Shape mirrored_cut_1 =
            BRepBuilderAPI_Transform(rounding_cut_1, mirror, true).Shape();

    // and translate it so that it ends up on the other side of the wedge
    gp_Trsf translate;
    translate.SetTranslation(gp_Vec(0, 0, thickness));
    TopoDS_Shape rounding_cut_2 =
            BRepBuilderAPI_Transform(mirrored_cut_1, translate, false).Shape();

    // Cut the wedge using the first and second cutting shape
    BRepAlgoAPI_Cut cut_1(wedge, rounding_cut_1);
    BRepAlgoAPI_Cut cut_2(cut_1, rounding_cut_2);

    // Return the result
    return cut_2.Shape();
}

/*
 * Copy the single tooth to form a complete sprocket
 * This is done in two stages to speed up the fusing
 * Fill the center of the sprocket with a disc
 */

 Sprocket step 3 

TopoDS_Shape CloneTooth(TopoDS_Shape base_shape)
{
    gp_Trsf clone;
    //TopoDS_Shape aggregated_shape = base_shape;
    TopoDS_Shape grouped_shape = base_shape;

    // Find a divisor, between 1 and 8, for the number_of teeth
    int multiplier = 1;
    int max_multiplier = 1;

    for (int current_multiplier = 1;
         current_multiplier <= 8;
         current_multiplier++)
    {
        if ((num_teeth % multiplier) == 0)
            max_multiplier = current_multiplier;
    }

    multiplier = max_multiplier;

    for (int i = 1; i < multiplier; i++)
    {
        clone.SetRotation(gp::OZ(), - i * tooth_angle);
        TopoDS_Shape rotated_shape =
                BRepBuilderAPI_Transform(base_shape, clone, true).Shape();
        grouped_shape = BRepAlgoAPI_Fuse(grouped_shape, rotated_shape).Shape();
        cout << i << endl;
    }

    TopoDS_Shape aggregated_shape = grouped_shape;

    // Rotate the basic tooth and fuse together
    for (int i = 1; i < num_teeth / multiplier; i++)
    {
        clone.SetRotation(gp::OZ(), - i * multiplier * tooth_angle);
        TopoDS_Shape rotated_shape =
                BRepBuilderAPI_Transform(grouped_shape, clone, true).Shape();
        aggregated_shape =
                BRepAlgoAPI_Fuse(aggregated_shape, rotated_shape).Shape();
        cout << i << endl;
    }

    // Fuse a disc to fill in the center
    BRepPrimAPI_MakeCylinder cylinder(gp::XOY(),
                                      top_radius - roller_diameter,
                                      thickness);
    aggregated_shape = BRepAlgoAPI_Fuse(aggregated_shape,
                                        cylinder.Shape()).Shape();

    return aggregated_shape;
}

/*
 * Create a hole in the center of the sprocket
 */

 Sprocket step 4 

TopoDS_Shape CenterHole(TopoDS_Shape base)
{
    BRepPrimAPI_MakeCylinder cylinder(center_radius, thickness);
    return BRepAlgoAPI_Cut(base, cylinder.Shape()).Shape();
}

/*
 * Provide chamfered mounting holes
 */

 Sprocket step 5 

TopoDS_Shape MountingHoles(TopoDS_Shape base)
{
    TopoDS_Shape result = base;

    for (int i = 0; i < mounting_hole_count; i++)
    {
        gp_Pnt center (cos(i * M_PI/3) * mounting_radius,
                       sin(i * M_PI/3) * mounting_radius,
                       0.0);
        gp_Ax2 center_axis (center, gp::DZ());

        BRepPrimAPI_MakeCylinder cylinder(center_axis, hole_radius, thickness);
        result = BRepAlgoAPI_Cut(result, cylinder.Shape()).Shape();

        BRepPrimAPI_MakeCone cone(center_axis,
                                  hole_radius + thickness / 2.0,
                                  hole_radius, thickness / 2.0);
        result = BRepAlgoAPI_Cut(result, cone.Shape()).Shape();
    }

    return result;
}

/*
 * Create cutouts to reduce the weight of the sprocket
 */

 Sprocket step 6 

TopoDS_Shape Cutout(TopoDS_Shape base)
{
    gp_Circ2d outer(gp::OX2d(), top_radius - 1.75 * roller_diameter);
    gp_Circ2d inner(gp::OX2d(), center_radius + 0.75 * roller_diameter);
    Handle(Geom2d_Circle) geom_outer = GCE2d_MakeCircle(outer);
    Handle(Geom2d_Circle) geom_inner = GCE2d_MakeCircle(inner);
    geom_inner->Reverse();

    Standard_Real base_angle = (2.0 * M_PI) / mounting_hole_count;
    Standard_Real hole_angle = atan(hole_radius / mounting_radius);
    Standard_Real correction_angle = 3 * hole_angle;

    gp_Lin2d left(gp::Origin2d(), gp::DX2d());
    gp_Lin2d right(gp::Origin2d(), gp::DX2d());
    left.Rotate(gp::Origin2d(), correction_angle);
    right.Rotate(gp::Origin2d(), base_angle - correction_angle);

    Handle(Geom2d_Line) geom_left = GCE2d_MakeLine(left);
    Handle(Geom2d_Line) geom_right = GCE2d_MakeLine(right);

    Geom2dAPI_InterCurveCurve inter_1(geom_outer, geom_left);
    Geom2dAPI_InterCurveCurve inter_2(geom_outer, geom_right);
    Geom2dAPI_InterCurveCurve inter_3(geom_inner, geom_right);
    Geom2dAPI_InterCurveCurve inter_4(geom_inner, geom_left);

    gp_Pnt2d p1, p2, p3, p4;

    (inter_1.Point(1).X() > 0) ? p1 = inter_1.Point(1) : p1 = inter_1.Point(2);
    (inter_2.Point(1).X() > 0) ? p2 = inter_2.Point(1) : p2 = inter_2.Point(2);
    (inter_3.Point(1).X() > 0) ? p3 = inter_3.Point(1) : p3 = inter_3.Point(2);
    (inter_4.Point(1).X() > 0) ? p4 = inter_4.Point(1) : p4 = inter_4.Point(2);

    // Trim the profile circle and mirror
    Handle(Geom2d_TrimmedCurve) trimmed_outer =
           GCE2d_MakeArcOfCircle(outer, p1, p2);
    Handle(Geom2d_TrimmedCurve) trimmed_inner =
           GCE2d_MakeArcOfCircle(inner, p4, p3);

    gp_Pln plane = gp_Pln(gp::Origin(), gp::DZ());
    BRepBuilderAPI_MakeEdge arc1(GeomAPI::To3d(trimmed_outer, plane));
    BRepBuilderAPI_MakeEdge lin1(gp_Pnt(p2.X(), p2.Y(),0),
                                 gp_Pnt(p3.X(), p3.Y(), 0));
    BRepBuilderAPI_MakeEdge arc2(GeomAPI::To3d(trimmed_inner, plane));
    BRepBuilderAPI_MakeEdge lin2(gp_Pnt(p4.X(), p4.Y(),0),
                                 gp_Pnt(p1.X(), p1.Y(), 0));

    BRepBuilderAPI_MakeWire cutout_wire(arc1);
    cutout_wire.Add(lin1);
    cutout_wire.Add(arc2);
    cutout_wire.Add(lin2);

    // Turn the wire into a face
    BRepBuilderAPI_MakeFace cutout_face(cutout_wire);
    BRepFilletAPI_MakeFillet2d filleted_face(cutout_face.Face());

    BRepTools_WireExplorer explorer(cutout_wire);
    while (explorer.More())
    {
        TopoDS_Vertex vertex = TopoDS::Vertex(explorer.CurrentVertex());
        filleted_face.AddFillet(vertex, roller_radius);
        explorer.Next();
    }

    BRepPrimAPI_MakePrism cutout(filleted_face.Shape(),
                                 gp_Vec(0.0, 0.0, thickness));

    TopoDS_Shape result = base;
    gp_Trsf rotate;
    for (int cut = 0; cut < mounting_hole_count; cut++)
    {
        rotate.SetRotation(gp::OZ(), cut * 2.0 * M_PI / mounting_hole_count);
        TopoDS_Shape rotated_cutout =
                BRepBuilderAPI_Transform(cutout.Shape(), rotate, true).Shape();
        result = BRepAlgoAPI_Cut(result, rotated_cutout).Shape();
    }

    return result;
}

int main()
{

     // Build a wedge containing one tooth
     TopoDS_Shape wedge = BuildTooth();

     // Round off the tooth
     TopoDS_Shape rounded_wedge = RoundTooth(wedge);

     // Copy and fuse the teeth to form a basic sprocket
     TopoDS_Shape basic_disc = CloneTooth(rounded_wedge);

     // Cut out a hole in the center
     TopoDS_Shape cut_disc = CenterHole(basic_disc);

     // Create mounting holes
     TopoDS_Shape mountable_disc = MountingHoles(cut_disc);

     // Remove material to reduce weight
     TopoDS_Shape finished_sprocket = Cutout(mountable_disc);

     // Write the result to a brep file
     BRepTools::Write(finished_sprocket, "sprocket.brep");

     cout << "Done" << endl;

     return 0;
}

(Download source)

Tools used:

• g++ version 5.4.0

• OpenCASCADE Community Edition 0.17.1-1

• Code::Blocks 13.12

• FreeCAD 0.15

• Blender 2.80


Tools used to generate the 2D drawings:

• LibreCAD 2.0.9-2

• Inkscape 0.91