package ppx_deriving_cad

  1. Overview
  2. Docs

[@@deriving cad]

A PPX deriver that generates functions for the spatial transformation of user defined abstract and record types composed of types for which said transformation functions are defined, in particular, the types of the OCADml library (e.g. V3.t and V2.t), as well as CAD specific types such as Scad.t of OSCADml.

API

Ppx_deriving_cad -- ppx module interface

Derivers

Usage

To generate the suite of basic transformation functions for your type, simply attach [@@deriving cad] (or [@@deriving cad_jane]) to the end.

For example:

open OCADml
open OSCADml

type mark =
  { scad : Scad.d3
  ; centre : V3.t
  }
[@@deriving cad]

Generates:

val translate_mark : V3.t -> mark -> mark
val xtrans_mark : float -> mark -> mark
val ytrans_mark : float -> mark -> mark
val ztrans_mark : float -> mark -> mark
val rotate_mark : ?about:V3.t -> V3.t -> mark -> mark
val xrot_mark : ?about:V3.t -> float -> mark -> mark
val yrot_mark : ?about:V3.t -> float -> mark -> mark
val zrot_mark : ?about:V3.t -> float -> mark -> mark
val axis_rotate_mark : ?about:V3.t -> V3.t -> float -> mark -> mark
val quaternion_mark : ?about:V3.t -> Quaternion.t -> mark -> mark
val scale_mark : V3.t -> mark -> mark
val xscale_mark : float -> mark -> mark
val yscale_mark : float -> mark -> mark
val zscale_mark : float -> mark -> mark
val mirror_mark : V3.t -> mark -> mark
val affine_mark : Affine3.t -> mark -> mark

If the name of the type being derived is t, then the functions generated (and those required to be present for the types inside of a type/record being derived) will be given unqualified names. For example, applying [@@deriving cad] to a lone record type t would give a module that adheres to the following signature.

open OCADml
open OSCADml

module Mark : sig
  type t =
    { scad : Scad.d3
    ; centre : V3.t
    }

  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 axis_rotate : ?about:V3.t -> V3.t -> float -> t -> t
  val quaternion : ?about:V3.t -> Quaternion.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
  val affine : Affine3.t -> t -> t
end = struct
  type t =
    { scad : Scad.d3
    ; centre : V3.t
    }
  [@@deriving cad]
end

Mappable types

Basic

The list, option, and result types, as well as tuples, are automatically mapped over, without any additional annotation or functions required. Note the set of functions generated here is restricted to those that are strictly relevant to 2d shapes/vectors compared to the first examples that contained 3d types.

module Tris : sig
  type t = (V2.t * V2.t * V2.t) list

  val translate : V2.t -> t -> t
  val xtrans : float -> t -> t
  val ytrans : float -> t -> t
  val rotate : ?about:V2.t -> float -> t -> t
  val zrot : ?about:V2.t -> float -> t -> t
  val scale : V2.t -> t -> t
  val xscale : float -> t -> t
  val yscale : float -> t -> t
  val mirror : V2.t -> t -> t
  val affine : Affine2.t -> t -> t
end = struct
  type t = (V2.t * V2.t * V2.t) list [@@deriving cad]
end

Abstract

By default, [@@deriving cad] will attempt to map over constructors other than the above basic types by using applying the map function of the relevant module, or for the non-t named type, using the same naming conventions as explained above.

module IntMap = Map.Make (Int)

type v3_map = V3.t IntMap.t [@@deriving cad]

Here, IntMap.map will be used to apply transformations to the contained OCADml.V3.t elements. The expected map function should obey the convention of the function f being the first positional argument. If you are following the conventions of JaneStreet and/or have base/core open, then you may use [@@deriving cad_jane] which defaults to expecting map functions to accept a keyword parameter ~f instead. If you are deriving a record containing types with mixed mapping conventions, you can make use of the [@cad.map] and [@cad.mapf] attributes to specify fields that do not match your default convention.

If the constructor type is not named t as in this example, then this ppx will attempt to use a function with the suffix _map. For example, if the type above was instead V3.t int_map, the function int_map_map will be expected in the scope of the derived type.

Intf generation and dimensional polymorphism

Annotating types in module sigs and .mli files will generate the relevant type signatures.

module PolyScads : sig
  type ('s, 'r, 'a) t =
    { a : ('s, 'r, 'a) Scad.t
    ; b : ('s, 'r, 'a) Scad.t
    }
  [@@deriving cad]
end = struct
  type ('s, 'r, 'a) t =
    { a : ('s, 'r, 'a) Scad.t
    ; b : ('s, 'r, 'a) Scad.t
    }
  [@@deriving cad]
end

Note that this is also an example of polymorphism over the dimensionality of the OSCADml.Scad.t type. Of course, when the type could be either 2d or 3d, only 2d transformations will be available (translation, rotation, scaling, and mirroring), as in the mappable type example.

Attributes

[@cad.unit]

This annotation should be applied to types and record fields which represent unit vector. Types/fields marked with this will not be subject to transformations that would cause them to lose their identity as such, or rotate about anything other than the world origin. Thus:

  • translate and scale will not be applied (identity function instead)
  • the ?about parameter will not be passed to the rotation functions (rotate, axis_rotate, and quaternion) applied to the type marked by @cad.unit.

For example:

type plane =
  { scad : Scad.d3
  ; normal : V3.t [@cad.unit]
  }
[@@deriving cad]

In this case the following would hold:

let true =
  let plane = { scad = Scad.cube (v3 10. 10. 0.001); normal = v3 0. 0. 1. } in
  let trans = translate_plane (v3 5. 5. 0.) plane in
  V3.equal plane.normal trans.normal

[@cad.ignore]

This annotation marks a field (in a record, not applicable to abstract types) to be ignored by all generated transformations. This is useful for ignoring whatever flags/configuration data that you want to carry around along with your type for which the relevant functions have not been implemented.

type mark =
  { scad : Scad.d3
  ; centre : V3.t
  ; id : int [@cad.ignore]
  }
[@@deriving cad]

[@cad.map] and [@cad.mapf]

This annotation marks a type/field for which the transformable type is contained within a mappable type (aka functor), for which map is defined, and whose parameter convention differs from the default specified by the deriver attached to the type declaration.

  • [@@deriving cad] -> positional f expected (e.g. map f)
  • [@@deriving cad_jane] -> keyword ~f expected (e.g. map ~f)

Thus, [@cad.map]indicates that relevant map functions will obey the convention of f being the first positional argument (overiding [@@deriving cad_jane]), whereas [@cad.mapf] indicates that a keyword argument of ~f is expected instead (overiding [@@deriving cad]). These attributes are not required for the list, option, and result types, as they do not rely on any functions in scope.

open Base
module IntMap = Caml.Map.Make (Int)
module JaneOption = Option (* aliased since option is special cased *)

module MixedMapConventions : sig
  type t =
    { std : v3 IntMap.t
    ; jane : v3 JaneOption.t
    }
  [@@deriving cad]
end = struct
  type t =
    { std : v3 IntMap.t
    ; jane : v3 JaneOption.t [@cad.mapf]
    }
  [@@deriving cad]
end

[@cad.d2] and [@cad.d3]

When the dimensionality of a type is ambiguous (e.g. containing no fields with concretely dimensional types such as Scad.d3, or V2.t), these annotations should be used to specify the correct set of functions/signatures to be generated.

module AmbiguousDims : sig
  type 'a p =
    { a : 'a [@cad.ignore]
    ; v : v2
    }
  [@@deriving cad]

  type 'a t = { p : 'a p [@cad.d2] } [@@deriving cad]
end = struct
  type 'a p =
    { a : 'a [@cad.ignore]
    ; v : v2
    }
  [@@deriving cad]

  type 'a t = { p : 'a p [@cad.d2] } [@@deriving cad]
end

Here, there are no OCADml (or related) types present in 'a t that can clue [@@deriving cad] into whether it is 2d or 3d, so we tag on an attribute to clear it up.