Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <!DOCTYPE html>
<html lang="en">
    <head>
        <title>"Ritzel Designer</title>
        <meta charset="utf-8">
            
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <noscript>********* Benötigt Javascript *********</noscript>
        <link type="text/css" rel="stylesheet" href="main.css">
    </head>
    <body>
        

        <script type="importmap">
          {
            "imports": {
              "three": "https://unpkg.com/three@0.172.0/build/three.module.js",
              "three/addons/": "https://unpkg.com/three@0.172.0/examples/jsm/"
            }
          }
        </script>

        <script type="module" src="./Main_v4.js"></script>

    </body>
</html>

              
            
!

CSS

              
                
              
            
!

JS

              
                /*
 Version4 includes a matching chain link plate that fits the selected chain.
 SVG code for sprocket and plate is logged to console.

 Version3 switches over to the use of a THREE.Shape, ready for extrusion (and possibly
 additional Shapes like holes etc.).
 
 Version2 re-orders all arcs to appear in an ordered fashion, like one would draw the contour
 in a contiguous, uninterrupted fashion with a pen, still as a THREE.Curve.
 
 While version1 only held various arcs in a cluttered sequence (base arcs first, then left / right arcs,
 then top arcs), this yielded only a look-alike result, not suited for further use/processing like
 extrusion etc.
 */

import * as THREE           from "three";
import { GUI }              from 'three/addons/libs/lil-gui.module.min.js';
import { OrbitControls }    from "three/addons/controls/OrbitControls.js";
import { LineGeometry }     from 'three/addons/lines/LineGeometry.js';

console.clear();

let scene = new THREE.Scene();
let sprocket_Group = new THREE.Group(); sprocket_Group.name = 'Sprocket';
let decoration_Group = new THREE.Group(); decoration_Group.name = 'decoration';     // polygon, kopfkreis, roller

scene.add( sprocket_Group );
scene.add( decoration_Group );

let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, .1, 1000);
camera.position.set(0, 0, -60);
let renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.shadowMap.enabled = true;
renderer.setSize(innerWidth, innerHeight);
renderer.setClearColor(0xffffff, 0);
document.body.appendChild(renderer.domElement);

window.addEventListener( 'resize', onWindowResize, false );

let controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.minDistance = .1;
controls.maxDistance = 1000;
controls.addEventListener( 'change', render );

let light = new THREE.DirectionalLight(0xffffff, .5);
const amb = new THREE.AmbientLight( 0x404040 ); // soft white light
scene.add( amb );
light.position.setScalar(1);
light.castShadow = true;
scene.add(light, new THREE.AmbientLight(0xffffff, 0.5));

let points = [];
let curChainIndex = 2;
let decoShow = true;

// Data for Pitch, Rollerdia, Innerwidth taken from: https://www.iwis.com/as-handbook/iwis-handbuch-kettentechnik-konstruktionsunterlagen-berechnungsbeispiele.pdf
// Data for Studdia, Platethickness taken from: https://www.maedler.de/product/1643/1615/640/einfach-rollenketten-standard
// Data for Plateheight taken from: https://www.kettentechnik.de/de/2011/08/16/einfach-rollenketten-din-8187/
var Chains  = [
    {Name: "3/8 x 5/32“", Pitch:  9.525,    Rollerdia:  6.35,   Innerwidth:  5.72,  Studdia:  3.28, Plateheight:  8.2, Platethickness: 1.405},
    {Name: "1/2 x 5/16“", Pitch: 12.700,    Rollerdia:  8.51,   Innerwidth:  7.75,  Studdia:  4.09, Plateheight: 10.3, Platethickness: 1.775},
    {Name: "5/8 x 3/8“",  Pitch: 15.875,    Rollerdia: 10.16,   Innerwidth:  9.65,  Studdia:  5.08, Plateheight: 14.7, Platethickness: 1.815},
    {Name: "3/4 x 7/16“", Pitch: 19.050,    Rollerdia: 12.07,   Innerwidth: 11.75,  Studdia:  5.72, Plateheight: 16.1, Platethickness: 1.970},
    {Name: "1“ x 17 mm",  Pitch: 25.400,    Rollerdia: 15.88,   Innerwidth: 17.02,  Studdia:  8.28, Plateheight: 21.0, Platethickness: 4.210},
    {Name: "1 1/4 x 3/4“",Pitch: 31.750,    Rollerdia: 19.0,    Innerwidth: 19.56,  Studdia: 10.19, Plateheight: 26.4, Platethickness: 4.725},
            ];


const gui = new GUI();
const params = {
    Chain: "5/8 x 3/8“",
    Pitch: Chains[curChainIndex].Pitch,
    Rollerdia: Chains[curChainIndex].Rollerdia,
    Innerwidth: Chains[curChainIndex].Innerwidth,
    NumTeeth: 5,
    Decoration: decoShow,
}

const folder = gui.addFolder( 'Sprocket' );
folder.add( params, 'Chain', {  "3/8 x 5/32“": 0,
                                "1/2 x 5/16“": 1,
                                "5/8 x 3/8“": 2,
                                "3/4 x 7/16“": 3,
                                "1“ x 17 mm": 4,
                                "1 1/4 x 3/4“": 5 } ).onChange( proxy );
folder.add( params, 'Pitch', Chains[curChainIndex].Pitch ).listen().disable().updateDisplay();
folder.add( params, 'Rollerdia', Chains[curChainIndex].Rollerdia ).listen().disable().updateDisplay();
folder.add( params, 'Innerwidth', Chains[curChainIndex].Innerwidth ).listen().disable().updateDisplay();
folder.add( params, 'Decoration', decoShow).onChange( proxy2 );
folder.add( params, 'NumTeeth', 5, 43, 1).onChange( createSprocket );
folder.open();

function proxy( val ) {
    curChainIndex = val;
    params.Pitch = Chains[curChainIndex].Pitch;
    params.Rollerdia = Chains[curChainIndex].Rollerdia;
    params.Innerwidth = Chains[curChainIndex].Innerwidth;
    createSprocket();
}

function proxy2( val ) {
    decoShow = val;
    createSprocket();
}

function createSprocket( ) {

    const N = parseInt( params.NumTeeth );              // Anzahl Zähne
    const dAng = 2 * Math.PI / N;                       // Winkel zwischen benachbarten Zähnen
    const P = Chains[ curChainIndex ].Pitch;            // Teilung / pitch
    const R = (P / 2) / Math.sin( dAng / 2 );           // Teilkreisradius
    const rr = Chains[ curChainIndex ].Rollerdia / 2;   // Rollenradius
    let cx, cy;                                         // center coordinates for ellipse curve (arc)
    let sAng, eAng;                                     // start / end angle of arc

    let geometry, mesh, m;
    let pts, curve, ellipse;

    // ********************* pitch polygon ***************************
                                  
    // remove previous meshes (Polygon, Kopfkreis, Rollers)
    while ( decoration_Group.children.length != 0 ) decoration_Group.children[0].removeFromParent();

    let vec1 = new THREE.Vector2().set( 0, R );         // start with 1st vertex @ 12:00 position
    let vec2 = new THREE.Vector2().set( 0, 0 );
    let center = new THREE.Vector2().set( 0, 0 );

    points.length = 0;
    points.push( vec1 );

    for ( let i = 0; i < N; i++ ) {
        vec2 = vec1.rotateAround( center, dAng );
        points.push( vec2.clone( vec2 ) );
        vec1.copy( vec2 );
    }
               
    m = new THREE.LineBasicMaterial( { color: 0x808080 } );
    geometry = new THREE.BufferGeometry().setFromPoints( points );
    mesh = new THREE.LineLoop( geometry, m); mesh.name = 'Polygon';

    decoration_Group.add( mesh );
               
    // ********************* rollers ***************************
                                             
    for ( let i = 0; i < N; i++ ) {
        const geometry = new THREE.CircleGeometry( rr, 32 );
        const material = new THREE.LineBasicMaterial( { transparent: true, opacity: 0.5, color: 0xffE0E0, side: THREE.DoubleSide } );
        const circle = new THREE.Mesh( geometry, material );
        circle.position.set( points[i].x, points[i].y, 0.0 );
        decoration_Group.add( circle );
    }
               
               
               

    const rk = ( 2 * R + ( 1.0 - ( 1.6 / N ) ) * P - rr ) / 2.0;    // min. Kopfkreisradius (aus Literatur)

    // ******************** flank arc start/end angles ***********

    let Rf = P - rr;                    // flank radius
    let Sx = 0, Sy = 0;
    let dx = 0, dy = 0;
    let ang1, ang2, delta;
    let e1 = 0, e2 = 0;

    // the outer ends of the left/right flank arcs are clipped at their intersection with 'Kopfkreis'
    let i = 0;
    let result = Intersect2Circles( [points[i].x,  points[i].y], Rf, [0, 0], rk );

    // compute angle of 1st intersection point wrt. current arc center
    Sx = result[0][0];
    Sy = result[0][1];
    ang1 = ( Math.atan2( ( Sy - points[i].y ) , ( Sx - points[i].x ) ) );

    // compute angle of 2nd intersection point wrt. current arc center
    Sx = result[1][0];
    Sy = result[1][1];
    ang2 = ( Math.atan2( ( Sy - points[i].y ) , ( Sx - points[i].x ) ) );

    // compute both angular "distances" from arc start angle sAng, then use smaller one
    sAng = -dAng / 2 + i * dAng;
    e1 = Math.abs( ang1 - sAng );
    e2 = Math.abs( ang2 - sAng );
    delta = e1 < e2 ? ang1 - sAng : ang2 - sAng ;

    // ******************** top arc start/end angles ***********

    let ex = 0, ey = 0;                             // flank arc endpoint = top arc start point
    let tAng = 0, dtAng = 0;

    eAng = +dAng / 2 + Math.PI - delta;

    ex = Rf * Math.cos( eAng ) + points[ 0 ].x;
    ey = Rf * Math.sin( eAng ) + points[ 0 ].y;
    tAng = Math.PI + Math.atan( ey / ex );          // start angle for top arc of tooth#1
    dtAng = tAng - (Math.PI / 2 + dAng / 2);        // delta ang (±) for symmetry axis of tooth

    // ********************* Kopfkreis ***************************

    curve = new THREE.EllipseCurve(
                   0.0,  0.0,                       // ax, aY
                   rk, rk,                          // xRadius, yRadius
                   0.0,                             // aStartAngle,
                   2 * Math.PI,                     // aEndAngle
                   true,                            // aClockwise
                   0                                // aRotation
                   );

    pts = curve.getPoints( 200 );                               // full circle, as opposed to "edgy" pitch polygon
    geometry = new THREE.BufferGeometry().setFromPoints( pts );
    const m1 = new THREE.LineBasicMaterial( { color: 0xE0E0E0 } );

    ellipse = new THREE.Line( geometry, m1 ); ellipse.name = "Kopfkreis";
    decoration_Group.add( ellipse );
                                  
// *********************** end of decoration ******************************************
// ************************************************************************************

    // first (base) arc's start point
    let sx = rr * Math.cos( -dAng / 2 ) + points[i].x;
    let sy = rr * Math.sin( -dAng / 2 ) + points[i].y;

    console.log( "<svg viewBox=\"-100 -100 300 300\" xmlns=\"http://www.w3.org/2000/svg\">" );
    console.log("<path");
    console.log( "d=\"M", sx,sy );
                                  
    const sprocket = new THREE.Shape();

    // remove previous geometry
    while ( sprocket_Group.children.length != 0 ) sprocket_Group.children[0].removeFromParent();

    for ( let i = 0; i < N; i++ ) {
        
        // draw four arcs per tooth in sequence and ccw order

        // ********************* baseArc ***************************

        sAng = -dAng / 2 + i * dAng;
        eAng = Math.PI + dAng / 2 + i * dAng;
        sprocket.absarc( points[i].x,  points[i].y, rr, sAng, eAng, true );

        ex = rr * Math.cos( eAng ) + points[i].x;
        ey = rr * Math.sin( eAng ) + points[i].y;
        console.log( "A ", rr, rr, 0, 0, 0, ex, ey );
        
        // ********************* right Arc ***************************
        
        let index = i == (N - 1) ? 0 : i + 1;             // wrap-around!
        
        sAng = -dAng / 2 + index * dAng;
        eAng = sAng + delta;
        sprocket.absarc( points[index].x,  points[index].y, Rf, sAng, eAng, false );

        // this arc's end point
        ex = Rf * Math.cos( eAng ) + points[index].x;
        ey = Rf * Math.sin( eAng ) + points[index].y;
        console.log( "A ", Rf, Rf, 0, 0, 1, ex, ey );

        // ********************* top Arc *****************************
        
        sAng = tAng + i * dAng - 2 * dtAng;
        eAng = sAng + 2 * dtAng;
        sprocket.absarc( 0.0,  0.0, rk, sAng, eAng, false );
        
        // this arc's end point
        ex = rk * Math.cos( eAng );
        ey = rk * Math.sin( eAng );
        console.log( "A ", rk, rk, 0, 0, 1, ex, ey );

        // ********************* left Arc *****************************
        
        sAng = +dAng / 2 + Math.PI + i * dAng - delta;
        eAng = sAng + delta;
        sprocket.absarc( points[i].x,  points[i].y, Rf, sAng, eAng, false );

        // this arc's end point
        ex = Rf * Math.cos( eAng ) + points[i].x;
        ey = Rf * Math.sin( eAng ) + points[i].y;
        console.log( "A ", Rf, Rf, 0, 0, 1, ex, ey );

    }

    console.log( "z" );
//    console.log( "</svg>" );      // SVG logging stops here for sprocket contour only
//    console.log( "\" /></svg>" );

    let innerwidth = Chains[curChainIndex].Innerwidth;
    let extrudeSettings = {
        depth: innerwidth,
        bevelEnabled: false,
        bevelSegments: 0,
        steps: 1,
        bevelSize: 0,
        bevelThickness: 0
    };
    geometry = new THREE.ExtrudeGeometry( sprocket, extrudeSettings );

    mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial( { color: 0xffffff } ) );
    sprocket_Group.add( mesh );
                                

    // ************************ chain link plate *********************************

    const plate = new THREE.Shape();

    let b = Chains[curChainIndex].Plateheight;    // max. height of plate suitable for selected chain
    let h = Chains[curChainIndex].Pitch;          // this value is inversely correlated to the degree of constriction of the plate
    let ra = b/2;                                 // big radius around holes
    let offset = R + 5 * rr;                      // how far to the right of sprocket the plate is shown

    let beta = Math.atan( h / ( P / 2 ) );
    let ri = h / Math.sin( beta ) - ra;           // constriction radius

    ex = ra * Math.cos( beta ) -P/2 + offset;
    ey = ra * Math.sin( beta );
    console.log( "M", ex, ey );                                             // moveTo start point of left arc

    sAng = beta;
    eAng = -beta;
    plate.absarc( -P/2 + offset,  0.0, ra, sAng, eAng, false );             // left arc
                          
    // left arc's end point
    ex = ra * Math.cos( eAng ) -P/2 + offset;
    ey = ra * Math.sin( eAng );
    console.log( "A ", ra, ra, 0, 1, 1, ex, ey );                           // sweep = 1: take the long way

    sAng = Math.PI - beta;
    eAng = + beta;
    plate.absarc( 0.0 + offset, -h, ri, sAng, eAng, true );                 // bottom arc

    // bottom arc's end point
    ex = ri * Math.cos( eAng ) + offset;
    ey = ri * Math.sin( eAng ) - h;
    console.log( "A ", ri, ri, 0, 0, 0, ex, ey );

    sAng = Math.PI + beta;
    eAng = Math.PI - beta;
    plate.absarc( P/2 + offset,  0.0, ra, sAng, eAng, false );              // right arc

    // right arc's end point
    ex = ra * Math.cos( eAng ) + P/2 + offset;
    ey = ra * Math.sin( eAng );
    console.log( "A ", ra, ra, 0, 1, 1, ex, ey );                           // sweep = 1: take the long way

    sAng = - beta;
    eAng = Math.PI + beta;
    plate.absarc( 0.0 + offset,  h, ri, sAng, eAng, true );                 // top arc

    // top arc's end point
    ex = ri * Math.cos( eAng ) + offset;
    ey = ri * Math.sin( eAng ) + h;
    console.log( "A ", ri, ri, 0, 0, 0, ex, ey );
//    console.log( "z" );

    let sr = Chains[curChainIndex].Studdia / 2;                             // stud radius
    const leftHole = new THREE.Shape();
    leftHole.absarc( -P / 2 + offset,  0, sr, 0.0, 2 * Math.PI, true );     // left stud bore
                          
    // insert left stud hole as TWO HALF-arcs
    sx = + sr - P / 2 + offset;
    sy = 0.0;
    ex = - sr - P / 2 + offset;
    ey = 0.0;
    console.log( "M", sx, sy );
    console.log( "A ", sr, sr, 0, 0, 0, ex, ey );
    console.log( "A ", sr, sr, 0, 0, 0, sx, sy );

    const rightHole = new THREE.Shape();
    rightHole.absarc(  P / 2 + offset,  0, sr, 0.0, 2 * Math.PI, true );    // right stud bore

    // insert right stud hole as TWO HALF-arcs
    sx = + sr + P / 2 + offset;
    sy = 0.0;
    ex = - sr + P / 2 + offset;
    ey = 0.0;
    console.log( "M", sx, sy );
    console.log( "A ", sr, sr, 0, 0, 0, ex, ey );
    console.log( "A ", sr, sr, 0, 0, 0, sx, sy );
    console.log( "z" );

    plate.holes.push( leftHole );
    plate.holes.push( rightHole );

    console.log( "\" style=\"fill:white;stroke:black;stroke-width:0.1\" /> </svg>" );


    let thickness = Chains[curChainIndex].Platethickness;
    extrudeSettings = {
      depth: thickness,
      bevelEnabled: false,
      bevelSegments: 0,
      steps: 1,
      bevelSize: 0.0,
      bevelThickness: 0.0
    };
    geometry = new THREE.ExtrudeGeometry( plate, extrudeSettings );
    const m2 = new THREE.LineBasicMaterial( { color: 0x0000ff } );
    mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial( { color: 0xffffff } ) );

    sprocket_Group.add( mesh );


    decoration_Group.visible = decoShow;

    render();

}


createSprocket();

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    controls.update();
    renderer.setSize( window.innerWidth, window.innerHeight );
    render();
}


function Intersect2Circles( A, a, B, b ) {
//
// taken from:
//
// http://walter.bislins.ch/blog/index.asp?page=Schnittpunkte+zweier+Kreise+berechnen+%28JavaScript%29
//
// A, B = [ x, y ]
// a = radius of circle about center A
// b = radius of circle about center B
// return = [ Q1, Q2 ] or [ Q ] or [] where Q = [ x, y ]
    var AB0 = B[0] - A[0];
    var AB1 = B[1] - A[1];
    var c = Math.sqrt( AB0 * AB0 + AB1 * AB1 );
    if (c == 0) {
       // no distance between centers
       return [];
    }
    var x = (a*a + c*c - b*b) / (2*c);
    var y = a*a - x*x;
    if (y < 0) {
       // no intersection
       return [];
    }
    if (y > 0) y = Math.sqrt( y );
    // compute unit vectors ex and ey
    var ex0 = AB0 / c;
    var ex1 = AB1 / c;
    var ey0 = -ex1;
    var ey1 =  ex0;
    var Q1x = A[0] + x * ex0;
    var Q1y = A[1] + x * ex1;
    if (y == 0) {
       // one touch point
       return [ [ Q1x, Q1y ] ];
    }
    // two intersections
    var Q2x = Q1x - y * ey0;
    var Q2y = Q1y - y * ey1;
    Q1x += y * ey0;
    Q1y += y * ey1;
    return [ [ Q1x, Q1y ], [ Q2x, Q2y ] ];

}


function render() {
    renderer.render( scene, camera );
}

              
            
!
999px

Console