packages feed

juicy-gcode 0.1.0.8 → 0.1.0.9

raw patch · 8 files changed

+96/−66 lines, 8 files

Files

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