juicy-gcode 0.1.0.8 → 0.1.0.9
raw patch · 8 files changed
+96/−66 lines, 8 files
Files
- ChangeLog.md +4/−0
- README.md +19/−17
- juicy-gcode.cabal +1/−1
- src/Approx.hs +10/−7
- src/GCode.hs +21/−6
- src/Main.hs +28/−24
- src/Render.hs +6/−5
- src/Types.hs +7/−6
ChangeLog.md view
@@ -1,5 +1,9 @@ # Revision history for juicy-gcode +## 0.1.0.9 -- 2020-05-27 + +- Add option to generate bezier curves instead of arcs + ## 0.1.0.8 -- 2020-05-19 - Fix unhandled bezier edge cases resulting NaNs in GCode
README.md view
@@ -15,11 +15,11 @@ The easiest way is to download one of the pre-built binaries from the [releases page](https://github.com/domoszlai/juicy-gcode/releases). Alternatively, you can build from source code as follows: -* Install [Stack](https://docs.haskellstack.org/en/stable/install_and_upgrade/) if you do not have it yet -* `$ git clone https://github.com/domoszlai/juicy-gcode.git` -* `$ stack build` -* `$ stack install` -* `$ juicy-gcode --help` +- Install [Stack](https://docs.haskellstack.org/en/stable/install_and_upgrade/) if you do not have it yet +- `$ git clone https://github.com/domoszlai/juicy-gcode.git` +- `$ stack build` +- `$ stack install` +- `$ juicy-gcode --help` ``` juicy-gcode - The SVG to G-Code converter @@ -34,7 +34,8 @@ -f,--flavor CONFIGFILE Configuration of G-Code flavor -o,--output OUTPUTFILE The output G-Code file (default is standard output) -d,--dpi DPI Density of the SVG file (default is 72 DPI) - -m,--mirror-y-axis Mirror Y axis + -m,--mirror-y-axis Mirror Y axis to have the result in G-Code coordinate system + -b,--generate-bezier Generate bezier curves (G5) instead of arcs (G2,G3) ``` ## Configuration @@ -58,11 +59,12 @@ ## Limitations Missing features: -* text (easy with e.g. [FontyFruity](https://hackage.haskell.org/package/FontyFruity), maybe once, you can convert text to curves easily anyway) -* filling (moderately difficult) -* clipping (probably not easy, maybe once) -* images (not planned) +- text (easy with e.g. [FontyFruity](https://hackage.haskell.org/package/FontyFruity), maybe once, you can convert text to curves easily anyway) +- filling (moderately difficult) +- clipping (probably not easy, maybe once) +- images (not planned) + ## Testing and bugs There is a JavaScript [hanging plotter simulator](https://github.com/domoszlai/hanging-plotter-simulator) mainly developed to test the generated gcode. @@ -72,16 +74,16 @@ SVG images are built using the following shapes (all of these are subject of an arbitrary affine transformation): -* lines -* circles -* ellipses -* elliptic arcs with optional x axis rotation -* quadratic and cubic bezier curves +- lines +- circles +- ellipses +- elliptic arcs with optional x axis rotation +- quadratic and cubic bezier curves In contrast G-Code implements only -* lines -* non-elliptical arcs +- lines +- non-elliptical arcs That means that only lines, circles and some arcs (non-elliptic ones without rotation) can be translated to G-Code directly. If transformations are also counted, then only lines can be translated to G-Code directly as circles are not invariant under affine transformations. Because of this, the converter is implemented in two stages.
juicy-gcode.cabal view
@@ -1,5 +1,5 @@ name: juicy-gcode -version: 0.1.0.8 +version: 0.1.0.9 license: BSD3 license-file: LICENSE author: dlacko
src/Approx.hs view
@@ -1,6 +1,8 @@ module Approx ( bezier2biarc ) where +import Debug.Trace + import qualified CubicBezier as B import qualified BiArc as BA import qualified Line as L @@ -60,10 +62,10 @@ | (L._m t1) == (L._m t2) = splitAndRecur 0.5 -- Approximation is not close enough yet, refine - | maxDistance > tolerance + | maxDistance > 0.5 = splitAndRecur maxDistanceAt | otherwise - = [biarc] + = Debug.Trace.trace (show error) [biarc] where -- Edge case: P1==C1 or P2==C2 -- there is no derivative at P1 or P2, use the other control point @@ -85,19 +87,20 @@ biarc = BA.create (B._p1 bezier) (B._p1 bezier - c1) (B._p2 bezier) (B._p2 bezier - c2) g -- calculate the error - nrPointsToCheck = (BA.arcLength biarc) / samplingStep + nrPointsToCheck = 10 parameterStep = 1 / nrPointsToCheck - (maxDistance, maxDistanceAt) = maxDistance' 0 0 0 + (error, maxDistance, maxDistanceAt) = maxDistance' 0 0 0 0 - maxDistance' m mt t + maxDistance' e m mt t | t <= 1 - = if' (d > m) (maxDistance' d t nt) (maxDistance' m mt nt) + = if' (d > m) (maxDistance' ne d t nt) (maxDistance' ne m mt nt) | otherwise - = (m, mt) + = (e / nrPointsToCheck, m, mt) where d = distance (BA.pointAt biarc t) (B.pointAt bezier t) nt = t + parameterStep + ne = e + d splitAndRecur t = let (b1, b2) = B.bezierSplitAt bezier t in approxOne b1 ++ approxOne b2
src/GCode.hs view
@@ -24,13 +24,18 @@ dd = fromIntegral dpi mm :: Double -> Double - mm px = (px * 2.54 * 10) / dd + mm px = (px * 2.54 * 10) / dd - toString' (GMoveTo p@(x,y) : gs) _ False = printf "G00 X%.4f Y%.4f" (mm x) (mm y) : toString' gs p False - toString' (GMoveTo p@(x,y) : gs) _ True = off : printf "G00 X%.4f Y%.4f" (mm x) (mm y) : toString' gs p False - toString' gs cp False = on : toString' gs cp True - toString' (GLineTo p@(x,y) : gs) _ True = printf "G01 X%.4f Y%.4f" (mm x) (mm y) : toString' gs p True - toString' (GArcTo (ox,oy) p@(x,y) cw : gs) (cx,cy) True = arcStr : toString' gs p True + toString' (GMoveTo p@(x,y) : gs) _ False + = printf "G00 X%.4f Y%.4f" (mm x) (mm y) : toString' gs p False + toString' (GMoveTo p@(x,y) : gs) _ True + = off : printf "G00 X%.4f Y%.4f" (mm x) (mm y) : toString' gs p False + toString' gs cp False + = on : toString' gs cp True + toString' (GLineTo p@(x,y) : gs) _ True + = printf "G01 X%.4f Y%.4f" (mm x) (mm y) : toString' gs p True + toString' (GArcTo (ox,oy) p@(x,y) cw : gs) (cx,cy) True + = arcStr : toString' gs p True where i = ox - cx j = oy - cy @@ -43,5 +48,15 @@ = printf "G01 X%.4f Y%.4f" (mm x) (mm y) | otherwise = printf "%s X%.4f Y%.4f I%.4f J%.4f" cmd (mm x) (mm y) (mm i) (mm j) + toString' (GBezierTo (c1x,c1y) (c2x,c2y) p2@(p2x,p2y) : gs) (p1x,p1y) True + = bStr : toString' gs p2 True + where + i = c1x - p1x + j = c1y - p1y + p = c2x - p2x + q = c2y - p2y + + bStr = 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) toString' [] _ _ = []
src/Main.hs view
@@ -9,14 +9,15 @@ import Render import GCode - -data Options = Options { _svgfile :: String - , _cfgfile :: Maybe String - , _outfile :: Maybe String - , _dpi :: Int - , _mirrorYAxis :: Bool - } - + +data Options = Options { _svgfile :: String + , _cfgfile :: Maybe String + , _outfile :: Maybe String + , _dpi :: Int + , _mirrorYAxis :: Bool + , _generateBezier :: Bool + } + options :: Parser Options options = Options <$> argument str @@ -24,39 +25,43 @@ <> help "The SVG file to be converted" ) <*> (optional $ strOption ( long "flavor" - <> short 'f' - <> metavar "CONFIGFILE" + <> short 'f' + <> metavar "CONFIGFILE" <> help "Configuration of G-Code flavor" )) <*> (optional $ strOption ( long "output" <> short 'o' - <> metavar "OUTPUTFILE" + <> metavar "OUTPUTFILE" <> help "The output G-Code file (default is standard output)" )) <*> (option auto ( long "dpi" <> value 72 <> short 'd' - <> metavar "DPI" + <> metavar "DPI" <> help "Density of the SVG file (default is 72 DPI)" )) <*> (switch ( long "mirror-y-axis" <> short 'm' - <> help "Mirror Y axis" )) + <> help "Mirror Y axis to have the result in G-Code coordinate system" )) + <*> (switch + ( long "generate-bezier" + <> short 'b' + <> help "Generate bezier curves (G5) instead of arcs (G2,G3)" )) runWithOptions :: Options -> IO () -runWithOptions (Options svgFile mbCfg mbOut dpi mirrorYAxis) = - do +runWithOptions (Options svgFile mbCfg mbOut dpi mirrorYAxis generateBezier) = + do mbDoc <- SVG.loadSvgFile svgFile flavor <- maybe (return defaultFlavor) readFlavor mbCfg case mbDoc of - (Just doc) -> writer (toString flavor dpi $ renderDoc mirrorYAxis dpi doc) + (Just doc) -> writer (toString flavor dpi $ renderDoc mirrorYAxis generateBezier dpi doc) Nothing -> putStrLn "juicy-gcode: error during opening the SVG file" where writer = maybe putStrLn (\fn -> writeFile fn) mbOut - -toLines :: Text -> String -toLines t = unpack $ replace (pack ";") (pack "\n") t - + +toLines :: Text -> String +toLines t = unpack $ replace (pack ";") (pack "\n") t + readFlavor :: FilePath -> IO GCodeFlavor readFlavor cfgFile = do cfg <- C.load [C.Required cfgFile] @@ -65,12 +70,11 @@ toolon <- C.require cfg (pack "gcode.toolon") tooloff <- C.require cfg (pack "gcode.tooloff") return $ GCodeFlavor (toLines begin) (toLines end) (toLines toolon) (toLines tooloff) - + main :: IO () main = execParser opts >>= runWithOptions where opts = info (helper <*> options) ( fullDesc - <> progDesc "Convert SVGFILE to G-Code" - <> header "juicy-gcode - The SVG to G-Code converter" ) - + <> progDesc "Convert SVGFILE to G-Code" + <> header "juicy-gcode - The SVG to G-Code converter" )
src/Render.hs view
@@ -73,10 +73,9 @@ (w, h) = (documentSize dpi doc) -renderDoc :: Bool -> Int -> SVG.Document -> [GCodeOp] -renderDoc mirrorYAxis dpi doc = stage2 $ renderTrees - (docTransform mirrorYAxis dpi doc) - (SVG._elements doc) +renderDoc :: Bool -> Bool -> Int -> SVG.Document -> [GCodeOp] +renderDoc mirrorYAxis generateBezier dpi doc + = stage2 $ renderTrees (docTransform mirrorYAxis dpi doc) (SVG._elements doc) where -- TODO: make it tail recursive stage2 :: [DrawOp] -> [GCodeOp] @@ -85,7 +84,9 @@ convert [] _ = [] convert (DMoveTo p:ds) _ = GMoveTo p : convert ds (fromPoint p) convert (DLineTo p:ds) _ = GLineTo p : convert ds (fromPoint p) - convert (DBezierTo c1 c2 p2:ds) cp = concatMap biarc2garc biarcs ++ convert ds (fromPoint p2) + convert (DBezierTo c1 c2 p2:ds) cp + | generateBezier = [GBezierTo c1 c2 p2] ++ convert ds (fromPoint p2) + | otherwise = concatMap biarc2garc biarcs ++ convert ds (fromPoint p2) where biarcs = bezier2biarc (B.CubicBezier cp (fromPoint c1) (fromPoint c2) (fromPoint p2)) 5 1 biarc2garc biarc = [arc2garc (BA._a1 biarc), arc2garc (BA._a2 biarc)]
src/Types.hs view
@@ -8,18 +8,19 @@ type Point = (Double,Double) -- A point in the plane, absolute coordinates -- all of them are invariant under affine transformation -data DrawOp = DMoveTo Point +data DrawOp = DMoveTo Point | DLineTo Point -- End point - | DBezierTo Point Point Point -- Control point1, control point2, end point + | DBezierTo Point Point Point -- Control point1, control point2, end point deriving Show - + -- this is basically what GCode can do data GCodeOp = GMoveTo Point | GLineTo Point -- End point | GArcTo Point Point Bool -- Center point, end point, clockwise - deriving Show + | GBezierTo Point Point Point -- First and second control points, end point + deriving Show -- just to make it available everywhere if' :: Bool -> t -> t -> t -if' True t _ = t -if' False _ f = f +if' True t _ = t +if' False _ f = f