{-# LANGUAGE OverloadedStrings #-}
{-|
  [Blender](https://www.blender.org/) is a free and open-source 3D graphics toolkit.
  It is usually used through a graphical user-interface but can also be
  scripted via Python. These Blender scripts can access 100% of Blender's
  functionality and offer a convenient way of coding 3D effects.

  Running Blender can be time-consuming but heavy caching means scripts
  are only run when they change.

  Blender cheatsheet:

> # To generate with a transparent background, set film_transparent = True:
> bpy.context.scene.render.film_transparent = True
>
> # Filmic is great for photorealism but bad for animations.
> # If you want your textures to keep their exact color values,
> # set the view_transform to 'Standard':
> bpy.context.scene.view_settings.view_transform = 'Standard'
>
> # Blender's default render engine is 'EEVEE', fast but not a raytracer.
> # To switch to raytracing, set the engine to 'CYCLES':
> bpy.context.scene.render.engine = 'CYCLES'
>
> # Rendering at full resolution can be slow. When developing, try
> # decreasing the resolution_percentage for faster renders.
> bpy.context.scene.render.resolution_percentage = 10
>
> # The resolution of the final image are set by resolution_x and resolution_y:
> bpy.context.scene.render.resolution_x = 320
> bpy.context.scene.render.resolution_y = 180

-}
module Reanimate.Blender
  ( blender
  , blender'
  ) where

import           Data.Hashable              (Hashable (hash))
import           Data.Text                  (Text)
import qualified Data.Text.IO               as T
import           Graphics.SvgTree           (Tree)
import           Reanimate.Animation        (SVG)
import           Reanimate.Cache            (cacheFile, encodeInt)
import           Reanimate.Constants        (screenHeight, screenWidth)
import           Reanimate.Misc             (requireExecutable, runCmd)
import           Reanimate.Parameters       (pNoExternals)
import           Reanimate.Raster           (mkImage)
import           Reanimate.Svg.Constructors (mkText)
import           System.FilePath            (replaceExtension, (<.>))
import           System.IO.Unsafe           (unsafePerformIO)

-- | Run a Blender script and embed the resulting image file. The
--   image will be scaled to fit the screen exactly (assuming a default
--   canvas layout). Note that Blender resolution defaults to 1920x1080
--   but can be changed in the script code.
blender :: Text -> SVG
blender script =
  unsafePerformIO $ mkBlenderImage script

-- | Generate Blender image as a separate PNG file. Can be embedded with
--   `mkImage`.
blender' :: Text -> FilePath
blender' script =
  unsafePerformIO $ mkBlenderImage' script

mkBlenderImage :: Text -> IO Tree
mkBlenderImage script | pNoExternals = pure $ mkText script
mkBlenderImage script =
  mkImage screenWidth screenHeight <$> mkBlenderImage' script

mkBlenderImage' :: Text -> IO FilePath
mkBlenderImage' _ | pNoExternals = pure "/blender/has/been/disabled"
mkBlenderImage' script = cacheFile template $ \target -> do
    exec <- requireExecutable "blender"
    let py_file = replaceExtension target "py"
    T.writeFile py_file script
    runCmd exec [ "--background","--render-format", "PNG"
                , "--python-exit-code", "1"
                , "--render-output", target, "--python", py_file]
  where
    template = encodeInt (hash script) <.> "png"