diff --git a/readme.md b/readme.md
index 28ee733..018b7ee 100644
--- a/readme.md
+++ b/readme.md
@@ -20,23 +20,23 @@ See [usage](docs/usage.md) for requirements, installation instructions and a usa
Vitamins A-I | Vitamins J-Q | Vitamins R-Z | Printed | Utilities | Core Utilities |
Axials | Jack | Rails | Box | Annotation | BOM |
Ball_bearings | KP_pillow_blocks | Ring_terminals | Butt_box | Bezier | Clip |
- Batteries | LDRs | Rockers | Cable_grommets | Dogbones | Global |
- Belts | LED_meters | Rod | Carriers | Fillet | Polyholes |
- Blowers | LEDs | SCS_bearing_blocks | Corner_block | Gears | Rounded_rectangle |
- Bulldogs | Leadnuts | SK_brackets | Door_hinge | Hanging_hole | Sphere |
- Buttons | Light_strips | SMDs | Door_latch | Horiholes | Teardrops |
- Cable_strips | Linear_bearings | SSRs | Fan_guard | Layout | |
- Cameras | Magnets | Screws | Fixing_block | Maths | |
- Circlips | Mains_sockets | Sealing_strip | Flat_hinge | Offset | |
- Components | Microswitches | Sheets | Foot | Quadrant | |
- DIP | Microview | Spades | Handle | Round | |
- D_connectors | Modules | Spools | PCB_mount | Rounded_cylinder | |
- Displays | Nuts | Springs | PSU_shroud | Rounded_polygon | |
- Extrusion_brackets | O_ring | Stepper_motors | Printed_box | Sector | |
- Extrusions | Opengrab | Swiss_clips | Ribbon_clamp | Sweep | |
- Fans | PCB | Toggles | SSR_shroud | Thread | |
- Fuseholder | PCBs | Transformers | Screw_knob | Tube | |
- Geared_steppers | PSUs | Tubings | Socket_box | | |
+ Batteries | LDRs | Rockers | Cable_grommets | Catenary | Global |
+ Belts | LED_meters | Rod | Carriers | Dogbones | Polyholes |
+ Blowers | LEDs | SCS_bearing_blocks | Corner_block | Fillet | Rounded_rectangle |
+ Bulldogs | Leadnuts | SK_brackets | Door_hinge | Gears | Sphere |
+ Buttons | Light_strips | SMDs | Door_latch | Hanging_hole | Teardrops |
+ Cable_strips | Linear_bearings | SSRs | Fan_guard | Horiholes | |
+ Cameras | Magnets | Screws | Fixing_block | Layout | |
+ Circlips | Mains_sockets | Sealing_strip | Flat_hinge | Maths | |
+ Components | Microswitches | Sheets | Foot | Offset | |
+ DIP | Microview | Spades | Handle | Quadrant | |
+ D_connectors | Modules | Spools | PCB_mount | Round | |
+ Displays | Nuts | Springs | PSU_shroud | Rounded_cylinder | |
+ Extrusion_brackets | O_ring | Stepper_motors | Printed_box | Rounded_polygon | |
+ Extrusions | Opengrab | Swiss_clips | Ribbon_clamp | Sector | |
+ Fans | PCB | Toggles | SSR_shroud | Sweep | |
+ Fuseholder | PCBs | Transformers | Screw_knob | Thread | |
+ Geared_steppers | PSUs | Tubings | Socket_box | Tube | |
Green_terminals | Panel_meters | Variacs | Strap_handle | | |
Hot_ends | Pillars | Veroboard | | | |
Hygrometer | Pin_headers | Washers | | | |
@@ -5245,6 +5245,36 @@ Bezier curves and function to get and adjust the length or minimum z point.
![bezier](tests/png/bezier.png)
+Top
+
+---
+
+## Catenary
+Catenary curve to model hanging wires, etc.
+
+Although the equation of the curve is simply ```y = a cosh(x / a)``` there is no explicit formula to calculate the constant ```a``` or the range of ```x``` given the
+length of the cable and the end point coordinates. See . The Newton-Raphson method is used to find
+```a``` numerically, see .
+
+The coordinates of the lowest point on the curve can be retrieved by calling ```catenary_points()``` with ```steps``` equal to zero.
+
+
+[utils/catenary.scad](utils/catenary.scad) Implementation.
+
+[tests/catenary.scad](tests/catenary.scad) Code for this example.
+
+### Functions
+| Function | Description |
+|:--- |:--- |
+| ```catenary(t, a)``` | Parametric catenary function linear along the length of the curve. |
+| ```catenary_ds_by_da(d, a)``` | First derivative of the length with respect to ```a```. |
+| ```catenary_find_a(d, l, a = 1)``` | Find the catenary constant ```a```, given half the horizontal span and the length. |
+| ```catenary_points(l, x, y, steps = 100)``` | Returns a list of 2D points on the curve that goes from the origin to ```(x,y)``` and has length ```l```. |
+| ```catenary_s(d, a)``` | Length of a symmetric catenary with width ```2d```. |
+
+![catenary](tests/png/catenary.png)
+
+
Top
---
diff --git a/tests/catenary.scad b/tests/catenary.scad
new file mode 100644
index 0000000..2d35330
--- /dev/null
+++ b/tests/catenary.scad
@@ -0,0 +1,56 @@
+//
+// NopSCADlib Copyright Chris Palmer 2020
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib 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.
+//
+// NopSCADlib 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.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+l = 250; // [1: 1000]
+x = 200; // [1: 1000]
+y = 50; //[-500 : 500]
+
+include <../utils/core/core.scad>
+use <../utils/catenary.scad>
+use <../utils/sweep.scad>
+use <../utils/annotation.scad>
+
+module catenaries() {
+ //
+ // catenary curve path from control points
+ //
+ curve = [for(p = catenary_points(l, x, y)) [p.x, p.y, 0]];
+ //
+ // Draw the curve
+ //
+ r = 0.5;
+ sweep(curve, circle_points(r, $fn = 64));
+ //
+ // Minimum Z
+ //
+ min_z = catenary_points(l, x, y, 0);
+
+ color("blue") {
+ translate([min_z.x, min_z.y + r])
+ rotate([-90, 0, 0])
+ arrow();
+
+ translate([min_z.x, min_z.y - r])
+ rotate([90, 0, 0])
+ arrow();
+ }
+}
+
+if($preview)
+ rotate(is_undef($bom) ? 0 : [70, 0, 315])
+ catenaries();
diff --git a/tests/png/catenary.png b/tests/png/catenary.png
new file mode 100644
index 0000000..7efef20
Binary files /dev/null and b/tests/png/catenary.png differ
diff --git a/utils/catenary.scad b/utils/catenary.scad
new file mode 100644
index 0000000..ff879f6
--- /dev/null
+++ b/utils/catenary.scad
@@ -0,0 +1,53 @@
+//
+// NopSCADlib Copyright Chris Palmer 2020
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib 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.
+//
+// NopSCADlib 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.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Catenary curve to model hanging wires, etc.
+//!
+//! Although the equation of the curve is simply ```y = a cosh(x / a)``` there is no explicit formula to calculate the constant ```a``` or the range of ```x``` given the
+//! length of the cable and the end point coordinates. See . The Newton-Raphson method is used to find
+//! ```a``` numerically, see .
+//!
+//! The coordinates of the lowest point on the curve can be retrieved by calling ```catenary_points()``` with ```steps``` equal to zero.
+//
+include
+use
+
+function catenary(t, a) = let(u = argsinh(t)) a * [u, cosh(u)]; //! Parametric catenary function linear along the length of the curve.
+function catenary_s(d, a) = 2 * a * sinh(d / a); //! Length of a symmetric catenary with width ```2d```.
+function catenary_ds_by_da(d, a) = 2 * sinh(d / a) - 2 * d / a * cosh(d / a); //! First derivative of the length with respect to ```a```.
+
+function catenary_find_a(d, l, a = 1) = //! Find the catenary constant ```a```, given half the horizontal span and the length.
+ assert(l > 2 * d, "Not long enough to span the gap") assert(d)
+ abs(catenary_s(d, a) - l) < 0.0001 ? a
+ : catenary_find_a(d, l, max(a - (catenary_s(d, a) - l) / catenary_ds_by_da(d, a), 0.001));
+
+function catenary_points(l, x, y, steps = 100) = //! Returns a list of 2D points on the curve that goes from the origin to ```(x,y)``` and has length ```l```.
+ let(
+ d = x / 2,
+ a = catenary_find_a(d, sqrt(sqr(l) - sqr(y)), d / 2), // Find a to get the correct length
+ offset = argsinh(y / catenary_s(d, a)),
+ t0 = sinh(-d / a + offset),
+ t1 = sinh( d / a + offset),
+ h = a * cosh(-d / a + offset) - a,
+ lowest = offset > d / a ? [0, 0] : offset < -d / a ? [x, y] : [d - offset * a, -h],
+ //dummy = echo(l = l, d = d, a = a, t0=t0, t1=t1, sinh(d / a), s = catenary_s(d, a), offset = offset * a, h = h),
+ p0 = catenary(t0, a)
+ )
+ steps ? [for(t = [t0 : (t1 - t0) / steps : t1]) catenary(t, a) - p0] : lowest;