packages feed

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 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)