Skip to main content

src/gleamscad.gleam

import fmglee
import gleam/bool
import gleam/list
import gleam/string

pub type RadiusOrDiameter {
  Radius
  Diameter
}

pub type CenteredOrNot {
  Centered
  NotCentered
}

/// builder pattern for our OpenSCAD expressions:
pub type OpenSCADExpr {
  //Boolean Operations:
  Union(expressions: List(OpenSCADExpr))
  Difference(expressions: List(OpenSCADExpr))
  Intersection(expressions: List(OpenSCADExpr))
  //Transformations:
  Translate(expr: OpenSCADExpr, vector: #(Float, Float, Float))
  Rotate(expr: OpenSCADExpr, angle: #(Float, Float, Float))
  Scale(expr: OpenSCADExpr, vector: #(Float, Float, Float))
  //resize([x,y,z],auto,convexity)
  Mirror(expr: OpenSCADExpr, vector: #(Float, Float, Float))
  //multmatrix(m)
  //color("colorname",alpha)
  //color("#hexvalue")
  //color([r,g,b,a])
  //offset(r|delta,chamfer)
  //hull()
  //minkowski(convexity)
  //2D Objects:
  Circle(value: Float, rod: RadiusOrDiameter)
  Square(width: Float, height: Float, center: CenteredOrNot)
  Polygon(List(#(Float, Float)))
  //polygon([points],[paths])
  //text(text,size,font,direction,language,script, halign,valign,spacing)
  //import("….ext", convexity)
  //projection(cut)
  //3D Objects:
  Cube(size: #(Float, Float, Float), center: CenteredOrNot)
  Sphere(value: Float, rod: RadiusOrDiameter)
  Cylinder(
    height: Float,
    value: Float,
    rod: RadiusOrDiameter,
    center: CenteredOrNot,
  )
  Cylinder2(
    height: Float,
    value1: Float,
    value2: Float,
    rod: RadiusOrDiameter,
    center: CenteredOrNot,
  )
  //polyhedron(points, faces, convexity)
  //import("….ext", convexity)
  LinearExtrude(
    expr: OpenSCADExpr,
    height: Float,
    center: CenteredOrNot,
    convexity: Float,
    twist: Float,
    slices: Float,
  )
  //rotate_extrude(angle,convexity)
  //surface(file = "….ext",center,convexity)
  //Custom OpenSCAD code:
  CustomCode(code: String)
  //Special variables...
}

/// Create a union of shapes
pub fn union(expressions: List(OpenSCADExpr)) -> OpenSCADExpr {
  Union(expressions)
}

/// Create a difference of shapes
pub fn difference(expressions: List(OpenSCADExpr)) -> OpenSCADExpr {
  Difference(expressions)
}

/// Creates an intersection of shapes
pub fn intersection(expressions: List(OpenSCADExpr)) -> OpenSCADExpr {
  Intersection(expressions)
}

/// Translate (move) a shape
pub fn translate(
  expr: OpenSCADExpr,
  x: Float,
  y: Float,
  z: Float,
) -> OpenSCADExpr {
  Translate(expr, #(x, y, z))
}

/// Rotate a shape
pub fn rotate(
  expr: OpenSCADExpr,
  x: Float,
  y: Float,
  z: Float,
) -> OpenSCADExpr {
  Rotate(expr, #(x, y, z))
}

/// Scales a shape
pub fn scale(expr: OpenSCADExpr, x: Float, y: Float, z: Float) -> OpenSCADExpr {
  Scale(expr, #(x, y, z))
}

/// Mirrors a shape
pub fn mirror(
  expr: OpenSCADExpr,
  x: Float,
  y: Float,
  z: Float,
) -> OpenSCADExpr {
  Mirror(expr, #(x, y, z))
}

/// Draws a 2d circle
pub fn circle(value: Float, rod: RadiusOrDiameter) -> OpenSCADExpr {
  Circle(value, rod)
}

/// Draws a 2d square
pub fn square(
  width: Float,
  height: Float,
  center: CenteredOrNot,
) -> OpenSCADExpr {
  Square(width, height, center)
}

/// Draws a 2d polygon
pub fn polygon(polygons: List(#(Float, Float))) -> OpenSCADExpr {
  Polygon(polygons)
}

/// Creates a 3d cube with all sides of equal length
pub fn cube(size: Float, center: CenteredOrNot) -> OpenSCADExpr {
  Cube(#(size, size, size), center)
}

/// Creates a 3d cube
pub fn cube3(
  width: Float,
  depth: Float,
  height: Float,
  center: CenteredOrNot,
) -> OpenSCADExpr {
  Cube(#(width, depth, height), center)
}

/// Creates a 3d sphere
pub fn sphere(value: Float, rod: RadiusOrDiameter) -> OpenSCADExpr {
  Sphere(value, rod)
}

/// Creates a 3d cylinder
pub fn cylinder(
  height: Float,
  value: Float,
  rod: RadiusOrDiameter,
  center: CenteredOrNot,
) -> OpenSCADExpr {
  Cylinder(height, value, rod, center)
}

/// Creates a 3d cylinder. Enables you to pass different diameters/radii for top and bottom
pub fn cylinder2(
  height: Float,
  value1: Float,
  value2: Float,
  rod: RadiusOrDiameter,
  center: CenteredOrNot,
) -> OpenSCADExpr {
  Cylinder2(height, value1, value2, rod, center)
}

/// Allows for adding custom code
pub fn custom_code(code: String) -> OpenSCADExpr {
  CustomCode(code)
}

pub fn linear_extrude(
  expr: OpenSCADExpr,
  height: Float,
  center: CenteredOrNot,
  convexity: Float,
  twist: Float,
  slices: Float,
) -> OpenSCADExpr {
  LinearExtrude(expr, height, center, convexity, twist, slices)
}

/// Helper function, for creating some of the strings
fn build_command(
  expr: OpenSCADExpr,
  command: String,
  vector: #(Float, Float, Float),
) -> String {
  fmglee.new("%s([%f, %f, %f]){\n%s\n}")
  |> fmglee.s(command)
  |> fmglee.f(vector.0)
  |> fmglee.f(vector.1)
  |> fmglee.f(vector.2)
  |> fmglee.s(to_openscad(expr))
  |> fmglee.build
}

fn centered_or_not_to_string(value: CenteredOrNot) {
  case value {
    Centered -> True
    NotCentered -> False
  }
  |> bool.to_string
  |> string.lowercase
}

/// Exports the data to a string, containing OpenSCAD code
pub fn to_openscad(expr: OpenSCADExpr) -> String {
  case expr {
    Union(expressions) ->
      fmglee.new("union(){\n%s\n}")
      |> fmglee.s(expressions |> list.map(to_openscad) |> string.join("\n"))
      |> fmglee.build
    Difference(expressions) ->
      fmglee.new("difference(){\n%s\n}")
      |> fmglee.s(expressions |> list.map(to_openscad) |> string.join("\n"))
      |> fmglee.build
    Intersection(expressions) ->
      fmglee.new("intersection(){\n%s\n}")
      |> fmglee.s(expressions |> list.map(to_openscad) |> string.join("\n"))
      |> fmglee.build
    Translate(expr, vector) -> build_command(expr, "translate", vector)
    Rotate(expr, angle) -> build_command(expr, "rotate", angle)
    Scale(expr, vector) -> build_command(expr, "scale", vector)
    Mirror(expr, vector) -> build_command(expr, "mirror", vector)
    Circle(value, rod) ->
      case rod {
        Radius ->
          fmglee.new("circle(r=%f);")
          |> fmglee.f(value)
          |> fmglee.build

        Diameter ->
          fmglee.new("circle(d=%f);")
          |> fmglee.f(value)
          |> fmglee.build
      }
    Square(width, height, center) ->
      fmglee.new("square(width=%f,height=%f,center=%s);")
      |> fmglee.f(width)
      |> fmglee.f(height)
      |> fmglee.s(centered_or_not_to_string(center))
      |> fmglee.build
    Polygon(polygons) ->
      fmglee.new("polygon(points=[%s]);")
      |> fmglee.s(
        polygons
        |> list.map(fn(point) {
          fmglee.new("[%f,%f]")
          |> fmglee.f(point.0)
          |> fmglee.f(point.1)
          |> fmglee.build
        })
        |> string.join(","),
      )
      |> fmglee.build
    Cube(size, center) -> {
      let #(w, d, h) = size
      fmglee.new("cube(size=[%f, %f, %f], center=%s);")
      |> fmglee.f(w)
      |> fmglee.f(d)
      |> fmglee.f(h)
      |> fmglee.s(centered_or_not_to_string(center))
      |> fmglee.build
    }
    Sphere(value, rod) ->
      case rod {
        Radius ->
          fmglee.new("sphere(r=%f);")
          |> fmglee.f(value)
          |> fmglee.build
        Diameter ->
          fmglee.new("sphere(d=%f);")
          |> fmglee.f(value)
          |> fmglee.build
      }
    Cylinder(height, value, rod, center) ->
      case rod {
        Radius ->
          fmglee.new("cylinder(h=%f,r=%f,center=%s);")
          |> fmglee.f(height)
          |> fmglee.f(value)
          |> fmglee.s(centered_or_not_to_string(center))
          |> fmglee.build
        Diameter ->
          fmglee.new("cylinder(h=%f,d=%f,center=%s);")
          |> fmglee.f(height)
          |> fmglee.f(value)
          |> fmglee.s(centered_or_not_to_string(center))
          |> fmglee.build
      }
    Cylinder2(height, value1, value2, rod, center) ->
      case rod {
        Radius ->
          fmglee.new("cylinder(h=%f,r1=%f,r2=%f,center=%s);")
          |> fmglee.f(height)
          |> fmglee.f(value1)
          |> fmglee.f(value2)
          |> fmglee.s(centered_or_not_to_string(center))
          |> fmglee.build
        Diameter ->
          fmglee.new("cylinder(h=%f,d1=%f,d2=%f,center=%s);")
          |> fmglee.f(height)
          |> fmglee.f(value1)
          |> fmglee.f(value2)
          |> fmglee.s(centered_or_not_to_string(center))
          |> fmglee.build
      }
    LinearExtrude(expr, height, center, convexity, twist, slices) ->
      fmglee.new(
        "linear_extrude(height=%f,center=%s,convexity=%f,twist=%f,slices=%f){\n%s\n}",
      )
      |> fmglee.f(height)
      |> fmglee.s(centered_or_not_to_string(center))
      |> fmglee.f(convexity)
      |> fmglee.f(twist)
      |> fmglee.f(slices)
      |> fmglee.s(to_openscad(expr))
      |> fmglee.build
    CustomCode(code) -> code
  }
}