Draw sketches from SVG, using  Line-us robot and .NET Core Console application
Tags: csharp, netcore, iot
Published: 2019-10-28 10:04:01

---
title: Draw sketches from SVG, using  Line-us robot and .NET Core Console application
published: true
description: In this fun project, we parse an SVG vector image into GCode and transmit this to a  Line-us robot.
tags: csharp,
  .net_core,
  iot
cover_image: https://blogs.siliconorchid.com/images/coding-inspiration/sketch-with-line-us/linus_dev_to.jpg
canonical_url: https://blogs.siliconorchid.com/post/coding-inspiration/sketch-with-line-us/
---

This article was originally published at [blogs.siliconorchid.com](https://blogs.siliconorchid.com/post/coding-inspiration/sketch-with-line-us/) on 26-Jul-2019



Over the past few months, I've largely been blogging about how to use various cloud services. This is typically the stuff that you could be using in a commercial setting.

Programming is something that can be a hobby for some people, not just a career skill.  For this article, I thought we should do something a little different and just play with code for fun.  I've chosen to look at something that is  [IoT](https://en.wikipedia.org/wiki/Internet_of_things) related and produces "physically visible" results.

We're going to experiment with a small piece of kit called the [**Line-us**](https://www.line-us.com/) which originated from a [2017 Kickstarter campaign](https://www.kickstarter.com/projects/line-us/line-us-the-little-robot-drawing-arm).    

*Line-us* is a device that holds a pen on the end of a small robotic arm and draws doodle-style sketches.  The device has built in Wifi and internet-connectivity, making it easy to connect to.

![animated image showing line-us drawing robot in action](https://blogs.siliconorchid.com/images/coding-inspiration/sketch-with-line-us/line-us-in-motion.gif#shadow)

<br/>
<hr/>

## What will we be doing?

For this project, we're going to write a [.NET Core Console App](https://docs.microsoft.com/en-us/dotnet/core/tutorials/with-visual-studio) that converts an [SVG vector image](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) into [G-Code](https://en.wikipedia.org/wiki/G-code).   

G-Code is a simple instruction language typically used by industrial [CNC machines](https://en.wikipedia.org/wiki/Numerical_control).  

We'll transmit the G-Code instructions, using [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol) networking, to the *Line-us* and sit back as a drawing appears on a piece of paper!


<br/>
<hr/>

## Prerequisites

* A *Line-us* robot

* There will be an assumption that you have familiarity working with:

    * [C#](https://docs.microsoft.com/en-us/dotnet/csharp/getting-started/introduction-to-the-csharp-language-and-the-net-framework)
    * [.NET Core Console projects](https://docs.microsoft.com/en-us/dotnet/core/tutorials/with-visual-studio)
    * [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection).
     

* This project uses .NET Core 2.x, so you'll need to have installed the [.NET Core 2.x  SDK](https://dotnet.microsoft.com/download), as appropriate to your platform.   .NET Core is cross-platform, which means this project will work on Windows, Mac and Linux.

* You'll need something to edit and build your project.  This demo was primarily created using .NET Core 2.2 on a Windows 10 system using [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) Community edition. There is no reason why you cannot use other platforms, code editors and [CLI](https://docs.microsoft.com/en-us/dotnet/core/tutorials/using-with-xplat-cli) tools - my recommendation would be to use [Visual Studio Code](https://code.visualstudio.com/download).
<br/>

*  You'll need to clone the sample code that accompanies this article from here : [https://github.com/SiliconOrchid/DrawWithLineUs](https://github.com/SiliconOrchid/DrawWithLineUs)



<br/>
<hr/>

## The road from photo to robot-drawn sketch

The *Line-us* is not a printer.  It is a line-drawing machine, more akin to a [plotter](https://en.wikipedia.org/wiki/Plotter).   Everything will be monochromatic and based purely on strokes of a pen on a page.

Your source material will still most likely be a bitmapped image, but there are a number of transformations that this image will need to undergo, before it can be sketched on paper by the *Line-us*.   

Let's overview the steps we need to take:

* **First, an image needs to be processed into an approximation of a black-and-white line drawing**.  

    You can use an online utility of a photo-editing program for this step.

    * A quick search for "online photo to line-art" will return a wide selection of free online conversion tools.   I tried both [RapidResizer Stencil Maker](https://online.rapidresizer.com/photograph-to-pattern.php)  and  [Snaptouch Sketch](http://www.snapstouch.com/Sketch.aspx) with good results.

    * I also used Adobe Photoshop to process a photo.  If using a photo-editor, I suggest: 
        * start by adjusting the image contrast heavily
        * then apply an "edge-detection" filter.

![photo of a lovebird alongside an edge-detected version](https://blogs.siliconorchid.com/images/coding-inspiration/sketch-with-line-us/lovebird-photo-to-edges.jpg)

<br/>
<br/>

* **Next, the processed bitmap needs to be converted into an [SVG vector image](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)**.

    I used the open-source utility [*Potrace*](http://potrace.sourceforge.net/).  Be mindful that *Potrace* only accepts images in the .BMP file format, so you will need to export your modified artwork into this format.

    * Using *Potrace*, I used the command `potrace.exe -s lovebird_processed.bmp`, where the `-s` switch is used to declare that you want to output an .SVG file.

![iedge-detected image of a lovebird alongside an SVG version](https://blogs.siliconorchid.com/images/coding-inspiration/sketch-with-line-us/lovebird-edges-to-vector.jpg)

<br/>
<br/>

* **Finally, the SVG needs to be converted into G-Code.**  
    Our project will not create a solution that addresses the first two items in the above list - but we will write code to read an SVG file.

    The following image is a photo of the actual hardcopy that is produced by the *Line-us*!

![lovebird sketch drawn by line-us robot](https://blogs.siliconorchid.com/images/coding-inspiration/sketch-with-line-us/lovebird-actual-sketch.jpg)



<br/>
<hr/>

## The  Line-us coordinate system

*Line-us* has a small robotic arm, which holds a pen at its end.   It can draw on a sheet of paper which is clamped immediately adjacent to the body of the device.    

The robotic arm can position the pen in 2-axes of movement (X and Y coordinates).  The pen can also be raised or lowered (the Z coordinate) by way of a cam mechanism that moves the body of the unit up and down.

The arm only has a limited range of reach, meaning that although the device can draw in a sweeping arc around the body, the available "rectangular" area is limited to something about the size of a postcard.

The coordinate system originates at the fulcrum of the robotic arm and covers the following range of values :-

* X-axis:  0 to 2000 (with only 650 to 1775 in the main drawable area)
* Y-axis: -1600 to 1600 (with only -1000 to 1000 in the main drawable area)
* Z-axis 0 to 1000 (with 0 being pen-fully-down and 1000 being pen-fully-up

According to the documentation, each 100 units corresponds to 5mm of travel.

The following diagram has been duplicated from the [*Line-us* documentation on GitHub](https://github.com/ Line-us/ Line-us-Programming/blob/master/Documentation/LineUsDrawingArea.pdf) and clearly illustrates the range of motion and the drawable area:-

![image showing line-us coordinate system and drawable area](https://blogs.siliconorchid.com/images/coding-inspiration/sketch-with-line-us/line-us-drawable-area.png#shadow)

<br/>
<hr/>


## What is G-Code?

[G-Code](https://en.wikipedia.org/wiki/G-code) is typically used to instruct [CNC machines](https://en.wikipedia.org/wiki/Numerical_control) (computer-controlled lathes and cutters, etc).

G-Code started life in the 1950's, became standardised in the 1970's and has more recently evolved to support more complex logic, that is required by increasingly advanced industrial machines.  

The *Line-us* however, is a *relatively* straightforward device and does not have a complex set of drawing features or commands.     

The *Line-us* firmware only supports a small set of G-Code commands, which you can find here:

 * [*Line-us* G-Code Spec on GitHub]( https://github.com/ Line-us/ Line-us-Programming/blob/master/Documentation/GCodeSpec.pdf)

The "language" of the commands is straightforward - for example, if we wanted to reset the pen to a "home" position, we need to send the device nothing other than the command "G28".

In this project, we're going to use a subset of just two commands!  These are:-

* `G00` - **Rapid Repositioning** - Moves the pen as quickly as possible to new coordinates.   In our code, we use this to reposition the pen after a line has been drawn, ready to start drawing the next one.   Example `G00 X1000 Y1000 Z1000`
* `G01` - **Linear Interpolation** - This is what we use to draw a line between two points.   Example `G00 X1200 Y1200 Z0`

A limitation to be aware of, is that the *Line-us* provides no native support for drawing true curves (usually described as [beziers](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)).    The *Line-us* can only draw straight lines, directly from one point to another.   

In practice however, this doesn't seem to pose a problem for our project, as many SVG source-images will be created from relatively small shapes, meaning that the lack of true curves isn't really apparent.   Also, remember that this is a fun toy for drawing "wonky" pictures - it's not meant to be perfect.

<br/>
<hr/>

## Manually test Line-us

Before we get stuck into any coding, we should ensure that our *Line-us* is working correctly and that we can communicate with it from your computer.   

We'll check this by performing a manual test, which will verify that you have a working network connection and that our *Line-us* is responding to commands as expected.

We're not going to cover the setup and configuration of the *Line-us* device itself in this article, as there is already ample support provided by the [manufacturers documentation](https://www.line-us.com/help.html) and a  [community forum](https://forum.line-us.com/).  For this article, we will assume that we have already successfully added the device to our WiFi network.     

We **need to know what the local IP address of the *Line-us* is**, so if you haven't already, connect to your network router and make a note of the IP that has been assigned.

<br/>
<hr/>

### Introducing and setting-up Telnet

We're going to use [**Telnet**](https://en.wikipedia.org/wiki/Telnet) to communicate with the *Line-us* during our manual test.   

Younger readers, in particular, may not be familiar with *Telnet*, so briefly, *Telnet* is one of the oldest protocols on the Internet.  It is used to connect to a remote system, over TCP, and provides a bi-directional text-based interface.   You tend not to find it used much these days, as it is insecure and has been replaced with [SSH](https://en.wikipedia.org/wiki/Secure_Shell)  

To use *Telnet*, you will need a *Telnet client* installed on your system.

* **Telnet for Windows users**
    A Telnet client is included with Windows, but nowadays is disabled by default.   To use it, we'll need to re-enable it.    
    * In the Windows Start search bar, type "turn win" and select the "Turn Windows Features On or Off" option.
    * In the "Windows Features" window that opens, scroll down the list and enable the "Telnet Client" option.
    * Windows will install the feature and Telnet will now be available on the command prompt.

<br/>

* **Telnet for Mac users**
    The *Telnet* Client has been removed from recent versions of MacOs.
    * Follow the instructions in [this guide to install Telnet on a Mac](http://osxdaily.com/2018/07/18/get-telnet-macos/).

> When I first encountered *Telnet* as a teenager, I would use it to connect to "MUDs" (multi-user dungeons) which were online multi-user text-based adventure games!

<br/>
<hr/>

### Connect to the  Line-us

With the *Telnet* client installed, open a command prompt and enter the following command.  You will need to substitute the local IP address in the example, with the one you identified from your own router:

```
Telnet 192.168.1.200 1337
```

The number `1337` is the port number that *Line-us* uses by default and must be included.

If the connection is successful, you will hear/see the robot twitch its arm and a message similar to the following will be returned to the console:

```
hello VERSION:"3.0.0 Feb 10 2019 10:25:48" NAME:line-us SERIAL:155xxxxx
```

At this point, we are successfully connected to the *Line-us* in a two-way conversation.  The *Line-us* has accepted our connection, responded with a "hello" and is now awaiting for further instructions.

Start by sending some test commands.  Simply enter the following, ensuring that you use capital letters were shown (and press enter):-

* `G28` - you should see the arm recenter to the home position.  The pen will also be raised.
    * With the *Line-us* raised, this is a perfect opportunity to correctly set the pen height.   The *Line-us* [instructions](https://www.line-us.com/help.html) recommend placing a UK £1 coin or two stacked US Dimes (which for the benefit of readers in other countries, is approx 3mm thick) underneath the tip of the pen and then tightening the clamp in this position.

* `G01 X1600 Y-800 Z1000` - this should move the arm noticeably.
 
If you can see an obvious result, then we're all set to progress.


<br/>
<hr/>

## Quick start to using the code

Aside from a small configuration edit, you should be able to clone the project from GitHub, add the  Line-us to your network and just run the code.   

Before running the project,  open the file `....\DrawWithLineUs\Config\ProgramConfig.cs` and update the configuration values.

* You will need to change the path to a sample SVG image.

* You will need to change the IP address, to whatever your router has assigned to your own  Line-us.

* If you want to actually send commands to the  Line-us, you should set `Testmode` to be `false`.  Alternatively, if you just want to check that the code works and emits results to the console window,  leave it set to `true`. 





<br/>
<hr/>

## Overview of the solution

* You should clone the solution from [https://github.com/SiliconOrchid/DrawWithLineUs](https://github.com/SiliconOrchid/DrawWithLineUs) and refer to the code as we go along.

Our C# .NET Core solution is primarily a console app called `DrawWithLineUs`.

The app can be broadly categorised into the following areas:

* **Configuration/Enumerations** - [`static`](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members) classes that contain `const` fields, for items such as the "drawable canvas area".
* **Models** - classes that are used to model things such as the list of coordinates.
* **Services** - classes that contain the main program logic.   Exploring this area further, there are four services that provide the following functionality:

    * **SVGService** - has the job of extracting data from an SVG file and turning it into a list of coordinates.
    * **GeometryService** - is concerned with activities such as determining the extremities of points within an image and calculating an appropriate scaling, so that our image can be resized into the drawable area. 
    * **GCodeService** - responsible for converting a list of C# types into appropriate G-Code strings.
    * **CommunicationService** - this class is responsible for handling the network communication and transmitting bytes back and forth between your app and the *Line-us*.



<br/>
<hr/>

### Designed to use Dependency Injection

The application uses  .NET Core's built-in  dependency injection.   

Services are written so that they implement [interfaces](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/interfaces/).  Using the DI framework, the services can easily be added to a class via its constructor.  If you are unfamiliar with this type of code, you can [learn about Dependency Injection here](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2).

* A majority of the code in `Program.cs` is related to the configuration of the DI system.
* Most of the code that relates to the app logic, can be found in the file `ConsoleApplication.cs`.

Code written in this way, lends itself better to unit testing.  The source code comes supplied with a small set of unit tests that can be found in `....\DrawWithLineUs\DrawWithLineUs.Console.Test\`

<br/>
<hr/>

## Reading SVG data

[Scalable Vector Graphics (SVG)](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) are an open standard which was created by the [W3C](https://en.wikipedia.org/wiki/World_Wide_Web_Consortium) and dates back to 1999.   Unlike other image formats which are [binary files](https://en.wikipedia.org/wiki/Binary_file), an SVG is nothing more than a text file containing [XML](https://en.wikipedia.org/wiki/XML).    This means they can be readily viewed and edited in any text editor and can easily be read and parsed using .NET libraries.

The SVG description language is extensive and beyond the scope of this article.  However, for our purposes, we can focus on a relatively small subset.

> There is a huge caveat, regarding the code in this solution, that should be made clear.    

> This article is intended to be an exploration of the *Line-us*, so we are not attempting to write a fully-featured SVG parser.

> Therefore, we are **assuming that we will only be using SVG images that have been created using [Path elements](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths)**. I have only tested using SVG files that are the output from a *[Potrace](https://en.wikipedia.org/wiki/Potrace)* conversion (which seems to generate images using only Path elements).      

> This means that you cannot expect to get results from all SVG files.   This is because an SVG file can be created using many different elements, including [basic shapes](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Basic_Shapes) such as  Rectangles, Circles, Ellipses, etc.

> However, there is absolutely no reason why this project couldn't be [forked](https://help.github.com/en/articles/fork-a-repo) and expanded upon to support SVG more fully! You could even submit a PR to my repo!

<br/>

The following code snippet shows the first couple of lines of an example SVG file.    

```xml
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="850.000000pt" height="650.000000pt" viewBox="0 0 850.000000 650.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,650.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M6627 5334 c-13 -13 -7 -23 23 -39 40 -21 50 -19 21 4 l-25 19 30 -5
c20 -4 26 -2 19 5 -13 14 -59 25 -68 16z"/>
<path d="M5360 5695 c0 -9 -4 -13 -10 -10 -5 3 -10 1 -10 -4 0 -6 6 -11 14
-11 17 0 29 27 16 35 -6 4 -10 -1 -10 -10z"/>
<path d="M5440 5665 c-6 -8 -10 -20 -7 -27 4 -8 8 -4 13 10 5 18 9 20 15 10 7
-10 9 -9 9 5 0 21 -13 22 -30 2z"/>
<path d="M6478 5673 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
<path d="M5571 5653 c0 -12 2 -13 6 -5 4 10 8 10 19 1 18 -15 18 -29 -1 -29
-23 0 -18 -16 10 -28 20 -9 23 -9 18 4 -4 10 0 12 11 8 9 -3 2 10 -16 30 -35
40 -48 45 -47 19z"/>

[… truncated for brevity …]
```

For our purposes, we can disregard entirely  the first few lines and concentrate purely on the `<path/>` nodes.   Specifically, we are only interested in the `d` (data) attribute, so our code will need to be able to parse and extract this information.



<br/>

<hr/>

### Understanding an SVG Path

[Mozilla provides an excellent guide to reading SVG Paths](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths)

![limage show breakdown of SVG path element](https://blogs.siliconorchid.com/images/coding-inspiration/sketch-with-line-us/svg-breakdown.png#shadow)

Next, let's concentrate on the structure of the actual data in the `<path/>`.    On first inspection, it looks to be nothing more than a long list of numbers.   

* These numbers are always found in pairs representing X and Y values.


If you look more closely, you may spot that these numbers are interspersed with the occasional letter.   These letters mean:

* **M** - Start of the path
* **l** - Start a sequence of **lines** : Unless changed, all subsequent number-pairs will relate to lines.
* **c** - Start a sequence of **curves** : Unless changed, all subsequent number-pairs will relate to curves.
* **z** - End of a path

The first pair of numbers immediately following an "M" are the **absolute** X/Y starting coordinates of a path.  

All subsequent number-pairs are the **delta** (the change) from the previous position and not an *absolute* coordinate.   In order to derive *absolute* coordinates, we would need to track the change(s) all the way back to the starting pair.   

Recalculating these *delta* points as *absolute* coordinates will be one of the requirements of our code.

* **Lines** comprise of a single pair of numbers.  The number-pair being inspected is the ending-point of the line.   The starting-point will be the ending-point of the previous step.

* **Curves** comprise of three pairs of numbers, which describe the shape of a [bezier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).  The first two pairs relate to control-points that influence the shape of the curve, whilst the third pair is the ending-point.

    In the image below, point A represents the starting point of the curve (the previous step), point B represents the first pair of digits, point C the second pair of digits and finally, point D represents the third and final pair (the end point).
 
![image showing simple bezier curve](https://blogs.siliconorchid.com/images/coding-inspiration/sketch-with-line-us/bezier-curve.jpg#shadow)

<br/>
<hr/>

###  Line-us cannot draw curves

**Importantly**,  Line-us does not understand curves and only works with straight lines.

This means that when we encounter a path element that describes a curve, we simply have to ignore the first two pairs of values and simply extract the end-point.   For our solution, a curve will effectively be the same thing as a straight-line.

This inevitably means that our image will not be the same as the source SVG and may appear jagged.  However, in practice, this wasn't really noticeable because most of the paths in the test images were so small that even straight-lines held up acceptably well to scrutiny.



<br/>
<hr/>



## Let's look at the code!

### Program entry point

As previously mentioned, the file `Program.cs` mostly contains the setup for Dependency Injection, so instead you should look to the file `ConsoleApplication.cs` for the entry point into the program.

Within this file, you will find a clearly named set of method calls.  These address each of the four main steps that our program needs to perform:-

```csharp
public void Run()
{
    GetCoordinatesFromSVG(); // extracts a list of coordinates from the source file
    ApplyGeometry(); // rescales the coordinates, to fit within the drawable area
    GenerateGCode(); // produces a list of G-Code from the list of coordinates
    Draw(); // Sends the G-Code to the  Line-us
}
``` 


<br/>
<hr/>

### SvgService

The SvGService is probably the most complex part of the project, so don't worry if you find it a bit confusing!

The code is located here:  `....\DrawWithLineUs\DrawWithLineUs\Service\SvgService.cs`

The SvgService is responsible for:

* Opening the SVG file specified in configuration and reading the XML content, by using `System.Xml.XmlReader`
* Locating only the `path` XML nodes from within the document and returning a string - we ignore anything else in the XML.
* For each path, tracking and extracting the appropriate coordinates that comprise the path.


Extracting XML data is a relatively straightforward problem to address, as the .NET framework has a library that does all of the heavy-lifting.   The code looks like this:-

```csharp
using (XmlReader reader = XmlReader.Create($"{PathToSourceSVG}", settings))
{
    while (reader.Read())
    {
        switch (reader.Name.ToString())
        {
            case "path":
                listPathNodes.Add(reader.GetAttribute("d"));
                break;
        }
    }
}
```

<br/>
<br/>

Extracting the appropriate coordinates that comprise a path is not entirely straightforward, so let's look at this code closely.

* Our program is initially presented with a long, single string that represents the "raw" data that has been extracted from the XML source.   
* The first thing we need to do, is to chop this up into a [List<string>](https://www.dotnetperls.com/list) of individual words, using the [.NET String.Split()](https://docs.microsoft.com/en-us/dotnet/csharp/how-to/parse-strings-using-split) method. 

> As a reminder, in an earlier section, we looked at how each SVG Path is made up of lines and curves.  We looked at how the data description uses the characters `l` and `c` as delimiters.  We learned that *lines* comprise of a single pair of numbers, whilst a *curve* comprises three pairs of numbers.  Finally we learned that we can't actually take advantage of curves, so we are only interested in extracting the third pair of digits.

The XML data is not usable to us in its original form. We need to model the data by using C# objects that store a list of paths, each of which contains a nested-list of *absolute* coordinates (not just the *delta*, as found in the XML).   These coordinates will eventually be used to drive the *Line-us* robot arm.   

* We use the model `...\DrawWithLineUs\Model\CoordinateStructure.cs` as a container for this information.     Rather than store the X/Y coordinates into separate `int` fields, we can use .NET's `System.Drawing.Point` struct, as it is intended for this kind of use.

```csharp
public class CoordinateStructure
{
    public List<Point> ListPoints { get; set; }
    public List<PointF> ListRescaledPoints { get; set; } //using PointF for better precision, having been scaled

    public CoordinateStructure()
    {
        ListPoints = new List<Point>();
        ListRescaledPoints = new List<PointF>();
    }
}
```

<br/>

We then need to parse through the list of *words*.    We use the term *words* here deliberately, because they are not necessarily just numbers.  For example, a word may include a letter like this:  `l-25`, which conveys the double meaning that  we are starting a new sequence of *lines* and that the first X value is "negative 25".

Your first idea may be that, because numbers always appear as pairs of X/Y values, that we just need to iterate over the list of words, incrementing our index value of 2 on each pass of the loop.   However, this doesn't work, because *curves* require us to skip the index forward by 6.    

The increasing complication is that a *path* can contain a mixture of both *lines* and *curves* (we'll call this a "variant"), but it doesn't tell us which *variant* we are looking at each time - only when a change happens.  This means that we also need to track what the current *variant* is, as we iterate.

* We use the variable `currentSvgPathVariantEnum` to persist the *variant* that is currently in effect.
    * We have used the [enumeration](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum) `SvgPathVariantEnum` to make it clearer, in the code, which type of *variant* we are working with.
* We use a `while` loop, which ultimately keeps iterating forward over the words, until we reach the end of the list.
* Within the `while` loop, we use the method `GetCurrentPathVariant(...)` to test the current word to see if it contains the characters `i` or `c`.   If either of these are found, we update the variable  `currentSvgPathVariantEnum`.
* Still within the `while` loop, we use the method `ExtractCoordinate(...)` to extract the appropriate set of delta numbers.   
    * The index used to extract the number will vary, depending on whether we are looking at a line or a curve.  We use a [Regular Expression](https://en.wikipedia.org/wiki/Regular_expression) to strip out any letters.  This code looks like the snippet just below:-
    * In addition to extracting the delta numbers, we pass in the previous *absolute* coordinates, combine this with the new *delta* values, and ultimately return new *absolute* coordinates as a `Point` struct.

```csharp
string coordX;
string coordY;
switch (currentSvgPathVariantEnum)
{
    case SvgPathVariantEnum.Line:
        coordX = Regex.Replace(listWords[i], "[^0-9.+-]", "");
        coordY = Regex.Replace(listWords[i + 1], "[^0-9.+-]", "");
        break;
    case SvgPathVariantEnum.Curve:
        coordX = Regex.Replace(listWords[i + 4], "[^0-9.+-]", "");
        coordY = Regex.Replace(listWords[i + 5], "[^0-9.+-]", "");
        break;

...	
```
 
<br/>


* Finally within the `while` loop, we use the method `IncrementCurrentPathIndex(...)` to determine the correct number by which we should increment the loop's index (i.e. either 2 or 6).

The service ultimately returns a list of the container objects : `List<CoordinateStructure>`.

<br/>
<br/>

The partial code listing of the main method looks like this ([check the source on GitHub](https://github.com/SiliconOrchid/DrawWithLineUs/blob/master/DrawWithLineUs/Service/SvgService.cs) for the class in its entirety):

```csharp
public List<CoordinateStructure> ExtractCoordinates(List<string> listPathNodes)
{
    // an array of arrays (which contain point coordinates, which are constructed from the list of offsets in the SVG
    List<CoordinateStructure> listCoordinateStructures = new List<CoordinateStructure>();

    Console.WriteLine($"Parsing nodes...");
    foreach (var pathNode in listPathNodes)
    {
        var coordinateStructure = new CoordinateStructure();

        var listWords = pathNode.Split(" ");

        // extract start coordinates
        int coordStartX = int.Parse(Regex.Replace(listWords[0], "[^0-9.+-]", ""));
        int coordStartY = int.Parse(Regex.Replace(listWords[1], "[^0-9.+-]", ""));
        coordinateStructure.ListPoints.Add(new Point(coordStartX, coordStartY));


        int i = 2; // don't start at very beginning, skip over the first pair of values as these are always the "starting position"
        SvgPathVariantEnum currentSvgPathVariantEnum = SvgPathVariantEnum.Unset;

        while (i < listWords.Length)
        {
            //check to see if the "variant" has changed (is this a line or curve, or are we carrying on from the last previously set variant?)
            var newPathVariant = GetCurrentPathVariant(listWords, i);
            if (newPathVariant != SvgPathVariantEnum.Unset)
            {
                currentSvgPathVariantEnum = newPathVariant;
            }

            // depending on the "variant" extract the appropriate coordinates and add this to "coordinateStructure.ListPoints" collection. 
            Point previousPoint = coordinateStructure.ListPoints[coordinateStructure.ListPoints.Count - 1];

            Point? nextPoint = ExtractCoordinate(previousPoint, listWords, i, currentSvgPathVariantEnum);

            if (nextPoint is null)
            {
                // skip over, as point was a "MoveTo" command.
            }
            else
            {
                coordinateStructure.ListPoints.Add((Point)nextPoint);
            }
 
            //depending on the current variant, increment the current index appropriately (either by 2 for a line, or 6 for a curve)
            i = i + IncrementCurrentPathIndex(currentSvgPathVariantEnum);
        }

        listCoordinateStructures.Add(coordinateStructure);
    }
    return listCoordinateStructures;
}
```


<br/>
<hr/>

### GeometryService

The  Line-us only accept coordinates within a certain range of values (refer to the diagram earlier in the article).  

The SvgService however, may return coordinates with large values that exceed the bounds of the drawable area.

We need to be able to rescale the image so that everything fits correctly - this is where the GeometryService comes in.

This service is responsible for:

* Iterating through each and every single point returned from the SvgService and determining the coordinate extremities (in your mind, you can picture this as a rectangle which goes around the very edge of your image).   
    * We use the class `BoundingBox` to model this result.   This class has methods to return the height and width of this box.
* We compare the width and height of this box against the height and width of the  Line-us available drawing area.   From this, we can determine an appropriate scaling ratio.
* We then reiterate through all points a second time, apply the scaling factor to resize/reposition them.
    * The recalculated coordinates are stored back into the instance of the model `CoordinateStructure`, except that this time they are store into `ListRescaledPoints`.
    * The fields inside `ListRescaledPoints` are of type `PointF` (for a floating-point point, rather than integer point).    This is so that we don't accidentally lose precision, should we end up scaling a very differently sized image.   We want to preserve the accuracy of the new points as much as possible, up until the point that we export GCode. 



<br/>
<hr/>

### GCodeService

This service is responsible for:

* Iterating the list of rescaled coordinates (having just been rescaled by the GeometryService).  The data is passed to this service via a populated instance of the `CoordinateStructure` class.
* Producing a list of appropriate GCode commands that will be later sent to the device.

By the time we come to use the GCodeService, we've done all the hard work of preparing lists of data that represent **multiple paths** each containing **multiple points**.

All that is left for us to do, is pay attention to what the pen of the robot is doing, by lifting it off the page and moving it to the start of each new path.

* When we start drawing a new path, we reposition the pen to the new starting coordinates.   
* Lower the pen ready to start drawing. 
* Move it to each point in sequence.
* We then raise the pen at the end of the sequence, ready for the move to the start of the next path.

Because our rescaled coordinates have been stored as a `PointF` (a floating-point precision point), we need to convert back to an `int` when producing our GCode.

<br/>

#### Tip:  try and use enums or static classes to describe fixed values

As a developer, something you should avoid the [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern) of using [magic numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)) or hardcoding of strings that,  although make sense at the time you write the code, are not apparent to other people (or indeed yourself in the future, when you forget the details!).   

As such, you should aim to make the meaning of your code as clear as possible by using easily-understood words in code.   You can usually use [enumerations](https://www.dotnetperls.com/enum) or [static classes](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members) to help you here.

So in the case of our GCode, we *could* easily have written the snippet above so it looked like this...

```csharp
listGCodes.Add($"G01 x{(int)step.X} y{(int)step.Y} z0\n");
```

… but how would someone who is new to the project, who is reading your code, know that "G01" means "Linear Interpolation" - or that the Z axis of zero means "pen down".   To get meaning, they would have to stop what they're doing and cross-reference with the documentation.

Instead, we can improve the meaning in our code like this:-

```csharp
public static class GCodes
{
    public const string RapidReposition = "g00";
    public const string LinearInterpolation = "g01";
}

public static class Pen
{
    public const int PenUpIndex = 1000;
    public const int PenDownIndex = 1;
}
```

With awareness of the above, we can instead write our code that produces GCode for the path like this:-

```csharp
private static void SequencePath(List<string> listGCodes, CoordinateStructure sequence)
{
    foreach (var step in sequence.ListRescaledPoints)
    {
        listGCodes.Add($"{GCodes.LinearInterpolation} x{(int)step.X} y{(int)step.Y} z{Pen.PenDownIndex}\n");
    }
}
```
> If you are not entirely familiar with some of the syntax in the example above, we are using C#6 [String Interpolation](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated) to build our string (where previously, you may have used `String.Format()` to achieve the same outcome).

<br/>
<hr/>

### CommunicationService

This service is responsible for:

* Establishing a [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol) connection between your computer and the  Line-us, by using the .NET library `System.Net.Sockets`.
* Receiving the initial "hello" back from the  Line-us, signalling the device is ready to start a two-way communication.
* Converting GCode into a [byte-array](https://stackoverflow.com/questions/4019837/what-do-we-mean-by-byte-array) and sending the command.

The class in its entirety looks like this:-
```csharp
public class CommunicationService : ICommunicationService
{
    public void ConnectToLineUs(out TcpClient client, out NetworkStream stream, string lineusIP, int lineusport)
    {
        IPAddress lineusipaddress = System.Net.IPAddress.Parse(lineusIP);
        client = new TcpClient();
        client.Connect(new IPEndPoint(lineusipaddress, lineusport));
        stream = client.GetStream();
        SayHello(stream);
    }

    private void SayHello(NetworkStream stream)
    {
        Byte[] data = new Byte[256];
        int bytes = stream.Read(data, 0, data.Length);
        String responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
        Console.WriteLine($"Received: {responseData}");
    }

    public void Transmit(NetworkStream stream, string instruction)
    {
        //Console.WriteLine($"Sent: {instruction}");
        byte[] data = System.Text.Encoding.ASCII.GetBytes(instruction);
        stream.Write(data, 0, data.Length);

        data = new Byte[256];

        int bytes = stream.Read(data, 0, data.Length);
        string responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
        Console.WriteLine($"Received: {responseData}");
    }
}
```


<br/>
<hr/>

## Wrapping up

 Line-us is a nice little machine, thoughtfully designed and easy to use.   It sidesteps much of the complications that come with "rolling your own" hardware systems.

It's very much something I could envisage being used as a way to get kids interested in programming and for me, mildly echoes the [LOGO](https://en.wikipedia.org/wiki/Logo_(programming_language)) programming that I was shown in school, a generation ago.

I'm hoping this article can re-assert that programming can be just for fun and hopefully inspire folks to try something different.

If you've discovered this article through a search, coming as someone wanting to learn about Line-us, then hopefully this has been useful to you as an example of C# code.

<br/>
<hr/>

## Where to next?

Check out this [things-to-do](https://www.line-us.com/things-to-do) link by line-us.com  for inspiration.

Here are some ideas for you to improve this project:

* Extend the SVG parser to support more shapes - not just *paths*.
* Extend the code to support multi-colour painting - check out the following image (from line-us press images):

![animated image showing line-us drawing robot in action](https://blogs.siliconorchid.com/images/coding-inspiration/sketch-with-line-us/line-us-colour-painting.jpg#shadow)



<br/>
<hr/>
## Further reading


* [ Line-us Documentation ](https://github.com/ Line-us/ Line-us-Programming/tree/master/Documentation)(including GCode Spec and drawable area diagram)

* A useful guide to the [syntax of a 'Path' element](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths)  of an SVG file by Mozilla

* [DI in a console application](https://medium.com/@lschoeneman/how-to-take-advantage-of-dependency-injection-in-net-core-2-2-console-applications-274e50a6c350)  by Larry Schoeneman