Homework 2: Getting Comfortable with libembroidery
and the Turtle
Class
Our overall goal in this assignment is to make a program that creates embroidery patches can can be sewn out on the embroidery machines in the Maker Space. In this part of the assignment, you will familiarize yourself with the C++ Turtle
class, which is a CS 70-specific wrapper to libembroidery
, a C library that generates embroidery pattern files.
The Turtle
Class
The Turtle
class is a CS 70-specific wrapper to libembroidery
, a C library that generates embroidery pattern files. This class provides an interface very similar to the Python Turtle
that you may have seen previously (e.g., in CS 5).
Any file that uses the Turtle
class requires you to add the line
#include <cs70/turtle.hpp>
to the top of the file so that the C++ compiler will get all the declarations it needs.
The functions you write this week will work with a Turtle
object. You can declare and initialize a (default) Turtle
in a variable t
by writing:
Turtle t;
In Java, I'd write
Turtle t = new Turtle();
Can I say that here?
No. This is one of those big C++ vs. Java differences.
Pair-programming navigators: If you see your partner trying to code like they're writing Java, gently remind them that we're writing C++.
But, like, Java, man… Java!!! Come back to me… I miss you. 😢
You can call a function on a Turtle
using the familiar dot notation:
t.penup();
Here's the full set of member functions available for your Turtle
(which you might want to keep open in another tab as you work):
Although you don't need the
Turtle
documentation open for the next part where we make a skeleton of our code.A skeleton? It's not even Hallowe'en yet!
Your Task, Part 1: Creating a Skeleton
In this part of the assignment you will create the files needed for the assignment. Your goal is to have code that can compile and run, even if it doesn't do anything yet.
I'm all for easy victories! When I can run my code I'm happy, even if it doesn't do much.
Required Files
Your code for this week will be split into three files:
embroidery.hpp
will declare all of the functions that you write.- We'll tell you what functions to declare below.
embroidery.cpp
will define all of the functions that you write.- We'll just make placeholders for the functions you will write later.
maker.cpp
will define themain
function that uses the functions you write inembroidery.cpp
.- We’ve provided
maker.cpp
for you, but you will need to edit it to uncomment calls to each of the functions as you finish writing them.
- We’ve provided
Creating the Header File, embroidery.hpp
Create the file embroidery.hpp
in your embroidery/
directory. Since it's a header file, you should begin by creating an include guard (covered in Week 3, Lesson 1).
Once you've done that, add the line
#include <cs70/turtle.hpp>
as the first code line after the include guard. This line will allow us to refer to the Turtle
class in our function declarations.
Your header file also needs to declare all of the functions that you are going to write this week:
void rect(Turtle& t, float width, float height);
void meetTurtle();
void plotExampleData();
void plotStudentData();
Although C++ itself would be happy if you just copied and pasted the above code into your embroidery.hpp
file, doing so isn't sufficient for this assignment because header files are often read by humans as well as by the compiler. The header file specifies the interface to your code, so it should be easy for a human reader to understand what each function does, what its parameters are, and what it returns (if anything).
To meet those requirements, you'll need to add comments to your code.
The help page on commenting includes information on how to document the interface of your functions. Click the button below to open it in a new tab:
Meh. What's the deal with the
&
inTurtle& t
? Is that a typo?Nope! The
&
means that we're passing a reference to theTurtle
object, not a copy of it. Using a reference is more efficient, especially for large objects, and it's vital if we want to be able to modify the original object.We cover this topic in Week 3, Lesson 2.
Meh. I hadn't bothered to do that lesson yet, but okay, whatever.
Creating the Implementation File, embroidery.cpp
Now that you've made the header file, you need to create the corresponding implementation file, embroidery.cpp
, in your embroidery/
directory.
Your implementation file should #include
your interface header file at the top:
#include "embroidery.hpp"
Next, you need to define all of the functions you are going to write this week. For now, your implementations can be blank placeholders (a.k.a. stubs). We call this rudimentary version of the file a stub file.
#include <stdexcept> // for std::logic_error
void rect(Turtle& /* t */, float /* width */, float /* height */) {
// ^-- variable names commented out to prevent "unused variable" warnings
// in this stub.
// FIXME: Uncomment variable names when stub is implemented.
throw std::logic_error("Not implemented (yet!)"); // FIXME: Implement it!
}
void meetTurtle() {
throw std::logic_error("Not implemented (yet!)"); // FIXME: Implement it!
}
void plotExampleData() {
throw std::logic_error("Not implemented (yet!)"); // FIXME: Implement it!
}
void plotStudentData() {
throw std::logic_error("Not implemented (yet!)"); // FIXME: Implement it!
}
The lines starting with
throw
generate a C++ exception if these unimplemented functions are called, crashing the program.
Compiling the Program
At this point, your program doesn't really do anything, but it should still be possible to compile and link it all. Go ahead and try!
Note that you will also need to tell the linker that you are using
functions from the turtle
, point
, and libembroidery
libraries:
clang++ -gdwarf-4 -c -std=c++17 -Wall -Wextra -pedantic embroidery.cpp
clang++ -gdwarf-4 -c -std=c++17 -Wall -Wextra -pedantic maker.cpp
clang++ -lembroidery -lturtle -lpoint maker.o embroidery.o -o maker
Running the Program
At this point, you should be able to run your program by typing ./maker
at the command line. You should see something like
embroidery SERVER > ./maker
terminate called after throwing an instance of 'std::logic_error'
what(): Not implemented (yet!)
[1] 131345 IOT instruction (core dumped) ./maker
The program will crash because the functions you are calling haven't been implemented yet, but at least you know that everything is set up correctly!
Your Task, Part 2: Writing Functions
Now that we have a skeleton that compiles and runs, we can start filling in the functions.
Include Additional Headers
Your embroidery.cpp
file will need to include the following additional headers to support the code we're going to write:
#include <iostream>
#include <fstream>
#include <string>
#include <cstddef>
#include <cs70/turtle.hpp>
You should add them now so that you don’t run into problems later.
Hay! I thought we already included
<cs70/turtle.hpp>
inembroidery.hpp
?It's true, we have, and in a pedantic sense it's redundant to include it again in
embroidery.cpp
. But including it in both files is a good idea (and conventional practice), because doing so makes it clear that this code uses theTurtle
class from the library, and because it makes each file more self-contained.Also, technically, the
embroidery.hpp
header file would still work if we replaced the line#include <cs70/turtle.hpp>
in that file with justclass Turtle;
.That declaration is sufficient to tell the compiler that a class
Turtle
exists, since it doesn't call any of its functions or need to know how big it is in the header file. But when we write code that does call functions we expectTurtle
to have or need to know the size of aTurtle
, the code won't compile.Obviously we included the library when we wrote
embroidery.hpp
, but you can never be sure that someone in the future might try to “make the code more efficient” by not loading the library header and just declaring the class instead.Exactly. Relying on indirect includes (a.k.a. “vicarious includes”) is generally discouraged in C++. If you need something declared in a public header file, you should include that header file directly.
Namespaces
A useful concept in C++ is that of a namespace, which is a way to help the compiler avoid conflicts between variable and function names. The standard (std
) namespace of C++ includes many useful functions, classes and values, including std::cout
and std::endl
. If saying std::cout
and std::endl
seems like a lot of typing, you could add the directive
using namespace std;
at the top of your file (after your include statements), and then you could just write cout
and endl
. (More on this topic later.)
This step is totally optional. Some people like including the std::
because it's really clear where the function is coming from, and some people like the way it's a bit quicker to type the code for a std
function if they leave it off. Either way is fine for this assignment, but you'll need to discuss it with your partner!
Writing Your First Turtle Functions
You'll write two functions for this part of the assignment.
The rect
Function
The first function you will write is rect
. You've already declared it in embroidery.hpp
and documented it there with comments (hopefully!), and written a stub in embroidery.cpp
. Now it's time to implement it!
In order to write your rect
function, you will need to consult the Turtle Class Specification to decide which functions are useful for this task. For example, to tell a Turtle
t
to put the pen down, you would call t.pendown()
. Some notes for drawing the rectangle:
- Assume that the turtle starts at the bottom-left corner of the rectangle it will be drawing and is facing in the direction of the desired horizontal base of the rectangle.
- The position with coordinates
(x+100, y+50)
is100
units to the right and50
units up from(x,y)
. - Depending on which functions you use to move the
Turtle
, you may not need to set direction or turn theTurtle
.
The meetTurtle
Function
The next function you will write is meetTurtle
, by replacing the stub in embroidery.cpp
with a real implementation. This function should start by declaring the following constants:
// Patch size in embroidery units: the rectangular area we draw into.
constexpr float PATCH_WIDTH = 50; // overall patch width
constexpr float PATCH_HEIGHT = 40; // overall patch height
// Stitch sizes: when satin is off these control spacing between stitches;
// when satin is on the step size controls the satin stitch "width".
constexpr int EDGE_STITCH_SIZE = 2; // coarser stitches for the border
constexpr int TEXT_STITCH_SIZE = 1; // finer stitches for text detail
// Visual scaling values for text and satin mode:
constexpr float FONT_SIZE = 1.0; // scale passed to displayMessage()
// Satin 'delta' controls forward step between side-to-side stitches in
// satin mode.
constexpr float SATIN_DELTA = 0.3;
Your code should then do the following:
- Create a
Turtle
object- We covered how to do that in the section on the
Turtle
class above.
- We covered how to do that in the section on the
- Draw a satin-stitched rectangle that is
PATCH_WIDTH
wide,PATCH_HEIGHT
tall, and has a step size ofEDGE_STITCH_SIZE
. Be sure to use yourrect
function! - Add three lines of satin-stitched text inside the rectangle that have a size of
TEXT_STITCH_SIZE
and a text scaleFONT_SIZE
:- The initials of partner one in your group
- A team name (choose something relatively short, so it will fit on your patch!)
- The initials of partner two in your group
- Call the
Turtle
’send()
function to finish the embroidery pattern - Save the results to a file called
meet_turtle.dst
Compiling and Running Your Code
You can use the same commands as before to compile and link your code. When you run your program, a file called meet_turtle.dst
will be created. In the section below, we describe how to convert that file into a format you can view.
Viewing the Results
The file our Turtle
will create is a .dst
format file, which is readable by the embroidery machines in the Maker Space. To make it easier to test your code as you work, the server includes a command-line tool called embconv
that will turn .dst
files into .svg
image files.
To use embconv
, give it the name of your embroidery file and the name of the output image file:
embconv meet_turtle.dst meet_turtle.svg
VS Code should allow you to view the file if you open it from the File Explorer pane (i.e., double-click the filename). The image isn't a perfect replication of what your image will look like as an embroidery project, but it's close enough to be helpful.
Optionally, download the file to the machine you're working on (by right-clicking on the filename in the File Explorer pane and choosing
) and then view it in a web browser.As you work on each of your functions this week, you should regularly write out your Turtle
’s output to a .dst
file, embconv
it to a .svg
file, and view it.
If everything works as expected, your final meet_turtle.svg
file should look something like
Note that the color on your image might be different than our example—.dst
files don’t store color information (the color of the embroidered result depends on the color of thread in the sewing machine), so embconv
just picks a random color each time it's run.
Helpful Hints
Don't Forget!
Every time you change your source files and want to see how it affects your image, you need to
- Recompile all the source files affected by the change.
- Link the object files made by the compiler into an executable.
- Run the executable.
- Use
embconv
to convert the image file. - Reload the image file in whatever application you're using to view it.
So don't forget! Every time you change
embroidery.cpp
, you need to recompile it, relink it, re-run./maker
and regenerate the.svg
file withembconv
before you can see the results.If you fix a bug in the code and the output image doesn’t change, you probably forgot one of these steps.
Pay Attention to Warnings (from clang++
and cpplint
)
We recommend that you fix compiler warnings immediately, rather than waiting until the end. Even if most warnings are about issues that don’t cause trouble in practice, sometimes warnings reflect very serious errors in your code, and getting rid of the unimportant warnings makes it easier to spot the important warnings!
Similarly, cpplint
is pretty picky about how you write your code, so your life will be easier if you run it on your code from time to time and correct your formatting as you go, rather than waiting until the end to fix dozens (or for larger programs, hundreds) of small annoying errors.
If Don't See a .dst
File…
Remember that you need to call the save()
function on your Turtle
object to write out the .dst
file. If you forget to do that, you won't see a .dst
file appear in your directory.
But it printed a message saying
Creating meet_turtle.dst..
. What gives?That's just a print statement in the
maker.cpp
file. It doesn't actually create the file. You have to call thesave()
function on yourTurtle
object to do that.
(When logged in, completion status appears here.)