ppx_deriving_scad
is a PPX deriver that generates functions for the spatial
transformation of user defined abstract and record types containing types for
which said transformation functions are defined, in particular, the Scad.t
,
V3.t
, and V2.t
types of the Scad_ml library.
For example:
open Scad_ml
type mark =
{ scad : Scad.d3
; centre : V3.t
}
[@@deriving scad]
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 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 scad]
to a lone record type t
would give a module that adhered to the
following signature.
open Scad_ml
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 mirror : V3.t -> t -> t
val affine : Affine3.t -> t -> t
end = struct
type t =
{ scad : Scad.three_d Scad.t
; centre : V3.t
}
[@@deriving scad]
end
The list
, option
, and result
types, as well as tuples, are automatically
mapped over, without any additional annotation or functions provided.
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 mirror : V2.t -> t -> t
val affine : Affine2.t -> t -> t
end = struct
type t = (V2.t * V2.t * V2.t) list [@@deriving scad]
end
By default, [@@deriving scad]
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 vec_map = V3.t IntMap.t [@@deriving scad]
Here, IntMap.map
will be used to apply transformations to the contained
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 Jane Street and/or have base
/core
open, then you may use
[@@deriving scad_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 [@scad.map]
and
[@scad.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.
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 scad]
end = struct
type ('s, 'r, 'a) t =
{ a : ('s, 'r, 'a) Scad.t
; b : ('s, 'r, 'a) Scad.t
} [@@deriving scad]
end
Note that this is also an example of polymorphism over the dimensionality of the
Scad_ml.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.
This annotation should be applied to abstract types and fields which represent unit vector. Types/fields marked with this will not be subject to transformations that would cause them to lose thier identity as such, or rotate about anything other than the world origin. Thus:
translate
andscale
will not be applied (identity function instead)- the
?about
parameter will not be passed to the rotation functions (rotate
,axis_rotate
, andquaternion
)
Usage:
type plane =
{ scad : Scad.d3
; normal : V3.t [@scad.unit]
} [@@deriving scad]
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 = plane_translate (v3 5. 5. 0.) plane in
V3.equal plane.normal trans.normal
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.
Usage:
type mark =
{ scad : Scad.d3
; centre : V3.t
; id : int [@scad.ignore]
} [@@deriving scad]
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 scad]
-> positionalf
expected (e.g.map f
)[@@deriving scad_jane]
-> keyword~f
expected (e.g.map ~f
)
Thus, [@scad.map]
indicates that relevant map
functions will obey the
convention of f
being the first positional argument (overiding [@@deriving scad_jane]
), whereas [@scad.mapf]
indicates that a keyword argument of ~f
is expected instead (overiding [@@deriving scad]
). These attributes are not
required for the list
, option
, and result
types, as they do not rely on
any functions in scope.
Usage:
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 scad]
end = struct
type t =
{ std : v3 IntMap.t
; jane : v3 JaneOption.t [@scad.mapf]
}
[@@deriving scad]
end
When the dimensionality of a type is ambiguous (e.g. containing no fields with
concretely dimensional types from Scad_ml
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 [@scad.ignore]
; v : v2
}
[@@deriving scad]
type 'a t = { p : 'a p [@scad.d2] } [@@deriving scad]
end = struct
type 'a p =
{ a : 'a [@scad.ignore]
; v : v2
}
[@@deriving scad]
type 'a t = { p : 'a p [@scad.d2] } [@@deriving scad]
end
Here, there are no Scad_ml
types present in a' t
that can clue
[@@deriving scad]
into whether it is 2d or 3d, so we tag on an attribute
to clear it up.