# Module `OCADml.Path3`

3d path generation (including arcs and basic shapes), manipulation (including roundovers (see `Round`), and conversion to sweeping transformations with `to_transforms`), and measurement.

`type t = V3.t list`

## Construction / Conversion

`val of_list : V3.t list -> t`

`of_list l`

Construct a path from a list of points `l` (no-op).

`val of_seq : V3.t Stdlib.Seq.t -> t`

`of_seq s`

Construct a path from a sequence of points `s`.

`val of_array : V3.t array -> t`

`of_array a`

Construct a path from an array of points `a`.

`val to_list : t -> V3.t list`

`to_list t`

Convert the path `t` to a list of points (no-op).

`val to_seq : t -> V3.t Stdlib.Seq.t`

`to_seq t`

Convert the path `t` to a sequence of points.

`val to_array : t -> V3.t array`

`to_array t`

Convert the path `t` to a array of points.

## General Path Utilities

`val length : ?closed:bool -> t -> float`

`length ?closed path`

Calculate the length (total travel distance) of the `path`. If `closed` is `true`, include the distance between the endpoints (default = `false`).

`val cummulative_length : ?closed:bool -> t -> float list`

`cummulative_length ?closed path`

Calculate the cummulative length (distance travelled by each point) along the `path`. If `closed` is `true`, include the distance between the endpoints (default = `false`).

`val to_continuous : ?closed:bool -> t -> float -> V3.t`

`to_continuous path`

Return a continuous function from values over the range of `0.` to `1.` to positions along `path` (like a bezier function), treated as open (`closed = false`) by default.

`val resample : freq:[< `N of int | `Spacing of float ] -> t -> t`

`resample ~freq path`

Resample `path` with the given `freq`uency (either a flat number of points, or a target point spacing). Note that the only points guaranteed to appear in the output are the start and end points of `path`. For upsampling that preserves input points, see `subdivide`.

```val subdivide : ?closed:bool -> freq: [ `N of int * [ `ByLen | `BySeg ] | `RoughN of int * [ `ByLen | `BySeg ] | `Refine of int * [ `ByLen | `BySeg ] | `RoughRefine of int * [ `ByLen | `BySeg ] | `Spacing of float ] -> V3.t list -> V3.t list```

`subdivide ?closed ~freq path`

Subdivides `path` with given `freq`uency, including each of the original points in the output (unlike `resample`). This can be a flat number of points provided directly with ``N (n, by)`, or as a multiple of number of points in `path` with ``Refine (factor, by)`. The strategy for distribution of points for these count based methods is set with the second parameter `by`, which can be either ``BySeg` (same number of additional points per segment) and ``ByLen` (segments gain new points proportional to their length). Alternatively, a maximum ``Spacing dist` can be specified instead. The ``Rough` point sampling variants will favour sampling uniformity, at the expense of not adhering exactly to the requested point count.

```val cut : ?closed:bool -> distances:[ `Abs of float list | `Rel of float list ] -> t -> t list```

`cut ?closed ~distances path`

Cut `path` at a list of increasing `distances` (``Abs`olute or ``Rel`ative) along it from the start. If `closed` is `true`, the segment between the end and beginning of `path` will be considered, and the first point will be the last of the second path returned. Negative ``Abs distance` will start from the end to find the split point. Raises `Invalid_argument` if `distance` is an endpoint, or further than the end of the `path`.

```val split : ?closed:bool -> distance:[ `Abs of float | `Rel of float ] -> t -> t * t```

`split ?closed ~distance path`

Split `path` into two at the position `distance` (``Abs`olute or ``Rel`ative) along `path` from the start. Otherwise the behaviour is the same as `cut`.

```val noncollinear_triple : ?eps:float -> t -> ((int * int * int) * (V3.t * V3.t * V3.t)) option```

`noncollinear_triple ?eps path`

Returns a pair of triples of non-collinear indices and the corresponding points from `path` (if the path is not completely collinear). Two well separated points are selected, and the third point is the furthest off the line drawn by the first two points.

`val is_collinear : ?eps:float -> t -> bool`

`is_collinear ?eps path`

Returns `true` if all points in `path` are collinear (fall within `eps` distance of the same line).

`val prune_collinear : ?closed:bool -> t -> t`

`prune_collinear path`

Remove collinear points from `path`. If `closed` is `true` the last point can be pruned as collinear with the first. The first point is never pruned.

```val deduplicate_consecutive : ?closed:bool -> ?keep:[ `First | `Last | `FirstAndEnds | `LastAndEnds ] -> ?eq:(V3.t -> V3.t -> bool) -> t -> t```

`deduplicate_consecutive ?closed ?keep ?eq path`

Remove consecutive duplicate points as determined by the (approximate) equality function `eq` (`V.approx ~eps:1e-9` by default) from `path`. By default `keep` is ``First`, which includes the first point of each run of duplicates in the output. This can be instead be set to `keep` the ``Last`, or to ``FirstAndEnds` or ``LastAndEnds`, which follow their respective simpler rules with the caveat of preserving the endpoints (first and last points) of the path. The path is treated as open (`closed = false`) by default, if `closed` is `true` the last point of the path may be dropped (even if `keep` is ``FirstAndEnds | `LastAndEnds`).

`val deriv : ?closed:bool -> ?h:float -> t -> t`

`deriv ?closed ?h path`

Computes a numerical derivative of `path`, with `h` (default `1.`) giving the step size of the sampling of `path`, so that the derivative can be scaled correctly. Setting `closed` to `true` will include computation of the derivative between the last and first point of the `path` (default `false`).

`val deriv_nonuniform : ?closed:bool -> h:float list -> t -> t`

`deriv_nonuniform ?closed ?h path`

Computes a numerical derivative of `path`, with `h` giving the non-uniform step sizes of the sampling of `path`, so that the derivative can be scaled correctly. Setting `closed` to `true` will include computation of the derivative between the last and first point of the `path` (default `false`). As `h` provides scaling factors for each segment of the path, it must have a length of one less than `path` if it's unclosed, and the same length if `closed` is `true`.

`val tangents : ?uniform:bool -> ?closed:bool -> t -> t`

`tangents ?uniform ?closed path`

Compute tangent unit vectors of `path`. Set `closed` to `true` to indicate that tangents should include between the end and beginning of the path (default = `false`). Sampling of `path` is assumed to be `uniform` unless the parameter is set to `false`, in which case the derivatives will be adjusted to correct for non-uniform sampling of points.

```val continuous_closest_point : ?closed:bool -> ?n_steps:int -> ?max_err:float -> (float -> V3.t) -> V3.t -> float```

`continuous_closest_point ?closed ?n_steps ?max_err f p`

Find the closest position (from `0.` to `1.`) along the path function `f` to the point `p`.

• `n_steps` sets the granularity of search at each stage.
• `max_err` the maximum distance the solution can be from the target `p`
`val segment : ?closed:bool -> t -> V3.line list`

`segment ?closed path`

Break `path` into line segments. If `closed` is `true`, include a segment between the last and first points of `path` (default `false`).

`val reindex_polygon : t -> t -> t`

`reindex_polygon reference poly`

Rotate the polygonal (closed / coplanar) path `poly` to optimize its pairwise point association with the `reference` polygon. Paths should have the same clockwise winding direction (not checked / corrected).

`val lerp : t -> t -> float -> t`

`lerp a b u`

Linearly interpolate between the paths `a` and `b`. Raises `Invalid_argument` if the paths are of unequal length.

```val nearby_idxs : ?min_tree_size:int -> ?radius:float -> V3.t list -> V3.t -> int list```

`nearby_idxs ?min_tree_size ?radius path p`

Find the indices of points within `radius` (default = `1e-9`) distance from the target point `p` in `path`. Match indices will be returned in arbitrary order (unsorted). When `path` is provided (eagerly on partial application), the length will be checked and a function to perform the search will be generated. If `path` is shorter than `min_tree_size`, it will be a simple direct search otherwise a `BallTree3.t` will be constructed. Thus, if you plan to search for more than one target point, take care to apply this function in two steps to avoid repeated length checks and closure/tree generations.

```val nearby_points : ?min_tree_size:int -> ?radius:float -> V3.t list -> V3.t -> V3.t list```

`nearby_points ?min_tree_size ?radius path`

Find the points within `radius` (default = `1e-9`) distance from the target point `p` in `path`. Matched points will be returned in arbitrary order (unsorted). When `path` is provided (eagerly on partial application), the length will be checked and a function to perform the search will be generated. If `path` is shorter than `min_tree_size`, it will be a simple direct search otherwise a `BallTree3.t` will be constructed. Thus, if you plan to search for more than one target point, take care to apply this function in two steps to avoid repeated length checks and closure/tree generations.

```val closest_tangent : ?closed:bool -> ?offset:V3.t -> line:V3.line -> t -> int * V3.line```

`closest_tangent ?closed ?offset ~line t`

Find the tangent segment (and its index) on the curved path `t` closest to `line` after `offset` (default = `V3.zero`) is applied to the points of `t` (can be used to centre the path relative to `line` to help in choosing the desired tangent).

## Creation and 2d-3d Conversion

`val of_tups : (float * float * float) list -> t`

`of_tups ps`

Create a 3d path from a list of xyz coordinate triples.

`val of_path2 : ?plane:Plane.t -> Path2.t -> t`

`of_path2 ?plane path`

Lift a 2d `path` onto `plane` (default = `Plane.xy`).

`val to_path2 : ?plane:Plane.t -> t -> Path2.t`

`to_path2 ?plane t`

Project the 3d path `t` onto `plane` (default = `Plane.xy`).

`val to_plane : ?no_check:bool -> ?eps:float -> t -> Plane.t`

`to_plane ?no_check ?eps t`

Compute the normalized cartesian equation of the plane that the path `t` resides on. If there are fewer than three points in `t`, or they are not coplanar within the tolerance `eps` (unless `no_check = true`), an `Invalid_argument` exception is raised.

`val project : Plane.t -> t -> Path2.t`

`project plane t`

Project the 3d path `t` onto `plane`.

## Basic Shapes

`val circle : ?fn:int -> ?fa:float -> ?fs:float -> ?plane:Plane.t -> float -> t`

`circle ?fn ?fa ?fs ?plane radius`

Draw a circular path of radius `r` onto `plane` (default = `Plane.xy`).

`val square : ?center:bool -> ?plane:Plane.t -> V2.t -> t`

`square ?center ?plane dims`

Draw a rectangular path with xy `dims` (e.g. width and height) onto `plane` (default = `Plane.xy`). If `center` is `true` then the path will be centred around the origin (default = `false`).

`val ellipse : ?fn:int -> ?fa:float -> ?fs:float -> ?plane:Plane.t -> V2.t -> t`

`ellipse ?fn ?fa ?fs ?plane radii`

Draw an ellipse with xy `radii` onto `plane` (default = `Plane.xy`). The greater of the two radii is used for fragment/resolution calculation.

`val star : ?plane:Plane.t -> r1:float -> r2:float -> int -> t`

`star ?plane ~r1 ~r2 n`

Draw an `n` pointed star with inner radius `r1` and outer radius `r2` onto `plane` (default = `Plane.xy`).

## Drawing Arcs

```val arc : ?rev:bool -> ?fn:int -> ?fa:float -> ?fs:float -> ?plane:Plane.t -> ?wedge:bool -> centre:V3.t -> radius:float -> start:float -> float -> t```

`arc ?rev ?fn ?fa ?fs ?plane ?wedge ~centre ~radius ~start a`

Draw an arc onto a 3d `plane` (default = `Plane.xy`). See `Path2.arc`.

```val arc_about_centre : ?rev:bool -> ?fn:int -> ?fa:float -> ?fs:float -> ?dir:[ `CW | `CCW ] -> ?wedge:bool -> centre:V3.t -> V3.t -> V3.t -> t```

`arc_about_centre ?rev ?fn ?fa ?fs ?dir ?wedge ~centre p1 p2`

Draw an arc between `p1` and `p2`, about `centre`, on the 3d plane occupied by the three points. See `Path2.arc_about_centre`.

```val arc_through : ?rev:bool -> ?fn:int -> ?fa:float -> ?fs:float -> ?wedge:bool -> V3.t -> V3.t -> V3.t -> t```

`arc_through ?rev ?fn ?fa ?fs ?wedge p1 p2 p3`

Draw an arc through the points `p1`, `p2`, and `p3`. See `Path2.arc_through`.

```val helix : ?fn:int -> ?fa:float -> ?fs:float -> ?left:bool -> n_turns:int -> pitch:float -> ?r2:float -> float -> t```

`helix ?fn ?fa ?fs ?left ~n_turns ~pitch ?r2 r1`

Draw a 3d helical path around a cylinder/cone with start radius `r1` and end radius `r2` (default = `r1`).

• `n_turns` sets the number of revolutions around the z-axis
• `pitch` describes the height of one complete turn
• `left` is used to set handedness (default = `true`)
• `fn`, `fa`, and `fs` parameters govern quality as they do in OpenSCAD

## Roundovers

Specification and application of circular, chamfer, and bezier (continuous curvature) roundovers to 3d paths.

Based on the BOSL2 rounding module.

`module Round : sig ... end`

Configuration module with types and helpers for specifying path roundovers.

```val roundover : ?fn:int -> ?fa:float -> ?fs:float -> ?overrun:[ `Fail | `Warn | `Fix | `NoCheck ] -> Round.t -> V3.t list```

`roundover ?fn ?fa ?fs ?overrun path_spec`

Apply the roundover specifictions in `path_spec` on the bundled path/shape, with quality set by the `fn`, `fa`, and `fs` parameters. Collinear points are ignored (included in output without roundover applied).

When `overrun` is set to ``Fail` (as it is by default) this function will raise `Failure` if computed joint distances would lead to point insertion that causes the path to reverse/double back on itself. Alternatively:

• ``Warn` will print the detected overruns to `stdout` rather than raising `Failure` (useful for debuggin)
• ``Fix` will automatically reduce the corner joints involved in each overrun proportional to their lengths.
• ``NoCheck` skips overrun detection

## Geometry

`val normal : t -> V3.t`

`normal t`

Calculate the normal vector of the path `t`. An `Invalid_argument` exception is raised if there are fewer than three points in `t`.

`val centroid : ?eps:float -> t -> V3.t`

`centroid ?eps t`

Compute the centroid of the path `t`. If `t` is collinear or self-intersecting (within `eps` tolerance), an `Invalid_argument` exception is raised.

`val area : ?signed:bool -> t -> float`

`area ?signed t`

Calculate the area of the co-planar path (describing a polygon) `t`. If `signed` is `true`, the signed area is returned.

`val coplanar : ?eps:float -> t -> bool`

`coplanar ?eps t`

Returns `true` if all points in `t` are coplanar, within the tolerance `eps`. If there are fewer than 3 points, or the path is collinear, this returns `false`.

`val bbox : t -> Gg.Box3.t`

`bbox t`

Compute the 3d bounding box of the path `t`.

## Sweeping Transform Helpers

`val scaler : ?ez:(V2.t * V2.t) -> V2.t -> float -> Affine3.t`

`scaler ?ez scale`

Create a lookup from relative position (`0.` to `1.`) to scaling transformation matrix for interpolating from `{x = 1.; y = 1.}` at `0.` to `scale` by `1.`. If provided, the pair of handle points `ez` will be used to ease the scaling (see `Easing.make`).

`val twister : ?ez:(V2.t * V2.t) -> float -> float -> Affine3.t`

`twister ?ez angle`

Create a lookup from relative position (`0.` to `1.`) to rotation transformation matrix for interpolating from `0.` (no rotation) at `0.` to `angle` by `1.`. If provided, the pair of handle points `ez` will be used to ease the scaling (see `Easing.make`).

```val to_transforms : ?mode:[ `Auto | `Align of V3.t | `NoAlign | `Euler ] -> ?scale_ez:(V2.t * V2.t) -> ?twist_ez:(V2.t * V2.t) -> ?scale:V2.t -> ?twist:float -> t -> Affine3.t list```

`to_transforms ?mode ?scale_ez ?twist_ez ?scale ?twist t`

Generate list of transformations that can be applied to three-dimensional vectors (`V3.t` via `Affine3.transform`) or shapes, to move them along the path `t` (intended to be applied to the vector/shape from its original position each time). Note that the transforms are generated assuming the shape is centred on the origin, and for the purposes of alignment to the beginning of the path and scaling/twisting, laying flat on the XY plane.

When `mode` is ``Auto | `Align _ | `NoAlign` (default it ``Auto`), tangents are used to estimate appropriate rotations for each translation, using quaternion alignment from tangent to tangent, accumulating rotation along the way. In the case of ``Auto`, some effort is made to automatically apply an alignment rotation towards the first tangent of the path before each transformation such that the orientation of the shape being swept will be consistent and sensible, though some manual rotation of the shape before sweeping along the generated transforms may be necessary to get the desired result.

``Align v` and ``NoAlign` follow the same quaternion accumulation strategy along the path `t`, however they differ from ``Auto` in their handling of pre-alignment transformations. With ``Align v`, the vector `v` will be used to align the assumed shape normal of `(v3 0. 0. 1.)` to, while ``NoAlign` will skip the alignment step entirely (likely desirable if the intention is to sweep a shape not laying on the XY plane).

Setting `mode = `Euler` will use euler rotations instead, which can have results more in line with expectations in some scenarios (helical-like paths for example, though `Mesh.helix_extrude` may be a better fit in that case), but fail in others. For instance, ``Euler` can generate an abrupt when the path tangent is exactly vertical.

If provided, `scale` and `twist`, specify scaling and rotation to be applied along the path increasing up to the provided value by the end. Scaling/twisting proceeds linearly by default, unless the corresponding `_ez` parameters are provided to describe the desired eased transition (see `scaler` and `twister`).

```val helical_transforms : ?fn:int -> ?fa:float -> ?fs:float -> ?scale_ez:(V2.t * V2.t) -> ?twist_ez:(V2.t * V2.t) -> ?scale:V2.t -> ?twist:float -> ?left:bool -> n_turns:int -> pitch:float -> ?r2:float -> float -> Affine3.t list```

```helical_transforms ?fn ?fs ?fa ?scale_ez ?twist_ez ?scale ?twist ?left ~n_turns ~pitch ?r2 r1```

Affine transformations following a helical path. This can be thought of like a special case of `to_transforms`, but using a path generated with `helix`, and with rotational calculations taking into account the helical trajectory.

```val prune_transforms : ?min_dist:float -> shape:(int -> t) -> Affine3.t list -> (int * Affine3.t) list```

`prune_transforms ?min_dist ~shape transforms`

Filter `transforms` into `(idx, m)` pairs for which the polygon computed by applying `m` to the corresponding shape (result of `shape idx`, allowing for scaling operations etc) is at least `min_dist` (default `0.05`) above the plane of the previous polygon. This can be useful for avoiding self-intersections in the output of `Mesh.sweep`. Note that all polygons returned by `shape` must be planar, else `Failure` will be raised.

## Path Matching / Vertex Association

Point duplicating strategies for associating vertices between incommensurate closed polygonal paths/profiles. Primarily for use in conjunction with `Mesh.skin` and `Mesh.morphing_sweep`, where commensurate profiles are required to draw edges between.

Ported from the skin module of the BOSL2 OpenSCAD library.

`val distance_match : V3.t list -> V3.t list -> V3.t list * V3.t list`

`distance_match a b`

If the closed polygonal paths `a` and `b` have incommensurate lengths, points on the smaller path are duplicated and the larger path is shifted (list rotated) in such a way that the total length of the edges between the associated vertices (same index/position) is minimized. The replacement paths, now having the same lengths, are returned as a pair (with the same order). This algorithm generally produces a good result when both `a` and `b` are discrete profiles with a small number of vertices.

This is computationally intensive ( O(N3) ), if the profiles are already known to be lined up, with their zeroth indices corresponding, then `aligned_distance_match` provides a ( O(N2) ) solution.

`val aligned_distance_match : V3.t list -> V3.t list -> V3.t list * V3.t list`

`aligned_distance_match a b`

Like `distance_match`, but the paths `a` and `b` are assumed to already be "lined up", with the zeroth indices in each corresponding to one another.

`val tangent_match : V3.t list -> V3.t list -> V3.t list * V3.t list`

`tangent_match a b`

If the closed polygonal paths `a` and `b` have incommensurate lengths, points on the larger (ideally convex, curved) path are grouped by association of their tangents with the edges of the smaller (ideally discrete) polygonal path. The points of the smaller path are then duplicated to associate with their corresponding spans of tangents on the curve, and the larger path is rotated to line up the indices. The profiles, now having the same length are returned as a pair in the order that they were applied returned.

This algorithm generally produces good results when connecting a discrete polygon to a convex finely sampled curve. It may fail if the curved profile is non-convex, or doesn't have enough points to distinguish all of the tangent points from each other.

## Basic Transfomations

`val translate : V3.t -> t -> t`
`val xtrans : float -> t -> t`
`val ytrans : float -> t -> t`
`val ztrans : float -> t -> t`
`val rotate : ?about:V3.t -> V3.t -> t -> t`
`val xrot : ?about:V3.t -> float -> t -> t`
`val yrot : ?about:V3.t -> float -> t -> t`
`val zrot : ?about:V3.t -> float -> t -> t`
`val quaternion : ?about:V3.t -> Quaternion.t -> t -> t`
`val axis_rotate : ?about:V3.t -> V3.t -> float -> t -> t`
`val affine : Affine3.t -> t -> t`
`val scale : V3.t -> t -> t`
`val xscale : float -> t -> t`
`val yscale : float -> t -> t`
`val zscale : float -> t -> t`
`val mirror : V3.t -> t -> t`