(** {0 Rounded sweeps} *)

open OCADml
open OSCADml

(** Create a bezier spline function which passes through all of points in
    [control] using {{!OCADml.Bezier3.of_path} [Bezier3.of_path]}, and
   interpolate [20] points along it to create our [path] with
   {{!OCADml.Bezier3.curve} [Bezier3.curve]}. *)

let control =
  V3.[ v 5. 5. 12.; v 0. 20. 20.; v 30. 30. 0.; v 50. 20. 5.; v 35. (-10.) 15. ]

let path = Bezier3.curve ~fn:30 @@ Bezier3.of_path control

(** We can quickly visualize [path], and the [control] points that it passes
    through by using the {{!OSCADml.Debug.show_path3} [Debug.show_path3]}
    helper, which places a {{!OSCADml.Scad.d3} [Scad.d3]} shape at each point
    along the path (this takes a function from index to shape, rather than a
    shape directly to allow for differentiating the points, {i e.g.} numbering
    with {{!OSCADml.Scad.text} [Scad.text]}). *)
let () =
  Scad.to_file "bezier_spline_path.scad"
  @@ Scad.union
       [ Debug.show_path3 (fun _ -> Scad.sphere 1.) path
       ; Debug.show_path3
           (fun _ -> Scad.(color ~alpha:0.3 Color.Magenta @@ sphere 2.))
           control
       ]

(** {%html:
    <p style="text-align:center;">
    <img src="_assets/bezier_spline_path.png" style="width:150mm;"/>
    </p> %}
    *)

(** Draw a 2d polygon with a chamfered square outline, and two circular holes.
    Chamfering the square outer path is accomplished via
    {{!OCADml.Path2.roundover} [Path2.roundover]}, which takes a
    {{!OCADml.Path2.Round.t} [Path2.Round.t]} specifation, built here
    using the {{!OCADml.Path2.Round.flat} [Path2.Round.flat]} constructor,
    that tells {{!OCADml.Path2.roundover} [Path2.roundover]} to apply
    [~corner] to all of the points of the given path. *)
let poly =
  let holes =
    let s = Path2.circle ~fn:90 2.
    and d = 1.9 in
    Path2.[ translate (v2 (-.d) (-.d)) s; translate (v2 d d) s ]
  and outer =
    Path2.square ~center:true (v2 10. 10.)
    |> Path2.Round.(flat ~corner:(chamf (`Width 2.)))
    |> Path2.roundover
  in
  Poly2.make ~holes outer

(** 2d shapes defined with {{!OCADml.Poly2.t} [Poly2.t]} can be translated into
    OpenSCAD polygons by way of {{!OCADml.Poly2.to_scad} [Poly2.to_scad]}. *)
let () = Scad.to_file "chamfered_square_with_holes.scad" (Scad.of_poly2 poly)

(** {%html:
    <p style="text-align:center;">
    <img src="_assets/chamfered_square_with_holes.png" style="width:150mm;"/>
    </p> %}
    *)

(** {{!OCADml.Mesh.sweep} [Mesh.sweep]} derived functions take a [~caps]
   parameter that specifies what to do with the end faces of the extruded mesh.
   By default, both caps are flat and identical to the input polygon
   ({{!OCADml.Mesh.Cap.flat_caps} [Mesh.Cap.flat_caps]}), but in this example, we
   will be rounding them over.

    To build our [caps], we'll use the {{!OCADml.Mesh.Cap.capped} [Mesh.Cap.capped]}
    constructor which takes specification types for how we would like to treat the
    bottom and top faces of our extrusion. In this case, we'll be applying
    roundovers to both the [bot]tom and [top] faces, so we use
    {{!OCADml.Mesh.Cap.round} [Mesh.Cap.round]} which takes an
    {{!val:OCADml.Mesh.Cap.offsets} [Mesh.Cap.offsets]} containing the offset
    distance/radius and "vertical" step for each outline of the outer roundover,
    and optionally, the desired treatment of the inner paths (as [~holes]).
    Typically, the offsets will be generated by a helper such as {{!OCADml.Mesh.Cap.chamf}
    [Mesh.Cap.chamf]} and {{!OCADml.Mesh.Cap.circ} [Mesh.Cap.circ]} below.

    Here we apply a negative (outward flaring) chamfer to the bottom face,
    setting [holes] to [`Same], so that the circular holes in [poly] are also
    expanded, rather than pinched off (default is ([~holes:`Flip]), which
    negates the outer roundover for inner paths). For the top face, we specify
    a positive (inward) circular roundover, leaving [holes] as its default since
    we want the holes to flare out instead. *)
let caps =
  Mesh.Cap.(
    capped
      ~bot:(round ~holes:`Same @@ chamf ~height:(-1.2) ~angle:(Float.pi /. 8.) ())
      ~top:(round @@ circ (`Radius 0.5)) )

(** Extrude [poly] along [path], with rounding over the end caps according to
    [caps] using {{!OCADml.Mesh.path_extrude} [Mesh.path_extrude]}. *)
let mesh = Mesh.path_extrude ~path ~caps poly

(** Convert our mesh into an OpenSCAD polyhedron and output to file with
   {{!OSCADml.Scad.of_mesh} [Scad.of_mesh]}. *)
let () = Scad.to_file "rounded_polyhole_sweep.scad" (Scad.of_mesh mesh)

(** {%html:
    <p style="text-align:center;">
    <img src="_assets/rounded_polyhole_sweep.png" style="width:150mm;"/>
    </p> %}
    *)

(** Rounded 3d paths for sweeping can also be drawn with the help of the
    {{!OCADml.Path3.Round} [Path3.Round]} module, which works much the same as
    its counterpart in {{!OCADml.Path2} [Path2]} that we used to chamfer our
    [poly] earlier. *)
let rounded_path =
  Path3.(
    roundover ~fn:32
    @@ Round.flat
         ~closed:true
         ~corner:(Round.circ (`Radius 10.))
         (v3 (-25.) 25. 0. :: Path3.square (v2 50. 50.)) )

(** {{!OCADml.Mesh.path_extrude} [Mesh.path_extrude]} (and other functions
    derived from [Mesh.sweep]) can be given [~caps:`Looped], which will connect
    the final position of the sweep up with the beginning, rather than sealing
    both ends off with caps. If the swept polygon has holes (as our [poly]
    does), they will be included, and continuous much like the outer shape. *)
let () =
  let loop = Scad.of_mesh @@ Mesh.(path_extrude ~caps:Cap.looped ~path:rounded_path poly)
  and cut =
    Scad.cylinder ~fn:50 ~center:true ~height:11. 5.
    |> Scad.scale (v3 1.2 1. 1.)
    |> Scad.translate (v3 20. (-4.) 0.)
  in
  Scad.difference loop [ cut ] |> Scad.to_file "chamfered_loop.scad"

(** {%html:
    <p style="text-align:center;">
    <img src="_assets/chamfered_loop.png" style="width:150mm;"/>
    </p> %}
    *)
