{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE StrictData #-}
module GCode ( GCodeFlavor(..)
, toString
) where
import GHC.Generics
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.List
import Data.Aeson (FromJSON (..))
import Text.Printf
import Graphics.Path
import Utils
import Codec.Picture (PixelRGBA8(..))
import Data.Maybe (fromMaybe)
import Data.Char (isSpace)
data GCodeColorBehavior = GCodeColorBehavior {
before :: Maybe String
, passes :: Maybe Int
, parameters :: Maybe (HashMap String Float)
} deriving (Show, Generic)
instance FromJSON GCodeColorBehavior
data GCodeFlavor = GCodeFlavor {
begin :: Maybe String
, end :: Maybe String
, toolon :: Maybe String
, tooloff :: Maybe String
, colors :: Maybe (HashMap String GCodeColorBehavior)
} deriving (Show, Generic)
instance FromJSON GCodeFlavor
data ColorState = ColorState {
csColor :: String
, csBefore :: Maybe String
, csParams :: Maybe String
, csPasses :: Int
}
trim :: String -> String
trim = dropWhileEnd isSpace . dropWhile isSpace
buildColorState :: HashMap String GCodeColorBehavior -> String -> ColorState
buildColorState colorMap color =
ColorState color colorBefore colorParams colorPasses
where
colorBefore = fmap trim $ HashMap.lookup color colorMap >>= before
colorPasses = fromMaybe 1 $ HashMap.lookup color colorMap >>= passes
colorParams = case fromMaybe [] (HashMap.lookup color colorMap >>= parameters >>= (Just . HashMap.toList)) of
[] -> Nothing
params -> Just $ unwords $ map (\(w,p) -> w ++ show p) params
defaultFlavor :: GCodeFlavor
defaultFlavor = GCodeFlavor (Just "G17\nG90\nG0 Z1\nG0 X0 Y0") (Just "G0 Z1") (Just "G01 Z0 F10.00") (Just "G00 Z1") Nothing
toString :: Maybe GCodeFlavor -> Int -> [ColoredPath] -> String
toString mbConfig dpi cps
= maybe [] ((++ "\n").trim) (begin config) ++
intercalate "\n" (toString' (flatten cps) (0,0) True Nothing) ++
"\n" ++
maybe [] ((++ "\n").trim) (end config)
where
config = fromMaybe defaultFlavor mbConfig
colorMap :: HashMap String GCodeColorBehavior
colorMap = fromMaybe HashMap.empty (colors config)
augmentCommand mbColor cmd = maybe cmd (\params -> cmd ++ " " ++ params) (mbColor >>= csParams)
mbToolonCommands = fmap trim (toolon config)
mbTooloffCommands = fmap trim (tooloff config)
toColorString Nothing = "000000"
toColorString (Just (PixelRGBA8 r g b _)) = concatMap (printf "%02X") [r, g, b]
flatten = concatMap (\(ColoredPath color path) -> Left (toColorString color): map Right path)
dd :: Double
dd = fromIntegral dpi
mm :: Double -> Double
mm px = (px * 2.54 * 10) / dd
toString' :: [Either String PathCommand] -> (Double, Double) -> Bool -> Maybe ColorState -> [String]
-- Move, tool off
toString' (Right (MoveTo p@(x,y)) : gs) _ False mbCurrentColor
= printf "G00 X%.4f Y%.4f" (mm x) (mm y) :
toString' gs p False mbCurrentColor
-- Switch off tool before move
toString' (Right (MoveTo p@(x,y)) : gs) _ True mbCurrentColor
= let cont = printf "G00 X%.4f Y%.4f" (mm x) (mm y) : toString' gs p False mbCurrentColor
in case mbTooloffCommands of
Nothing -> cont
Just tooloffCommands -> tooloffCommands : cont
-- Switch on tool before drawing
toString' gs cp False mbCurrentColor
= let cont = toString' gs cp True mbCurrentColor
in case mbToolonCommands of
Nothing -> cont
Just toolonCommands -> toolonCommands : cont
-- Drawing line
toString' (Right (LineTo p@(x,y)) : gs) _ True mbCurrentColor
= augmentCommand mbCurrentColor (printf "G01 X%.4f Y%.4f" (mm x) (mm y)) :
toString' gs p True mbCurrentColor
-- Drawing arc
toString' (Right (ArcTo (ox,oy) p@(x,y) cw) : gs) (cx,cy) True mbCurrentColor
= arcStr : toString' gs p True mbCurrentColor
where
i = ox - cx
j = oy - cy
cmd = if' cw "G03" "G02"
arcStr = augmentCommand mbCurrentColor $
printf "%s X%.4f Y%.4f I%.4f J%.4f" cmd (mm x) (mm y) (mm i) (mm j)
-- Drawing bezier
toString' (Right (BezierTo (c1x,c1y) (c2x,c2y) p2@(p2x,p2y)) : gs) (p1x,p1y) True mbCurrentColor
= bStr : toString' gs p2 True mbCurrentColor
where
i = c1x - p1x
j = c1y - p1y
p = c2x - p2x
q = c2y - p2y
bStr = augmentCommand mbCurrentColor $
printf "G05 I%.4f J%.4f P%.4f Q%.4f X%.4f Y%.4f" (mm i) (mm j) (mm p) (mm q) (mm p2x) (mm p2y)
-- Potential color change, no current color
toString' (Left newColor : gs) p drawing mbCurrentColor
| fmap csColor mbCurrentColor == Just newColor
= toString' gs p drawing mbCurrentColor
| otherwise
= let cont = toString' gs p drawing (Just newColorState)
in case csBefore newColorState of
Nothing -> cont
Just beforeCmd -> beforeCmd : cont
where
newColorState = buildColorState colorMap newColor
toString' [] _ _ _ = []