openscad / MCAD

OpenSCAD Parametric CAD Library (LGPL 2.1)

Home Page:http://reprap.org/wiki/MCAD

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Proposal to integrate OpenSCAD ISO metric thread library / functions into MCAD

mofosyne opened this issue · comments

commented

Hi,

I've just got permission from TrevM that it is public domain code (source: https://www.thingiverse.com/thing:311031/comments#comment-6423499)

I've been finding myself using this module quite often to implement simple ISO standard screw threading on various models.

I think this will be a good addition to include into MCAD, however I would like to know if there is any changes that should be done to his implementation to keep it to the same quality and interfacing standard for MCAD, so that it is of good enough quality for inclusion into the next OpenSCAD standard library.

If it make sense to do so, I can then clean it up and then do a pull request.


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

commented

Main source code for his library is posted in https://www.thingiverse.com/thing:311031/files as ISOThread.scad

MCAD already has a thread library in the dev branch (https://github.com/openscad/MCAD/blob/dev/fasteners/threads.scad) capable of generating ISO threads, but I guess the interface might be a bit more fiddly than the library you linked. I'm not sure the inclusion of yet another thread implementation is the best way to go, but if the extension of API interface in the MCAD dev branch is worth looking at, if it makes it easier to use.

commented

Oh, I wasn't aware of this threads.scad even after googling... Hence why I ended up discovering the above ISOThread.scad instead

I wonder if there is an official MCAD reference documentation and examples (e.g. something that could be included into OpenSCAD offline documentation etc...)

Not familiar with threads.scad and how easy it is to use, but will explore it in the future and see how they both compare.

commented

Oh i see it's a dev branch and not yet released... well at least we can still compare both and integrate the best of both bits.

I have a proposal 😄...

With developer time always quite limited, it might be difficult to get the dev branch released. People being used to get MCAD bundled with OpenSCAD is not making things easier due to the difficult OpenSCAD release cycle.

Maybe an option would be to call it MCAD2 (or so) while keeping all the work @hyperair already did for compatibility and allow for simpler, OpenSCAD independent releases. So ideally it's a drop-in replacement for existing MCAD stuff, but gets the newer work into the hands of designers to use. The current MCAD could go into maintenance mode.

We could add it to the library list and maybe even use it as test bed for some sort of library integration / managment which is a topic often discussed but not really going forward.

I'm looking at this mostly from the OpenSCAD side, so if we can find one or two people discussing from the library perspective, maybe we could get things moving eventually.

commented

I wonder if it's possible to ask OpenSCAD if they can do something similar to Deno... of allowing includes to be pointing to a website url instead. (Source: https://blog.logrocket.com/what-is-deno/)

import { assertEquals } from "https://deno.land/std/testing/asserts.ts";

That would decouple the OpenSCAD from having to sync MCAD. (That's not to say we shouldn't still include it in as a batteries included approach for offline capabilities, but this would at least make sharing libraries much easier).

(Of course the other method is a package manager if OpenSCAD has one).

I know deno and I've read the arguments. Maybe at some later stage, in the forseeable future, I don't think it's possible.

commented

Just tried to copy the MCAD dev libarary to C:\Program Files\OpenSCAD\libraries to at least check if https://github.com/openscad/MCAD/blob/dev/fasteners/nuts_and_bolts.scad is not already doing the same thing. Haven't had much luck even after copying over a dependency https://github.com/OskarLinde/scad-utils

Got compile issue with threads.scad

use <MCAD/fasteners/threads.scad>
test_threads();

image

Quick Review of nuts_and_bolts.scad

This looks like it was for the same purpose as ISOThread.scad since ISOThreads also does nuts and bolts, but surprisingly it's only for creating holes to screw in actual metal bolts into. Looks like it rendered the holes ok

Quick Check of iso4017.scad

Looks like it's the same as nuts_and_bolts.scad, and is for having a hole to screw into plastic.

image


Checking TrevM's implementation

Compiles without any issues. However until I can compile MCAD/fasteners/threads.scad, it's a little hard to gauge what's different or missing between the two.

image

Regardless, it looks like TrevM's implementation if baked into MCAD2 should be split into two files, one for thread.scad and nuts_and_bolts.scad to ensure separation of concerns.

So I think what you are saying makes sense at least.

Just to clarify, my proposal was coming from OpenSCAD perspective as we had some discussions on library handling. Decision if that makes sense and on how to proceed here would be by @hyperair as MCAD maintainer.

commented

Just in case the site ever goes down. Here is a copy of the public domain source code.

ISOThread.zip

While his source code itself doesn't specify license, the page does have a declaration that state:

OpenSCAD ISO metric thread library / functions (updated) by TrevM is licensed under the Creative Commons - Public Domain Dedication license.

Which I've obtained from the wayback machine http://web.archive.org/web/20171128001316/https://www.thingiverse.com/thing:311031

I also got a direct statement from him (which unfortunately can't be shown in the wayback machine) in which he responded to my question on if I can include it in MCAD with:

TrevM
April 07, 2022
It's Public Domain, anyone can have it for any use :-)

I am TrevM on Thingiverse and TrevM309 on GitHub, just confirming, fully Public Domain as advertised, feel very free to use in any way that you wish

Other OpenSCAD threads libs to also check/integrate if needed

ISOThread by TrevM309 (fully Public Domain)

As discussed above. https://github.com/openscad/MCAD/files/9477739/ISOThread.zip

scad-lib-FDMscrews by mechadense ( LGPL-3.0 license )

scad-lib-FDMscrews

threadlib by adrianschlatter ( BSD-3-Clause license )

https://github.com/adrianschlatter/threadlib

OpenSCAD threads.scad module by rcolyer ( CC0-1.0 license )

https://github.com/rcolyer/threads-scad

Easy Bolt by jbruce12000 (GNU - GPL)

Super easy customizer for creating bolts, nuts, washers, threaded rods, and standoffs.

  • This actually uses threadlib by adrianschlatter above... so we may want to focus just on that. But the Supported Threads list looks quite useful.

https://www.thingiverse.com/thing:6308320

Thread-drawing modules for OpenSCAD by Dan Kirshner (GNU General Public License or later)

https://dkprojects.net/openscad-threads/

Version 2.7. 2022-02-27 source just in case
/*
 * ISO-standard metric threads, following this specification:
 *          http://en.wikipedia.org/wiki/ISO_metric_screw_thread
 *
 * Copyright 2022 Dan Kirshner - dan_kirshner@yahoo.com
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * See <http://www.gnu.org/licenses/>.
 *
 * Version 2.7.  2022-02-27  Increase minimum thread segments.
 * Version 2.6.  2021-05-16  Contributed patches for leadin (thanks,
                             jeffery.spirko@tamucc.edu) and aligning thread
                             "facets" (triangulation) with base cylinder
                             (thanks, rambetter@protonmail.com).
 * Version 2.5.  2020-04-11  Leadin option works for internal threads.
 * Version 2.4.  2019-07-14  Add test option - do not render threads.
 * Version 2.3.  2017-08-31  Default for leadin: 0 (best for internal threads).
 * Version 2.2.  2017-01-01  Correction for angle; leadfac option.  (Thanks to
 *                           Andrew Allen <a2intl@gmail.com>.)
 * Version 2.1.  2016-12-04  Chamfer bottom end (low-z); leadin option.
 * Version 2.0.  2016-11-05  Backwards compatibility (earlier OpenSCAD) fixes.
 * Version 1.9.  2016-07-03  Option: tapered.
 * Version 1.8.  2016-01-08  Option: (non-standard) angle.
 * Version 1.7.  2015-11-28  Larger x-increment - for small-diameters.
 * Version 1.6.  2015-09-01  Options: square threads, rectangular threads.
 * Version 1.5.  2015-06-12  Options: thread_size, groove.
 * Version 1.4.  2014-10-17  Use "faces" instead of "triangles" for polyhedron
 * Version 1.3.  2013-12-01  Correct loop over turns -- don't have early cut-off
 * Version 1.2.  2012-09-09  Use discrete polyhedra rather than linear_extrude ()
 * Version 1.1.  2012-09-07  Corrected to right-hand threads!
 */

// Examples.
//
// Standard M8 x 1.
// metric_thread (diameter=8, pitch=1, length=4);

// Square thread.
// metric_thread (diameter=8, pitch=1, length=4, square=true);

// Non-standard: long pitch, same thread size.
//metric_thread (diameter=8, pitch=4, length=4, thread_size=1, groove=true);

// Non-standard: 20 mm diameter, long pitch, square "trough" width 3 mm,
// depth 1 mm.
//metric_thread (diameter=20, pitch=8, length=16, square=true, thread_size=6,
//               groove=true, rectangle=0.333);

// English: 1/4 x 20.
//english_thread (diameter=1/4, threads_per_inch=20, length=1);

// Tapered.  Example -- pipe size 3/4" -- per:
// http://www.engineeringtoolbox.com/npt-national-pipe-taper-threads-d_750.html
// english_thread (diameter=1.05, threads_per_inch=14, length=3/4, taper=1/16);

// Thread for mounting on Rohloff hub.
//difference () {
//   cylinder (r=20, h=10, $fn=100);
//
//   metric_thread (diameter=34, pitch=1, length=10, internal=true, n_starts=6);
//}


// ----------------------------------------------------------------------------
function segments (diameter) = min (150, max (ceil (diameter*6), 25));


// ----------------------------------------------------------------------------
// diameter -    outside diameter of threads in mm. Default: 8.
// pitch    -    thread axial "travel" per turn in mm.  Default: 1.
// length   -    overall axial length of thread in mm.  Default: 1.
// internal -    true = clearances for internal thread (e.g., a nut).
//               false = clearances for external thread (e.g., a bolt).
//               (Internal threads should be "cut out" from a solid using
//               difference ()).  Default: false.
// n_starts -    Number of thread starts (e.g., DNA, a "double helix," has
//               n_starts=2).  See wikipedia Screw_thread.  Default: 1.
// thread_size - (non-standard) axial width of a single thread "V" - independent
//               of pitch.  Default: same as pitch.
// groove      - (non-standard) true = subtract inverted "V" from cylinder
//                (rather thanadd protruding "V" to cylinder).  Default: false.
// square      - true = square threads (per
//               https://en.wikipedia.org/wiki/Square_thread_form).  Default:
//               false.
// rectangle   - (non-standard) "Rectangular" thread - ratio depth/(axial) width
//               Default: 0 (standard "v" thread).
// angle       - (non-standard) angle (deg) of thread side from perpendicular to
//               axis (default = standard = 30 degrees).
// taper       - diameter change per length (National Pipe Thread/ANSI B1.20.1
//               is 1" diameter per 16" length). Taper decreases from 'diameter'
//               as z increases.  Default: 0 (no taper).
// leadin      - 0 (default): no chamfer; 1: chamfer (45 degree) at max-z end;
//               2: chamfer at both ends, 3: chamfer at z=0 end.
// leadfac     - scale of leadin chamfer length (default: 1.0 = 1/2 thread).
// test        - true = do not render threads (just draw "blank" cylinder).
//               Default: false (draw threads).
module metric_thread (diameter=8, pitch=1, length=1, internal=false, n_starts=1,
                      thread_size=-1, groove=false, square=false, rectangle=0,
                      angle=30, taper=0, leadin=0, leadfac=1.0, test=false)
{
   // thread_size: size of thread "V" different than travel per turn (pitch).
   // Default: same as pitch.
   local_thread_size = thread_size == -1 ? pitch : thread_size;
   local_rectangle = rectangle ? rectangle : 1;

   n_segments = segments (diameter);
   h = (test && ! internal) ? 0 : (square || rectangle) ? local_thread_size*local_rectangle/2 : local_thread_size / (2 * tan(angle));

   h_fac1 = (square || rectangle) ? 0.90 : 0.625;

   // External thread includes additional relief.
   h_fac2 = (square || rectangle) ? 0.95 : 5.3/8;

   tapered_diameter = diameter - length*taper;

   difference () {
      union () {
         if (! groove) {
            if (! test) {
               metric_thread_turns (diameter, pitch, length, internal, n_starts,
                                    local_thread_size, groove, square, rectangle, angle,
                                    taper);
            }
         }

         difference () {

            // Solid center, including Dmin truncation.
            if (groove) {
               cylinder (r1=diameter/2, r2=tapered_diameter/2,
                         h=length, $fn=n_segments);
            } else if (internal) {
               cylinder (r1=diameter/2 - h*h_fac1, r2=tapered_diameter/2 - h*h_fac1,
                         h=length, $fn=n_segments);
            } else {

               // External thread.
               cylinder (r1=diameter/2 - h*h_fac2, r2=tapered_diameter/2 - h*h_fac2,
                         h=length, $fn=n_segments);
            }

            if (groove) {
               if (! test) {
                  metric_thread_turns (diameter, pitch, length, internal, n_starts,
                                       local_thread_size, groove, square, rectangle,
                                       angle, taper);
               }
            }
         }

         // Internal thread lead-in: take away from external solid.
         if (internal) {

            // "Negative chamfer" z=0 end if leadin is 2 or 3.
            if (leadin == 2 || leadin == 3) {

               // Fixes by jeffery.spirko@tamucc.edu.
               cylinder (r1=diameter/2 - h + h*h_fac1*leadfac,
                         r2=diameter/2 - h,
                         h=h*h_fac1*leadfac, $fn=n_segments);
               /*
               cylinder (r1=diameter/2,
                         r2=diameter/2 - h*h_fac1*leadfac,
                         h=h*h_fac1*leadfac, $fn=n_segments);
               */
            }

            // "Negative chamfer" z-max end if leadin is 1 or 2.
            if (leadin == 1 || leadin == 2) {
               translate ([0, 0, length + 0.05 - h*h_fac1*leadfac]) {

                  cylinder (r1=tapered_diameter/2 - h,
                            h=h*h_fac1*leadfac,
                            r2=tapered_diameter/2 - h + h*h_fac1*leadfac,
                            $fn=n_segments);
                  /*
                  cylinder (r1=tapered_diameter/2 - h*h_fac1*leadfac,
                            h=h*h_fac1*leadfac,
                            r2=tapered_diameter/2,
                            $fn=n_segments);
                  */
               }
            }
         }
      }

      if (! internal) {

         // Chamfer z=0 end if leadin is 2 or 3.
         if (leadin == 2 || leadin == 3) {
            difference () {
               cylinder (r=diameter/2 + 1, h=h*h_fac1*leadfac, $fn=n_segments);

               cylinder (r2=diameter/2, r1=diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,
                         $fn=n_segments);
            }
         }

         // Chamfer z-max end if leadin is 1 or 2.
         if (leadin == 1 || leadin == 2) {
            translate ([0, 0, length + 0.05 - h*h_fac1*leadfac]) {
               difference () {
                  cylinder (r=diameter/2 + 1, h=h*h_fac1*leadfac, $fn=n_segments);

                  cylinder (r1=tapered_diameter/2, r2=tapered_diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,
                            $fn=n_segments);
               }
            }
         }
      }
   }
}


// ----------------------------------------------------------------------------
// Input units in inches.
// Note: units of measure in drawing are mm!
module english_thread (diameter=0.25, threads_per_inch=20, length=1,
                      internal=false, n_starts=1, thread_size=-1, groove=false,
                      square=false, rectangle=0, angle=30, taper=0, leadin=0,
                      leadfac=1.0, test=false)
{
   // Convert to mm.
   mm_diameter = diameter*25.4;
   mm_pitch = (1.0/threads_per_inch)*25.4;
   mm_length = length*25.4;

   echo (str ("mm_diameter: ", mm_diameter));
   echo (str ("mm_pitch: ", mm_pitch));
   echo (str ("mm_length: ", mm_length));
   metric_thread (mm_diameter, mm_pitch, mm_length, internal, n_starts,
                  thread_size, groove, square, rectangle, angle, taper, leadin,
                  leadfac, test);
}

// ----------------------------------------------------------------------------
module metric_thread_turns (diameter, pitch, length, internal, n_starts,
                            thread_size, groove, square, rectangle, angle,
                            taper)
{
   // Number of turns needed.
   n_turns = floor (length/pitch);

   intersection () {

      // Start one below z = 0.  Gives an extra turn at each end.
      for (i=[-1*n_starts : n_turns+1]) {
         translate ([0, 0, i*pitch]) {
            metric_thread_turn (diameter, pitch, internal, n_starts,
                                thread_size, groove, square, rectangle, angle,
                                taper, i*pitch);
         }
      }

      // Cut to length.
      translate ([0, 0, length/2]) {
         cube ([diameter*3, diameter*3, length], center=true);
      }
   }
}


// ----------------------------------------------------------------------------
module metric_thread_turn (diameter, pitch, internal, n_starts, thread_size,
                           groove, square, rectangle, angle, taper, z)
{
   n_segments = segments (diameter);
   fraction_circle = 1.0/n_segments;
   for (i=[0 : n_segments-1]) {

      // Keep polyhedron "facets" aligned -- circumferentially -- with base
      // cylinder facets.  (Patch contributed by rambetter@protonmail.com.)
      rotate ([0, 0, (i + 0.5)*360*fraction_circle + 90]) {
         translate ([0, 0, i*n_starts*pitch*fraction_circle]) {
            //current_diameter = diameter - taper*(z + i*n_starts*pitch*fraction_circle);
            thread_polyhedron ((diameter - taper*(z + i*n_starts*pitch*fraction_circle))/2,
                               pitch, internal, n_starts, thread_size, groove,
                               square, rectangle, angle);
         }
      }
   }
}


// ----------------------------------------------------------------------------
module thread_polyhedron (radius, pitch, internal, n_starts, thread_size,
                          groove, square, rectangle, angle)
{
   n_segments = segments (radius*2);
   fraction_circle = 1.0/n_segments;

   local_rectangle = rectangle ? rectangle : 1;

   h = (square || rectangle) ? thread_size*local_rectangle/2 : thread_size / (2 * tan(angle));
   outer_r = radius + (internal ? h/20 : 0); // Adds internal relief.
   //echo (str ("outer_r: ", outer_r));

   // A little extra on square thread -- make sure overlaps cylinder.
   h_fac1 = (square || rectangle) ? 1.1 : 0.875;
   inner_r = radius - h*h_fac1; // Does NOT do Dmin_truncation - do later with
                                // cylinder.

   translate_y = groove ? outer_r + inner_r : 0;
   reflect_x   = groove ? 1 : 0;

   // Make these just slightly bigger (keep in proportion) so polyhedra will
   // overlap.
   x_incr_outer = (! groove ? outer_r : inner_r) * fraction_circle * 2 * PI * 1.02;
   x_incr_inner = (! groove ? inner_r : outer_r) * fraction_circle * 2 * PI * 1.02;
   z_incr = n_starts * pitch * fraction_circle * 1.005;

   /*
    (angles x0 and x3 inner are actually 60 deg)

                          /\  (x2_inner, z2_inner) [2]
                         /  \
   (x3_inner, z3_inner) /    \
                  [3]   \     \
                        |\     \ (x2_outer, z2_outer) [6]
                        | \    /
                        |  \  /|
             z          |[7]\/ / (x1_outer, z1_outer) [5]
             |          |   | /
             |   x      |   |/
             |  /       |   / (x0_outer, z0_outer) [4]
             | /        |  /     (behind: (x1_inner, z1_inner) [1]
             |/         | /
    y________|          |/
   (r)                  / (x0_inner, z0_inner) [0]

   */

   x1_outer = outer_r * fraction_circle * 2 * PI;

   z0_outer = (outer_r - inner_r) * tan(angle);
   //echo (str ("z0_outer: ", z0_outer));

   //polygon ([[inner_r, 0], [outer_r, z0_outer],
   //        [outer_r, 0.5*pitch], [inner_r, 0.5*pitch]]);
   z1_outer = z0_outer + z_incr;

   // Give internal square threads some clearance in the z direction, too.
   bottom = internal ? 0.235 : 0.25;
   top    = internal ? 0.765 : 0.75;

   translate ([0, translate_y, 0]) {
      mirror ([reflect_x, 0, 0]) {

         if (square || rectangle) {

            // Rule for face ordering: look at polyhedron from outside: points must
            // be in clockwise order.
            polyhedron (
               points = [
                         [-x_incr_inner/2, -inner_r, bottom*thread_size],         // [0]
                         [x_incr_inner/2, -inner_r, bottom*thread_size + z_incr], // [1]
                         [x_incr_inner/2, -inner_r, top*thread_size + z_incr],    // [2]
                         [-x_incr_inner/2, -inner_r, top*thread_size],            // [3]

                         [-x_incr_outer/2, -outer_r, bottom*thread_size],         // [4]
                         [x_incr_outer/2, -outer_r, bottom*thread_size + z_incr], // [5]
                         [x_incr_outer/2, -outer_r, top*thread_size + z_incr],    // [6]
                         [-x_incr_outer/2, -outer_r, top*thread_size]             // [7]
                        ],

               faces = [
                         [0, 3, 7, 4],  // This-side trapezoid

                         [1, 5, 6, 2],  // Back-side trapezoid

                         [0, 1, 2, 3],  // Inner rectangle

                         [4, 7, 6, 5],  // Outer rectangle

                         // These are not planar, so do with separate triangles.
                         [7, 2, 6],     // Upper rectangle, bottom
                         [7, 3, 2],     // Upper rectangle, top

                         [0, 5, 1],     // Lower rectangle, bottom
                         [0, 4, 5]      // Lower rectangle, top
                        ]
            );
         } else {

            // Rule for face ordering: look at polyhedron from outside: points must
            // be in clockwise order.
            polyhedron (
               points = [
                         [-x_incr_inner/2, -inner_r, 0],                        // [0]
                         [x_incr_inner/2, -inner_r, z_incr],                    // [1]
                         [x_incr_inner/2, -inner_r, thread_size + z_incr],      // [2]
                         [-x_incr_inner/2, -inner_r, thread_size],              // [3]

                         [-x_incr_outer/2, -outer_r, z0_outer],                 // [4]
                         [x_incr_outer/2, -outer_r, z0_outer + z_incr],         // [5]
                         [x_incr_outer/2, -outer_r, thread_size - z0_outer + z_incr], // [6]
                         [-x_incr_outer/2, -outer_r, thread_size - z0_outer]    // [7]
                        ],

               faces = [
                         [0, 3, 7, 4],  // This-side trapezoid

                         [1, 5, 6, 2],  // Back-side trapezoid

                         [0, 1, 2, 3],  // Inner rectangle

                         [4, 7, 6, 5],  // Outer rectangle

                         // These are not planar, so do with separate triangles.
                         [7, 2, 6],     // Upper rectangle, bottom
                         [7, 3, 2],     // Upper rectangle, top

                         [0, 5, 1],     // Lower rectangle, bottom
                         [0, 4, 5]      // Lower rectangle, top
                        ]
            );
         }
      }
   }
}