juicy-gcode 0.2.1.0 → 0.3.0.0
raw patch · 7 files changed
+114/−38 lines, 7 filesdep ~lensdep ~lineardep ~text
Dependency ranges changed: lens, linear, text
Files
- ChangeLog.md +7/−0
- README.md +17/−7
- juicy-gcode.cabal +2/−1
- src/Approx/BiArc.hs +11/−10
- src/Approx/Linear.hs +42/−0
- src/Main.hs +20/−11
- src/Render.hs +15/−9
ChangeLog.md view
@@ -1,5 +1,12 @@ # Revision history for juicy-gcode +## 0.3.0.0 -- 2022-12-14++- Add linear approximation algorithm+- New option: --curve-fitting+- Breaking change: --generate-bezier has been removed in favor of --curve-fitting+- Breaking change: --resolution has been renamed to --tolerance+ ## 0.2.1.0 -- 2022-11-26 - The approximation error is now calculated along the radial direction
README.md view
@@ -5,7 +5,10 @@ ## Overview -Juicy-gcode is a configurable SVG to G-code converter that approximates bezier curves with [biarcs](http://dlacko.org/blog/2016/10/19/approximating-bezier-curves-by-biarcs/) for maximal curve fitting.+Juicy-gcode is a configurable SVG to G-code converter that approximates bezier curves based on your needs:+- with [biarcs](http://dlacko.org/blog/2016/10/19/approximating-bezier-curves-by-biarcs/) for maximal curve fitting: bezier curves are approximated with arcs ([G2, G3 commands](https://marlinfw.org/meta/gcode/)), the resulting path is [G1 continues](https://skill-lync.com/blogs/introductions-to-surface-continuities-and-its-types)+- with linear approximation when arcs are not supported by your firmware: bezier curves are approximated with line segments ([G0, G1 commands](https://marlinfw.org/meta/gcode/)), the resulting path is [not smooth](https://skill-lync.com/blogs/introductions-to-surface-continuities-and-its-types), and the generated gcode is usually significantly larger +- with cubic bezier curves if your firmware supports it: some firmwares (e.g. [Marlin](https://marlinfw.org/docs/gcode/G005.html)) can handle bezier curves directly ([G5 command](https://marlinfw.org/meta/gcode/)), the result is [G2 continues](https://skill-lync.com/blogs/introductions-to-surface-continuities-and-its-types) ## Installation @@ -20,9 +23,11 @@ ## Usage +> :warning: **Breaking change**: Since version 0.3.0.0, `--generate-bezier` has been removed in favor of the more generic `--curve-fitting` parameter and `--resolution` has been renamed to `--tolerance`+ > :warning: **Breaking change**: Since version 0.2.0.1, default DPI is changed to 96 and the option to mirror the Y axis is removed (it is always mirrored now for correct result) -The easier way to use juicy-gcode is to simply provide an SVG file name. The generated GCode will be written to standard output.+The easier way to use juicy-gcode is to simply provide an SVG file name. The generated GCode will be written to standard output. The default approximation method is the biarcs based. ``` $ juicy-gcode SVGFILE@@ -37,17 +42,22 @@ Sometimes you want to overwrite some default settings. These are the * *--dpi* (default 96 DPI) [the resolution of the SVG file](https://developer.mozilla.org/en-US/docs/Web/CSS/resolution) that is used to determine the size of the SVG when it does not contain explicit units-* *--resolution* (default is 0.1 mm) the resolution of the generated GCode. Paths smaller than this are replaced by line segments instead of further approximated by biarcs+* *--tolerance* (default is 0.1 mm) maximum allowed derivation of the approximation curve ```-$ juicy-gcode SVGFILE --dpi 72 --resolution 0.01 +$ juicy-gcode SVGFILE --dpi 72 --tolerance 0.01 ``` -Some firmwares (e.g. [Marlin](https://marlinfw.org/docs/gcode/G005.html)) can handle bezier curves directly. In this case-you can command juicy-gcode not to approximate bezier-curves but emit them unchanged. +Curve fitting options (default is `biarc`): ```-$ juicy-gcode SVGFILE --generate-bezier+$ juicy-gcode SVGFILE --curve-fitting=biarc+```+```+$ juicy-gcode SVGFILE --curve-fitting=linear+```+```+$ juicy-gcode SVGFILE --curve-fitting=cubic-bezier ``` ## Configuration
juicy-gcode.cabal view
@@ -1,5 +1,5 @@ name: juicy-gcode-version: 0.2.1.0+version: 0.3.0.0 license: BSD3 license-file: LICENSE author: dlacko@@ -20,6 +20,7 @@ main-is: Main.hs other-modules: Approx.BiArc+ Approx.Linear Graphics.BiArc Graphics.CircularArc Graphics.CubicBezier
src/Approx/BiArc.hs view
@@ -23,6 +23,7 @@ maxiter = 10 -- Approximate a bezier curve with biarcs (Left) and line segments (Right)+-- M.A. Sabin, The use of piecewise forms for the numerical representation of shape (1977) bezier2biarcs :: B.CubicBezier -> Double -> [PathCommand]@@ -33,10 +34,10 @@ = [LineTo (toPoint (B._p2 mbezier))] -- Degenerate curve: p1 == c1, don't split | B._p1 mbezier == B._c1 mbezier- = approxOne mbezier+ = approxSpiral mbezier -- Degenerate curve: p2 == c2, don't split | B._p2 mbezier == B._c2 mbezier- = approxOne mbezier+ = approxSpiral mbezier -- Split by the inflexion points (if any) | otherwise = byInflection (B.inflectionPoints mbezier)@@ -44,11 +45,11 @@ order a b | b < a = (b, a) | otherwise = (a, b) - byInflection [t] = approxOne b1 ++ approxOne b2+ byInflection [t] = approxSpiral b1 ++ approxSpiral b2 where (b1, b2) = B.splitAt mbezier t - byInflection [t1, t2] = approxOne b1 ++ approxOne b2 ++ approxOne b3+ byInflection [t1, t2] = approxSpiral b1 ++ approxSpiral b2 ++ approxSpiral b3 where (it1, it2') = order t1 t2 @@ -59,11 +60,11 @@ (b1, toSplit) = B.splitAt mbezier it1 (b2, b3) = B.splitAt toSplit it2 - byInflection _ = approxOne mbezier+ byInflection _ = approxSpiral mbezier - -- Recursive step (TODO: tail recursive) - approxOne :: B.CubicBezier -> [PathCommand]- approxOne bezier+ -- Apprixmate one bezier spiral+ approxSpiral :: B.CubicBezier -> [PathCommand]+ approxSpiral bezier -- Approximate bezier length. if max length is smaller than resolution, do not approximate | B.maxArcLength bezier < resolution = [LineTo (toPoint (B._p2 bezier))]@@ -110,7 +111,7 @@ (maxDistanceAt, maxDistance) = calculateMaxDistance bezier biarc splitAndRecur t = let (b1, b2) = B.splitAt bezier t- in approxOne b1 ++ approxOne b2+ in approxSpiral b1 ++ approxSpiral b2 biarc2path :: BA.BiArc -> [PathCommand] biarc2path biarc = map@@ -125,7 +126,7 @@ CA._r (BA._a2 biarc) > 99999 || CA._r (BA._a2 biarc) < 0.001) -- Calculate the maximum approximation error along the radial direction--- D.J. Walton*, D.S. Meek, Approximation of a planar cubic Bezier spiral by circular arcs (1996)+-- D.J. Walton, D.S. Meek, Approximation of a planar cubic Bezier spiral by circular arcs (1996) calculateMaxDistance :: B.CubicBezier -> BA.BiArc -> (Double, Double) calculateMaxDistance bezier biarc -- This should not happenm but if, split the bezier at the middle
+ src/Approx/Linear.hs view
@@ -0,0 +1,42 @@+module Approx.Linear (+ linearApprox+) where++import qualified Graphics.CubicBezier as B+import qualified Graphics.Line as L+import Graphics.Path+import Graphics.Point++import Linear.Metric++-- Approximate a bezier curve with a series of points (line segments)+-- Weiyin Ma, Renjiang Zhang, Efficient Piecewise Linear Approximation of Bézier Curves with Improved Sharp Error Bound (2006)+linearApprox :: B.CubicBezier+ -> Double+ -> [PathCommand]+linearApprox mbezier resolution+ -- Edge case: all points on the same line -> it is a line + | L.isOnLine (L.fromPoints (B._p2 mbezier) (B._p1 mbezier)) (B._c1 mbezier) &&+ L.isOnLine (L.fromPoints (B._p2 mbezier) (B._p1 mbezier)) (B._c2 mbezier)+ = [LineTo (toPoint (B._p2 mbezier))]+ -- Just a regular bezier+ | otherwise+ = approx mbezier+ where+ approx :: B.CubicBezier -> [PathCommand]+ approx bezier+ | maxError > resolution+ = splitAndRecur 0.5+ | otherwise+ = [LineTo (toPoint b1), LineTo (toPoint b2), LineTo (toPoint b3)]++ where+ b1 = ((9 * B._p1 bezier) + (15 * B._c1 bezier) + (7 * B._c2 bezier) + B._p2 bezier) / 32+ b2 = (B._p1 bezier + (7 * B._c1 bezier) + (15 * B._c2 bezier) + (9 * B._p2 bezier)) / 32+ b3 = B._p2 bezier++ maxError = max (norm (B._p1 bezier - 2 * B._c1 bezier + B._c2 bezier))+ (norm (B._c1 bezier - 2 * B._c2 bezier + B._p2 bezier))++ splitAndRecur t = let (nb1, nb2) = B.splitAt bezier t+ in approx nb1 ++ approx nb2
src/Main.hs view
@@ -14,13 +14,14 @@ import Render import GCode+import Data.Maybe (fromMaybe) data Options = Options { _svgfile :: String , _cfgfile :: Maybe String , _outfile :: Maybe String , _dpi :: Int , _resolution :: Double- , _generateBezier :: Bool+ , _approximation :: Maybe Approximation } options :: Parser Options@@ -45,23 +46,31 @@ <> metavar "DPI" <> help "Used to determine the size of the SVG when it does not contain any units; dot per inch (default is 96)" ) <*> option auto- ( long "resolution"+ ( long "tolerance" <> value 0.1- <> short 'r'- <> metavar "RESOLUTION"- <> help "Shorter paths are replaced by line segments; mm (default is 0.1)" )- <*> switch- ( long "generate-bezier"- <> short 'b'- <> help "Generate bezier curves (G5) instead of arcs (G2,G3)" )+ <> short 't'+ <> metavar "TOLERANCE"+ <> help "Maximum derivation of the approximation curve" )+ <*> optional (option parseApproximation+ ( long "curve-fitting"+ <> short 'c'+ <> metavar "TYPE"+ <> help "Bezier curve approximation algorithm. TYPE can be linear, biarc (default) or cubic-bezier" )) +parseApproximation :: ReadM Approximation+parseApproximation = str >>= \s -> case s of+ "biarc" -> return BiArc+ "linear" -> return Linear+ "cubic-bezier" -> return CubicBezier+ _ -> readerError "Accepted bezier approximation types are 'biarc', 'linear', and 'cubic-bezier'."+ runWithOptions :: Options -> IO ()-runWithOptions (Options svgFile mbCfg mbOut dpi resolution generateBezier) =+runWithOptions (Options svgFile mbCfg mbOut dpi resolution mbApproximation) = do mbDoc <- SVG.loadSvgFile svgFile flavor <- maybe (return defaultFlavor) readFlavor mbCfg case mbDoc of- (Just doc) -> writer (toString flavor dpi $ renderDoc generateBezier dpi resolution doc)+ (Just doc) -> writer (toString flavor dpi $ renderDoc (fromMaybe BiArc mbApproximation) dpi resolution doc) Nothing -> putStrLn "juicy-gcode: error during opening the SVG file" where writer = maybe putStr writeFile mbOut
src/Render.hs view
@@ -1,4 +1,5 @@ module Render (+ Approximation(..), renderDoc ) where @@ -12,6 +13,7 @@ import Graphics.Point import Graphics.Transformation import Approx.BiArc+import Approx.Linear import SvgArcSegment import SVGExt @@ -82,8 +84,10 @@ (w, h) = documentSize dpi doc -renderDoc :: Bool -> Int -> Double -> SVG.Document -> [PathCommand]-renderDoc generateBezier dpi resolution doc+data Approximation = BiArc | CubicBezier | Linear++renderDoc :: Approximation -> Int -> Double -> SVG.Document -> [PathCommand]+renderDoc approximation dpi resolution doc = stage2 $ renderTrees (docTransform dpi doc) (SVG._elements doc) where pxresolution = fromIntegral dpi / 2.45 / 10 * resolution@@ -96,13 +100,15 @@ approximate (MoveTo p:ds) _ = MoveTo p : approximate ds (fromPoint p) approximate (LineTo p:ds) _ = LineTo p : approximate ds (fromPoint p) approximate (ArcTo p1 p2 d:ds) _ = ArcTo p1 p2 d : approximate ds (fromPoint p2)- approximate (BezierTo c1 c2 p2:ds) cp- | generateBezier- = BezierTo c1 c2 p2 : approximate ds (fromPoint p2)- | otherwise- = bezier2biarcs- (B.CubicBezier cp (fromPoint c1) (fromPoint c2) (fromPoint p2)) pxresolution- ++ approximate ds (fromPoint p2)+ approximate (BezierTo c1 c2 p2:ds) cp =+ case approximation of+ BiArc -> bezier2biarcs+ (B.CubicBezier cp (fromPoint c1) (fromPoint c2) (fromPoint p2)) pxresolution+ ++ approximate ds (fromPoint p2)+ CubicBezier -> BezierTo c1 c2 p2 : approximate ds (fromPoint p2)+ Linear -> linearApprox+ (B.CubicBezier cp (fromPoint c1) (fromPoint c2) (fromPoint p2)) pxresolution+ ++ approximate ds (fromPoint p2) renderPathCommands :: Point -> Point -> Maybe Point -> [SVG.PathCommand] -> [PathCommand] renderPathCommands _ currentp _ (SVG.MoveTo origin (p:ps):ds)