--- jupyter: description: 'Parametric curves in a planex2d sketch: Curve draws a curve from its formula (h(t), v(t)), fitted as a spline to about 1e-12 of its size. A gear involute whose base radius is a dimension, and a NACA 0012 airfoil and a cam from their formulas, each checked against its exact length or area.' --- # Parametric curves `sk.Curve((h, v), (t0, t1))` draws the curve that the point $(h(t), v(t))$ traces as the parameter $t$ runs from $t_0$ to $t_1$, in either form of `px.Curve`: | Call | The point | |---|---| | `sk.Curve(("h(t)", "v(t)"), (t0, t1))` | Two expression strings in $t$. In a constraint sketch, they and $t_0$ and $t_1$ may name driving dimensions, and the curve follows when a dimension changes | | `sk.Curve(f, (t0, t1))` | A Python function $f(t)$ that returns the point $(h, v)$ | planex2d fits a spline to the formula itself, to about 1e-12 of the curve's size, so the curve is exact to rounding, where a spline through points is only as close as its points allow. The expressions use numbers, pi, + − * / ^ and the usual functions, with angles in degrees, $t$ too where it is an angle. In a `px.Sketch`, the curve is a rigid body, fixed where its formula puts it. See [Parametric curves](../basics/curves.md) for both forms and `px.Curve` outside a sketch, and [Curves driven by dimensions](../constraints/curves.md) for placing a curve with constraints. ```python import math import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row ``` ## An involute The flank of a gear tooth is the involute of the gear's base circle, the path of the end of a string unwound from the circle. The example is the involute of a base circle of radius $r_b$, unwound by 40°. The base circle is a construction circle whose radius is the dimension `rb`, and the formula names `rb`: $$h = r_b\,(\cos t + t \sin t), \qquad v = r_b\,(\sin t - t \cos t),$$ with $t$ in radians where it multiplies, `t*pi/180` in the formula. The curve lies where its formula puts it, starting on the base circle (Figure 1). Unwound by the angle $\theta$, the involute is $r_b \theta^2/2$ long, which the print compares: ```python gear = px.Sketch() gear.Circle(20, center=(0, 0), construction=True, id="base") gear.Constrain("base.center", "origin", px.Coincident) gear.Constrain("base", px.Radius, 20, id="rb") gear.Curve(("rb*(cos(t) + t*pi/180*sin(t))", "rb*(sin(t) - t*pi/180*cos(t))"), (0, 40), id="flank") print("status", gear.status, "| length", gear.Measure("flank")["length"], "| rb θ²/2", 20 * math.radians(40) ** 2 / 2) ``` The formula stays with the sketch. When `Update` changes `rb`, the curve is fitted again to the new base circle. With $r_b = 25$, the involute starts on the larger circle and is longer by the factor 25/20: ```python small = gear.Copy() gear.Update({"rb": 25}) print("start", gear.Location("flank.start")[:2], "| length", gear.Measure("flank")["length"], "| rb θ²/2", 25 * math.radians(40) ** 2 / 2) ``` ```python nbsphinx="hide-input" Row((small, "r_b = 20", {"names": False, "fill": False}), (gear, "r_b = 25", {"names": False, "fill": False}), caption="An involute of its base circle, before and after the update") ``` ## An airfoil from its formula The NACA 0012 airfoil of [Splines](splines.md) has a thickness formula in the chord position $x$, which the page there samples at 80 points. Its root $\sqrt{x}$ makes a curve in $x$ steep at the leading edge, so the formula runs in $t$ with cosine spacing instead, $h = c\,(1 - \cos t)/2$ for $t$ from 0° to 180°, and $v$ uses $h$, the other coordinate at the same $t$. The upper side is one curve from the leading edge to the trailing edge, and `Mirrored` copies it about the horizontal axis as the lower side (see [Symmetry](symmetry.md) for mirroring). The sketch is a QuickSketch with plain numbers in its formulas (Figure 2). Its area is compared with the integral of the thickness formula: ```python thickness = "60*(0.2969*sqrt(h/100) - 0.1260*(h/100) - 0.3516*(h/100)^2 + 0.2843*(h/100)^3 - 0.1036*(h/100)^4)" wing = px.QuickSketch().Curve(("100*(1 - cos(t))/2", thickness), (0, 180), id="upper") wing.Mirrored(["upper"], "H", id="lower") profile = wing.Face() exact = 12000 * (0.2969 * 2 / 3 - 0.1260 / 2 - 0.3516 / 3 + 0.2843 / 4 - 0.1036 / 5) print("area", profile.mass, "exact", exact, "relative", (profile.mass - exact) / exact) ``` ```python nbsphinx="hide-input" Row((wing, "NACA 0012: the upper side from its formula, mirrored", {"names": False}), caption="An airfoil from its formula") ``` The spline through 80 points on the Splines page has an area within about 1e-6 of the formula's. The curve from the formula is within about 4e-13. ## A closed curve A curve whose end meets its start is closed, one edge with no vertex, and smooth all around where the formula is periodic. The example is the cam of [Splines](splines.md), its radius $r = R + s\,(1 - \cos t)/2$ with $R = 20$ and the lift $s = 8$ over a full turn, with a bore of 10 (Figure 3). Its area is compared with $\pi\,(R + s/2)^2 + \pi s^2/8$ less the bore: ```python lift = "(20 + 8*(1 - cos(t))/2)" cam = px.QuickSketch().Curve((lift + "*cos(t)", lift + "*sin(t)"), (0, 360), id="lift").Circle(5, center=(0, 0)) disc = cam.Face() exact = math.pi * 24**2 + math.pi * 8**2 / 8 - math.pi * 5**2 print("edges", len(disc.edges), "| area", disc.mass, "exact", exact, "relative", (disc.mass - exact) / exact) ``` ```python nbsphinx="hide-input" Row((disc, "The cam: one closed curve, and the bore"), caption="A closed curve") ``` See [Splines](splines.md) for splines through points, [Parametric curves](../basics/curves.md) for `px.Curve` and Python functions, and [Changing dimensions](../constraints/update.md) for `Update`.