1
0
Fork 0

chore: Port FreeSewing.dev to docusaurus

The replaces the NextJS site powering FreeSewing.dev with a Docusaurus
setup. It's part of my efforts to simplify FreeSewing's setup so we can
focus on our core value proposition.
This commit is contained in:
Joost De Cock 2024-09-28 13:13:48 +02:00
parent 497633d1d3
commit ab3204f9f1
692 changed files with 11037 additions and 20674 deletions

View file

@ -0,0 +1,226 @@
---
title: Getting started with Codespaces
order: 13
---
:::note
This documentation assumes that you have a GitHub account with
a repository forked from `freesewing/freesewing`.
:::
## What is Codespaces?
[GitHub Codespaces][ghcs] (Codespaces) is an online service from
[GitHub][gh] that allows you to edit files and run computer programs
via a web browser.
The files and computer programs are hosted on and run from a remote
server run by GitHub.
[gh]: https://github.com
[ghcs]: https://github.com/features/codespaces
For FreeSewing, you can use Codespaces to edit our repository files to
modify existing designs, add new files to create new designs, and run
the FreeSewing lab website so you can test designs.
You can also edit, add, and test documentation files in Codespaces.
Because all of this is is done online via a web browser, you do not need
to have or use your own computer to perform this development work.
## Accessing Codespaces
Access Codespaces from the GitHub website while logged in:
- The "Your Codespaces" page is accessed via the "Codespaces" link at the top
of any GitHub page.
- A shortcut URL is: https://github.com/codespaces
## Creating a codespace
To create a new codespace:
- Select the "New codespace" button at the top of the Your Codespaces page.
- A shortcut URL is: https://github.com/codespaces/new
This will open a "Create a New Codespace" page.
On the Create a New Codespace page, select the options for your codespace:
- Repository -> ("`username/freesewing`" assuming that is the name of your GitHub repository)
- Branch -> (select the branch you want to use)
- Region -> (select the region most appropriate for you)
- Machine type -> (choose "2-core")
- Press the "Create codespace" button.
The Codespaces app will open in the browser window.
The Codespaces app is basically the [Visual Studio Code][vs] app with
Codespaces and GitHub integration built in.
[vs]: https://code.visualstudio.com
(Wait 45 seconds or so for the Codespace app to clone the repository from
GitHub to the codespace repository and start.)
## Editing files
You can browse and edit files in your codespace repository from within the app:
- On the Activity Bar on the far left side of the page, select the
Explorer icon.
(The icon looks like two pages of paper.)
- Use the Explorer sidebar to browse your repository and select a file to edit.
- Edit the file in the main window of the app.
## Saving, committing, and pushing changes
Auto-save is enabled by default, so any changes you make are
automatically saved in your codespace repository.
To commit changes to your codespace repository:
- In the Activity Bar on the far left side of the page,
select the Source Control icon.
- In the Source Control sidebar you can see a list of changed files.
- Press the "+" plus icon next to a file to stage it in preparation to
commit.
- Type a commit message in the text box and press the "Commit" button.
This commits the staged files to your codespace repository.
To push committed changes from your codespace repository back to your
GitHub repository:
- After committing changes to your codespace repository, press the
"Sync Changes" button to push the committed changes to your GitHub
repository.
- There is also an "..." ellipses menu at the top of the Source Control
sidebar that you can use.
Select the "Push" menu item.
## Running the lab, dev, and org websites
In the "Terminal" panel at the bottom of the page, you can run commands.
To start the lab website (to view and test new designs and design changes):
- In the Terminal panel, run `yarn kickstart` and then `yarn lab`.
To start the dev or org websites (to view and test documentation changes):
- dev: In the Terminal panel, run `cd sites/dev` and `yarn start`.
- org: In the Terminal panel, run `cd sites/org` and `yarn start`.
After the lab, dev, or org website starts:
- The usual `localhost:8000` port will automatically be forwarded to a custom
URL.
- A pop-up will appear saying that the port has been forwarded. The "Open in
browser" button on the pop-up will open a new browser tab/window with the
custom URL.
- You can also access the custom URL via the "Ports" panel.
:::tip
An example of a custom URL: `https://username-ominous-space-waffle-rwpgzw5q15vqc52q9-8000.preview.app.github.dev/`
:::
## To make a website publicly available
Forwarded ports are private by default.
The custom URL page is accessible only to you, the GitHub user
who owns the codespace, while you are logged into your GitHub account.
However, you can make the port public so others can access it
(or so you can access it on a different browser while not logged into GitHub).
To make the port public:
- In the Ports panel, right-click on the port and select
Port Visibility -> Public.
- The custom URL will be now be a publicly-accessible forwarded port.
You can copy and share the custom URL.
## Starting and stopping a codespace
You can start and stop your codespace via the Your Codespaces page.
- To start: Click on the codespace name to browse to the Codespaces
app URL for that codespace.
(You can also right-click to copy the URL and open it in a
different browser window.)
- To stop: Open the "..." ellipses menu next to the codespace
and select "Stop codespace".
:::warning
Codespaces continue to run unless explicitly stopped or the idle timeout is
reached.
(Closing the Codespaces app window does not stop the codespace.)
:::
## Renaming a codespace
Each new codespace is generated with a random name.
You can rename a codespace to make it easier to identify.
(This will help if you have more than one codespace.)
To rename a codespace:
- Go to the Your Codespaces page.
- Open the "..." ellipses menu next to the codespace
and select "Rename".
## Codespaces settings
You can access GitHub Settings via the user icon menu in the upper right
corner of any GitHub page (but not on the Codespaces app page).
- Selecting the "Settings" menu item will open the GitHub Settings page.
- The Codespaces settings are under "Codespaces" in the left sidebar
on the Settings page.
- A shortcut URL is: https://github.com/settings/codespaces
Among the Codespaces settings available are:
- "Default idle timeout" (how long codespaces continue to run when idle,
before being stopped)
- "Default retention period" (how long codespace repositories are kept
when unused, before being deleted)
:::note
Do not be confused by the other Settings icon/menu at the bottom left of the
Codespaces app page.
That is where settings for the Codespaces app and Visual Studio Code editor
are located.
:::
## Usage and Quotas
To check usage:
- Usage information is under "Billing and plans" in the left sidebar on
the Settings page.
- A shortcut URL is: https://github.com/settings/billing
About quotas:
- As a free account user, you have a quota of 120 core-hours of usage
and 15 GB of storage per billing month.
That is the total for all your codespaces combined.
- However, because your codespace uses a 2-core machine, you effectively
have only 60 hours of usage per month.
- (You can also select a 4-core machine for your codespace, but that
would reduce the effective usage quota to only 30 hours per month.)
- You will receive email notifications and and _toast_ messages in the
Codespaces app when you have used 75%, 90%, and 100% of your quotas.
- Quotas reset each month at the start of your account's billing cycle.
(Billing cycle start/end dates are different for each user.)
:::warning
Storage used by a codespace is counted against the quota regardless of
whether the codespace is running or stopped. If you are no longer
actively using a codespace, you may want to delete it so its storage
no longer continues to count towards your quota.
:::
:::tip
Don't worry -- you won't get a bill if you exceed your quota!
Instead, you will just be unable to start any of your codespaces
until the start of the next billing month.
And, if you have any work-in-progress changes that you need access to
before then, you can export those changes to a new GitHub branch.
:::
## Deleting a codespace
To delete a codespace:
- Go to the Your Codespaces page.
- Open the "..." ellipses menu next to the codespace
and select "Delete".
:::note RELATED
For more information, please see:
- [GitHub Codebases documentation](https://docs.github.com/en/codespaces).
- [About billing for GitHub Codespaces](https://docs.github.com/en/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces)
:::

View file

@ -0,0 +1,17 @@
---
title: Getting started with Gitpod
order: 10
---
If you don't want to set up a dev environment or if you just want to kick the
tires like, right now, you can run our development environment in your browser via Gitpod:
https://gitpod.io/#https://github.com/freesewing/freesewing
:::tip
We recommend that you instead fork our repository and use Gitpod with
your fork so you can make changes and push them to your repository on Gitpod.
To do so, simple adapt the URL above as follows:
https://gitpod.io/#url-to-your-fork
:::

View file

@ -0,0 +1,174 @@
---
title: Setting up the FreeSewing development environment
order: 40
---
FreeSewing provides a development environment to help you design and develop
patterns.
There are two ways to run this development environment:
- [**Monorepo development**](#monorepo-development): Use this if you intend to
contribute your work to FreeSewing
- [**Stand-alone development**](#stand-alone-development): Use this if you want
to do your own thing, and not contribute to FreeSewing
## Monorepo development
:::note
This is the recommended way for (aspiring) FreeSewing contributors
:::
### TL;DR
```bash
git clone https://github.com/freesewing/freesewing
cd freesewing
yarn kickstart
```
:::tip
Even better: [clone your own
fork](https://github.com/freesewing/freesewing/fork)
```bash
git clone https://github.com/your-username/freesewing
cd freesewing
yarn kickstart
```
:::
This sets up the monorepo. If you would like to create a new design, run the
following command:
```sh
yarn new design
```
If you'd like to create a new plugin, run this variant instead:
```sh
yarn new plugin
```
### Step by step
:::note
These docs assume you have git installed.
But if you're running Linux, you have git, right?
:::
#### Install yarn
Our repository uses yarn workspaces. So you'll need `yarn` to work with it.
To install it run:
```bash
npm install yarn --global
```
#### Fork our repository
You'll want to fork our repository. This way you have your own copy where you can make
all the changes you want. To do so, visit https://github.com/freesewing/freesewing/fork
#### Clone the forked repository
Now that you have your very own fork, it's time to clone it locally.
```bash
git clone <url to your fork>
```
Make sure to use the URL to your own fork, typically `https://github.com/your-username/freesewing` but
obviously with your real username rather than `your-username`.
#### Install dependencies
Enter the directory that was created, and run the `yarn kickstart` command:
```bash
cd freesewing
yarn kickstart
```
Now you're ready to [start the development environment](/tutorials/getting-started-linux/dev-start).
:::note
There is another `yarn` command that comes with some Linux distributions,
installed as part of the `cmdtest` package and used for command line
scenario testing.
If you get an `ERROR: There are no scenarios; must have at least one.`
message when trying to run the `yarn` command, it may be because the wrong
`yarn` is being used.
Possible workarounds for this include uninstalling the `cmdtest` package
or making sure that npm `yarn` is installed and comes first in your `PATH`
environment variable.
:::
## Creating a new design
If you would like to create a new design, run the following command:
```sh
yarn new design
```
## Creating a new plugin
If you'd like to create a new plugin, run the following command:
```sh
yarn new plugin
```
## Stand-alone development
With Node.js installed, all you need to do to setup the stand-alone development environment is run this command:
```bash
npx @freesewing/new-design
```
After you've answered [some questions](#questions), it will take a while to set
everything up. When it's done, you will have a new folder with the development
environment inside.
Now you're ready to [start the development
environment](/tutorials/getting-started-linux/dev-start).
:::tipThe folder will have the name you chose above.:::
:::note
### Questions
#### What template to use
Use `From scratch` unless you want to start from our of our blocks:
- Use `Extend Brian` to start from [Brian](https://freesewing.org/designs/brian)
- Use `Extend Bent` to start from [Bent](https://freesewing.org/designs/bent)
- Use `Extend Bella` to start from [Bella](https://freesewing.org/designs/bella)
- Use `Extend Breanna` to start from [Breanna](https://freesewing.org/designs/breanna)
- Use `Extend Titan` to start from [Titan](https://freesewing.org/designs/titan)
#### What name to use
This will become the name of your design. Stick to \[a-z] here to avoid problems.
If you're not certain what to pick, just mash some keys, it doesn't matter.
#### What package manager to use
You may wish to choose `yarn` since that is the package manager
that we use when doing work in the monorepo,
and many of our tutorials are written to use `yarn`.
However, it doesn't really matter.
You can choose either `yarn` or `npm` as you wish.
:::

View file

@ -0,0 +1,62 @@
---
title: Start the development environment
order: 50
---
FreeSewing provides a development environment to help you design and develop patterns.
There are two ways to run this development environment:
- [**Monorepo development**](#monorepo-development): Use this if you intend to contribute your work to FreeSewing
- [**Stand-alone development**](#stand-alone-development): Use this if you want to do your own thing, and not contribute to FreeSewing
## Monorepo development
Run `yarn lab` to start the development environment:
```bash
yarn lab
```
Then point your browser to http://localhost:8000
:::tip
### Adding a new design
This is all you need to work on existing designs. If you'd like to add a new design, run:
```bash
yarn new design
```
Just make sure to re-start the lab afterwards with `yarn lab`
:::
## Stand-alone development
You will have a new folder that has the name you picked for your design.
If you chose `test`, you will have a folder named `test`.
If you chose `banana`, you'll have a folder named `banana`.
(Within this new folder, the `design` subfolder holds your design's configuration file and source code.
You can ignore all other subfolders and files; they are part of the development environment.)
To start the development environment, enter the folder that was created
and run `yarn dev` (or `npm run dev` if you're using npm as a package manager).
Then open your browser and go to http://localhost:8000
:::tip
The development environment will watch for any changes you make to
the pattern's source code or configuration.
When you do, it will update automatically in your browser.
:::
:::note
##### Yay, you're done!
All you have to do now is design your pattern.
Thankfully, we have a tutorial for that too:
- [Pattern design tutorial](/tutorials/pattern-design/): A step-by-step guide to designing your first pattern
:::

View file

@ -0,0 +1,15 @@
---
title: Installing Node.js
order: 20
---
Now we will use `nvm` to install Node.js. Run the following command:
```bash
nvm install lts/hydrogen
```
This will install the so-called LTS version of Node.js 18 on your system.
LTS versions -- short for Long Term Support -- are good Node.js versions
to use because they are stable and supported for a long time.

View file

@ -0,0 +1,33 @@
---
title: Installing nvm
order: 10
---
FreeSewing is built with [Node.js](https://nodejs.org/), a JavaScript runtime.
You'll need to install Node.js on your system, and to do so, we'll
use [`nvm`](https://github.com/nvm-sh/nvm), short for _Node Version Manager_.
Using `nvm` has a number of benefits in comparison with installing Node.js directly from
the Node.js website, or from a package provided by your Linux distribution:
- You can easily switch between different Node.js versions
- Everything gets installed in your home folder, avoiding permission problems
To setup `nvm`, [follow the install instructions in the nvm
README](https://github.com/nvm-sh/nvm#installing-and-updating).
After installation is completed, try running the following command:
```bash
nvm
```
If all goes well, it should show you the `nvm` help.
:::tip
If you get `nvm: command not found` or something similar, close the current terminal
window, and open a new one. Now `nvm` should be found.
:::

View file

@ -0,0 +1,54 @@
---
title: Using a different Node.js version
order: 30
---
Now that you've got Node.js setup, we can start setting up the FreeSewing
development environment.
But before doing so, let's look at how `nvm` can help you juggle different
Node.js versions.
### nvm ls
To see the different Node.js versions on your system, run this command:
```bash
nvm ls
```
It will show you a list of local Node.js versions,
either the version number or an _alias_ that points to a specific version.
You should see the `lts/*` alias in the list which is what we've just installed.
### nvm ls-remote
To see all Node.js versions that are available, not just those you have locally,
run this command:
```bash
nvm ls-remote
```
It will spit out a long list of Node.js versions that you can install.
### nvm install
For any of these versions, either local or remote, you can install them
by making a note of the version or alias and running this command:
```bash
nvm install <version-or-alias>
```
### nvm use
With multiple Node.js versions installed, `nvm` allows you to switch between different
versions. Just tell it which version you want to use:
```bash
nvm use lts/hydrogen
```
If you picked a version that is not installed, `nvm` will simply tell you
and even suggest the command you should type to install it. Handy!

View file

@ -0,0 +1,13 @@
---
title: Getting started on Linux
order: 15
---
In this tutorial, we will setup Node.js and initialize the FreeSewing
development environment on a Linux system.
We'll cover the following steps:
<ReadMore />
:::tipThese instructions are also valid for BSD or other Unix systems:::

View file

@ -0,0 +1,159 @@
---
title: Setting up the FreeSewing development environment
order: 40
---
FreeSewing provides a development environment to help you design and develop
patterns.
There are two ways to run this development environment:
- [**Monorepo development**](#monorepo-development): Use this if you intend to
contribute your work to FreeSewing
- [**Stand-alone development**](#stand-alone-development): Use this if you want
to do your own thing, and not contribute to FreeSewing
## Monorepo development
:::note
This is the recommended way for (aspiring) FreeSewing contributors
:::
### TL;DR
```bash
git clone https://github.com/freesewing/freesewing
cd freesewing
yarn kickstart
```
:::tip
Even better: [clone your own
fork](https://github.com/freesewing/freesewing/fork)
```bash
git clone https://github.com/your-username/freesewing
cd freesewing
yarn kickstart
```
:::
This sets up the monorepo. If you would like to create a new design, run the
following command:
```sh
yarn new design
```
If you'd like to create a new plugin, run this variant instead:
```sh
yarn new plugin
```
### Step by step
:::note
These docs assume you have git installed.
But if you're running macOS, you have git, right?
:::
#### Install yarn
Our repository uses yarn workspaces. So you'll need `yarn` to work with it.
To install it run:
```bash
npm install yarn --global
```
#### Fork our repository
You'll want to fork our repository. This way you have your own copy where you can make
all the changes you want. To do so, visit https://github.com/freesewing/freesewing/fork
#### Clone the forked repository
Now that you have your very own fork, it's time to clone it locally.
```bash
git clone <url to your fork>
```
Make sure to use the URL to your own fork, typically `https://github.com/your-username/freesewing` but
obviously with your real username rather than `your-username`.
#### Install dependencies
Enter the directory that was created, and run the `yarn kickstart` command:
```bash
cd freesewing
yarn kickstart
```
Now you're ready to [start the development environment](/tutorials/getting-started-linux/dev-start).
## Creating a new design
If you would like to create a new design, run the following command:
```sh
yarn new design
```
## Creating a new plugin
If you'd like to create a new plugin, run the following command:
```sh
yarn new plugin
```
## Stand-alone development
With Node.js installed, all you need to do to setup the stand-alone development environment is run this command:
```bash
npx @freesewing/new-design
```
After you've answered [some questions](#questions), it will take a while to set
everything up. When it's done, you will have a new folder with the development
environment inside.
Now you're ready to [start the development
environment](/tutorials/getting-started-linux/dev-start).
:::tipThe folder will have the name you chose above.:::
:::note
### Questions
#### What template to use
Use `From scratch` unless you want to start from our of our blocks:
- Use `Extend Brian` to start from [Brian](https://freesewing.org/designs/brian)
- Use `Extend Bent` to start from [Bent](https://freesewing.org/designs/bent)
- Use `Extend Bella` to start from [Bella](https://freesewing.org/designs/bella)
- Use `Extend Breanna` to start from [Breanna](https://freesewing.org/designs/breanna)
- Use `Extend Titan` to start from [Titan](https://freesewing.org/designs/titan)
#### What name to use
This will become the name of your design. Stick to \[a-z] here to avoid problems.
If you're not certain what to pick, just mash some keys, it doesn't matter.
#### What package manager to use
You may wish to choose `yarn` since that is the package manager
that we use when doing work in the monorepo,
and many of our tutorials are written to use `yarn`.
However, it doesn't really matter.
You can choose either `yarn` or `npm` as you wish.
:::

View file

@ -0,0 +1,62 @@
---
title: Start the development environment
order: 50
---
FreeSewing provides a development environment to help you design and develop patterns.
There are two ways to run this development environment:
- [**Monorepo development**](#monorepo-development): Use this if you intend to contribute your work to FreeSewing
- [**Stand-alone development**](#stand-alone-development): Use this if you want to do your own thing, and not contribute to FreeSewing
## Monorepo development
Run `yarn lab` to start the development environment:
```bash
yarn lab
```
Then point your browser to http://localhost:8000
:::tip
### Adding a new design
This is all you need to work on existing designs. If you'd like to add a new design, run:
```bash
yarn new design
```
Just make sure to re-start the lab afterwards with `yarn lab`
:::
## Stand-alone development
You will have a new folder that has the name you picked for your design.
If you chose `test`, you will have a folder named `test`.
If you chose `banana`, you'll have a folder named `banana`.
(Within this new folder, the `design` subfolder holds your design's configuration file and source code.
You can ignore all other subfolders and files; they are part of the development environment.)
To start the development environment, enter the folder that was created
and run `yarn dev` (or `npm run dev` if you're using npm as a package manager).
Then open your browser and go to http://localhost:8000
:::tip
The development environment will watch for any changes you make to
the pattern's source code or configuration.
When you do, it will update automatically in your browser.
:::
:::note
##### Yay, you're done!
All you have to do now is design your pattern.
Thankfully, we have a tutorial for that too:
- [Pattern design tutorial](/tutorials/pattern-design/): A step-by-step guide to designing your first pattern
:::

View file

@ -0,0 +1,15 @@
---
title: Installing Node.js
order: 20
---
Now we will use `nvm` to install Node.js. Run the following command:
```bash
nvm install lts/hydrogen
```
This will install the so-called LTS version of Node.js 18 on your system.
LTS versions -- short for Long Term Support -- are good Node.js versions
to use because they are stable and supported for a long time.

View file

@ -0,0 +1,33 @@
---
title: Installing nvm
order: 15
---
FreeSewing is built with [Node.js](https://nodejs.org/), a JavaScript runtime.
You'll need to install Node.js on your system, and to do so, we'll
use [`nvm`](https://github.com/nvm-sh/nvm), short for _Node Version Manager_.
Using `nvm` has a number of benefits in comparison with installing Node.js from
the Node.js website, or from a package that came with macOS.
- You can easily switch between different Node.js versions
- Everything gets installed in your home folder, avoiding permission problems
To setup `nvm`, [follow the install instructions in the nvm
README](https://github.com/nvm-sh/nvm#installing-and-updating).
After the installation, try running the following command:
```bash
nvm
```
If all goes well, it should show you the `nvm` help.
:::tip
If you get `nvm: command not found` or something similar, close the current terminal
window, and open a new one. Now `nvm` should be found.
:::

View file

@ -0,0 +1,17 @@
---
title: Installing the Xcode command line tools
order: 10
---
Before we can get started, we need some basic tools for development.
They are bundled in the _Xcode command-line tools_ so let's install
that first.
Open the Terminal application, and type the following command:
```bash
xcode-select --install
```
A pop-up will appear asking you to confirm the installation.
Confirm, and go make a cup of coffee while the install does its thing.

View file

@ -0,0 +1,54 @@
---
title: Using a different Node.js version
order: 30
---
Now that you've got Node.js setup, we can start setting up the FreeSewing
development environment.
But before doing so, let's look at how `nvm` can help you juggle different
Node.js versions.
### nvm ls
To see the different Node.js versions on your system, run this command:
```bash
nvm ls
```
It will show you a list of local Node.js versions,
either the version number or an _alias_ that points to a specific version.
You should see the `lts/*` alias in the list which is what we've just installed.
### nvm ls-remote
To see all Node.js versions that are available, not just those you have locally,
run this command:
```bash
nvm ls-remote
```
It will spit out a long list of Node.js versions that you can install.
### nvm install
For any of these versions, either local or remote, you can install them
by making a note of the version or alias and running this command:
```bash
nvm install <version-or-alias>
```
### nvm use
With multiple Node.js versions installed, `nvm` allows you to switch between different
versions. Just tell it which version you want to use:
```bash
nvm use v10.22.1
```
If you picked a version that is not installed, `nvm` will simply tell you
and even suggest the command you should type to install it. Handy!

View file

@ -0,0 +1,18 @@
---
title: Getting started on Mac
order: 25
---
In this tutorial, we will setup Node.js and initialize the FreeSewing
development environment on a Mac system running macOS.
:::tip
We'll be typing commands in a terminal window. You can find the Terminal
application at `/Applications/Utilities/`.
:::
We'll cover the following steps:
<ReadMore />

View file

@ -0,0 +1,257 @@
---
title: Getting started with Vercel
order: 14
---
## What is Vercel?
Vercel is an online service that builds and deploys web apps and
websites.
It allows you to preview designs you are working on and changes
you are making to the FreeSewing app and websites.
By sharing links to your builds/deployments, other people can also
preview your work.
Vercel offers a free "Hobby" account for personal, non-commercial use,
so everyone is able to use it.
:::note
This tutorial assumes that you already have a GitHub account
containing a fork of the FreeSewing repository.
:::
## Why you might want to use Vercel
There are reasons why you might want your own Vercel account:
- You can preview your changes:
- if you develop on a mobile device or if you do not have access to a
computer.
- without having to set up and maintain development environments on your
own computer.
- without having to manually rebuild your development environments each
time there is a code change.
- You can more easily share your work with others.
However, it is not necessary to have your own Vercel account.
Because we use Vercel to preview pull requests, if you always submit your
work to the main repository then you will be able to share your work without
needing to use Vercel yourself.
## About repositories, projects, and deployments
Vercel works through _repositories_, _projects_, and _deployments_.
_Repositories_ are simply Git repositories, for example your personal
fork of the FreeSewing repository.
You will select the repositories you wish to import into Vercel.
Within each repository there can be multiple projects.
_Projects_ are specific build types for the repository.
For example, one project in your FreeSewing repository could
be the one that builds the freesewing.org website.
You would use this project to test new designs or changes
to existing designs.
Another project could be the one that builds the freesewing.dev
website, used to test documentation changes on that website.
:::tip
Free Hobby accounts are limited to 3 Projects per Git repository.
:::
Under each project there will be many, many deployments.
_Deployments are simply builds, an instance of a website/app
built from a specific commit version of a specific branch of the repository.
These deployments can be accessed using a web browser to preview
the web app or website.
Vercel automatically builds a deployment for every push or update
you make to every branch in your repository.
The reason why Vercel builds these deployments automatically is so you
will always have the latest version available to test without
having to think about it or do any additional work.
And, because deployments are built for every push and update,
you have the ability to easily test different versions, past
and current.
This ability can help you identify what code change might have introduced
an issue or change in behavior.
:::tip
Don't worry about all of these deployments, many or most of which
you might never actually use.
Deployments are free, even the unused ones.
As Vercel itself says if you request to delete a deployment:
_Deployments that are not actively receiving any traffic do not
generate any costs nor count towards any limits._
It is Vercel's intention that there should be no need to delete
deployments.
Instead, they intend that almost all deployments will simply be left
to exist, even the unused ones.
(Because of this there are no retention periods or simple ways to
delete multiple deployments all at once.)
:::
## Creating an account
You will need to sign up for a Vercel account.
Luckily, you can simply sign up using your GitHub
account/credentials without having to create a separate username
or password.
1. On the [Vercel website][v] select the "Sign Up" button.
(A shortcut URL is: [https://vercel.com/signup][vsu].)
2. Select the "Continue with GitHub" button.
3. A pop-up window will appear asking you for permission to access
your GitHub information.
Press the green "Authorize Vercel" button to continue.
[v]: https://vercel.com
[vsu]: https://vercel.com/signup
## Importing a repository
After you create your account you will then see an "Import Git Repository"
screen.
The default listed is likely to be the actual `freesewing/freesewing`
repository owned by FreeSewing.
However, you should instead import your own personal fork of the
FreeSewing repository.
1. In the dropdown menu, select "Add a GitHub Account".
2. Select your personal GitHub account from the list.
3. Select the "Only select repositories" radio button.
4. In "Select repositories" drop-down menu, select your `freesewing`
repository.
5. Click the green "Install" button.
6. Confirm that you are giving permission to access the repository
by entering your GitHub password.
7. Finally, back at the Import Git Repository screen complete the
import by selecting the white "Import" button.
## Creating a project
You will next be taken to the Configure Project page where you can
create a project.
By default, the default Root Directory will be `sites/dev`.
The Root Directory setting will determine the build type for the project.
- `sites/dev` will build a freesewing.dev website
- `sites/org` will build a freesewing.org website
1. Change the name of the project, if you wish.
Names can consist of alphanumeric lowercase and hyphen characters.
2. Change the Root Directory to the desired setting, as described above.
3. In the Build & Development Settings,
add `yarn build` as the Build Command override.
(All the other settings will work fine with the default values.)
4. Press the white "Deploy" button.
Vercel will then create the project and start building the project's first
deployment based on the current `develop` branch.
Once the build completes
you will see a Congratulations page, with a preview image of the
website home page.
## About deployments
As mentioned previously, Vercel will eventually create and build many
deployments for your project.
One is known as the _production deployment_, based on the configured Production Branch of the repository (by default the branch named `main`).
All other deployments are referred to as _preview deployments_, including
ones based on the `develop` branch
(unless you change the Production Branch in the Project Settings to
`develop` instead of `main).
Created deployments include:
1. The initial production deployment. (Because you don't have a branch named
`main` in your repository, Vercel will instead create the initial
production deployment from the default `develop` branch.)
2. A new preview deployment every time you update your `develop` branch in GitHub
(for example, whenever you sync it with the latest `freesewing/freesewing`
updates)
3. A new preview deployment for every new branch you push to GitHub
4. A new preview deployment for every update you make to these new branches
when you push to GitHub
5. A new preview deployment for every update you make to your existing branches
when you push to GitHub
If you have multiple projects for the same repository
(for example, if you have both `sites/org` and `sites/dev` projects),
then multiple deployments will be created every time you push to GitHub.
Deployments are automatically created by Vercel.
However, because free Hobby accounts are limited to 1 concurrent
build, new deployments might be queued before they start building.
Once they start, deployments take about
4-5 minutes to build for `/sites/dev` and
16-18 minutes or so for `sites/org`.
## The Vercel Dashboard
You will manage your account and projects from the Vercel Dashboard
page, [https://vercel.com/dashboard][vd].
The default __Overview__ tab at the top of the Dashboard page will show your repositories
and projects.
Click on a project name to go to its project page.
[vd]: https://vercel.com/dashboard
## Project pages
The default __Project__ tab at the top of the project page will show the
the production deployment and some of the most recent preview
deployments for that project.
Click on the __Deployments__ tab to see all of the project's deployments.
Click on a deployment name to go to its deployment page.
Click on the __Settings__ tab to see the project's settings.
## Deployment pages
On the default __Deployment__ tab at the top of the deployment page
you will see information about the deployment.
Under __Domains__ you will see one or more URLs that can be used to
access the deployment.
These are also the URLs that you can share with others so they
can view your deployments.
- URLs containing hash characters link to the deployment for a single
commit.
- URLs without a hash point to the deployment for the latest version
of that branch.
If you ever want to delete a deployment you can do so on its
deployment page, under the "__...__" three dots menu.
## Usage and Billing
Vercel's free Hobby accounts come with
100 GB of bandwidth and 100 hours of build time each month.
This should be at least 10-15x the amount you will actually use in
a month, so do not worry about this.
If you want to check your usage, please seee the __Usage__ tab at the
top of the Dashboard page.
- A shortcut URL is [https://vercel.com/dashboard/usage][vu]
- Or, [https://vercel.com/account/billing][vb] will show a summary
of your usage.
## Disabling automatic deployments
You can disable and enable automatic deployments for a project,
for example if you wish to temporarily stop them while working on
a bug that prevents successful builds.
On the Project Settings page, select __Git__ from the menu on the
left. Change the __Ignored Build Step__ behavior from "Automatic" to
"Don't build anything".
[vu]: https://vercel.com/dashboard/usage
[vb]: https://vercel.com/account/billing

View file

@ -0,0 +1,227 @@
---
title: Getting started on Windows
order: 30
---
:::warning
Official support for FreeSewing is provided for Linux, Mac, and BSD-based operating systems.
This _Getting started on Windows_ tutorial was written by FreeSewing community members and should be considered unofficial.
However, if you encounter issues with this tutorial or require assistance with other Microsoft Windows issues, please feel free to [ask for help](/howtos/help) and our community members will be happy to assist you!
:::
You will first need to set up your development system either using Windows Subsystem for Linux or directly in Windows. Then, you can setup and start the FreeSewing development environment.
## Setting up a development environment using Windows Subsystem for Linux (WSL) and Visual Studio Code (VSCode)
If you already have a working WSL environment and VSCode Remote configured you
can follow the [getting started on Linux
guide](/tutorials/getting-started-linux) or skip ahead to [Setting up the
FreeSewing development environment
(WSL)](#setting-up-the-freesewing-development-environment-wsl). If not, the
following process is very similar but has some differences to avoid quirks
specific to this environment.
Windows Subsystem for Linux allows you to run a Linux distribution as a
development environment, with enough functionality to develop a FreeSewing
pattern. While this approach offers some advantages this is not strictly
necessary to develop patterns on Windows. If you would prefer a simpler setup
process refer to [Setting up a development environment in
Windows](#setting-up-a-development-environment-in-windows).
Installing and using an IDE is optional, you can skip that step or use a
different editor if you'd like. This guide suggests VSCode as it is freely
available on multiple platforms and provides enough functionality to build the
FreeSewing project.
### Install WSL
:::warning This guide uses WSL version 2, which requires installing the Hyper-V
virtualisation system. If you have another virtualisation system installed (such
as VirtualBox or VMWare) you may run into conflicts which require either
updating that system to a version which can use the HyperV backend or porting
your existing machines to use HyperV. :::
Follow the [Windows Subsystem for Linux Installation Guide for Windows
10](https://docs.microsoft.com/en-gb/windows/wsl/install-win10) (requires a
recent version of Windows 10).
#### Install NVM
Open a new WSL terminal from the shortcuts created or by searching for "WSL
Terminal" in the start menu. [Install NVM by following the NVM setup
guide](https://github.com/nvm-sh/nvm#install--update-script). Once installed
you will need to activate NVM by either following the instructions printed to
the screen or opening a new terminal.
#### Install Node.js (and optionally Yarn)
Now that you have NVM installed, you can install Node.js. The latest version can be
installed using `nvm install default`. You can also install a specific version
using `nvm install v18.17.0`. For the purposes of debugging it can be useful to
have the same version of Node.js installed as the main project uses, which you can
then activate using `nvm use <version>`. You can determine what version the
FreeSewing project uses by checking
[freesewing/freesewing/.nvmrc](https://github.com/freesewing/freesewing/blob/develop/.nvmrc).
:::warning At the time this guide was written the latest version of Node.js/npm has
a bug in the dependency resolution process which causes the freesewing project
to fail to build. Use the latest LTS version (currently 18.17.0) or the specific
version used by the main project to avoid this issue. :::
Node.js comes with the Node Package Manager (npm) by default which can be used to
set up the project. The default package manager uses a fairly simplistic approach
to dependency resolution which can make builds take a long time. Yarn is an
alternative package manager which makes builds faster, especially for monolithic
projects like FreeSewing. If you'd like to install yarn run `npm install yarn
--global` (optional, but recommended).
#### Install and configure Git (recommended)
The create-freesewing-pattern script will attempt to create a git repository as
part of the setup. It's not strictly required to have git installed in the WSL
environment but you will get errors during the project setup process if it isn't
installed or configured correctly. As such it's recommended to have git
installed on the WSL environment even if you're going to be using a GUI client
from the windows side.
```bash
sudo apt install git
git config --global user.email "<the email address you use for your git account>"
git config --global user.name "<your display name for your git account>"
```
### Install VSCode (optional)
[Download and install VSCode](https://code.visualstudio.com/).
:::note
FreeSewing uses .editorconfig files to enforce a consistent style for the
project. VSCode relies on extensions to provide this functionality and due to a
design shortcoming it does not respect certain editorconfig options which will
break certain files in the freesewing project ([see vscode/65663 for
details](https://github.com/microsoft/vscode/issues/65663)). If you use this
editor please ensure that your settings.json file is configured to not trim
trailing whitespace from Markdown files. The following snippet can be added to
your settings.json file to add an exemption for this file type:
```json
"[markdown]": { "files.trimTrailingWhitespace": false },
```
:::
#### Install VSCode Remote
In order to be able to use VSCode's IDE features (such as the built in terminal)
and also make use of the Node.js installation we set up in the previous steps you
will need to install VSCode Remote so that VSCode can make use of the Linux
environment. [Follow the getting started guide for VSCode
Remote](https://code.visualstudio.com/docs/remote/wsl) (If you've been following
this guide you have already done steps 1 and 2, you will only need to install
the [remote development
extension](https://aka.ms/vscode-remote/download/extension))
## Setting up a development environment in Windows.
### Install NVM
While Node.js can be installed directly on Windows, we strongly recommend using a
version manager such as [Node Version Manager for
Windows](https://github.com/coreybutler/nvm-windows/releases/latest). That link
will take you to the latest release which provides an installer you can download
and run. Once nvm-windows is installed you will be able to continue with the
rest of this process.
### Install Node.js (and optionally Yarn)
Open a Powershell terminal or command prompt. Run `nvm ls available` to show
versions that can be installed. Choose the appropriate version (you should use
the same version as the freesewing project or latest LTS version) then run `nvm
install 18.17.0` and `nvm use 18.17.0` (where `18.17.0` is the full version
string of the version you wish to use) to activate the newly installed version.
You will receive a prompt for elevated permissions and will need to accept it in
order to activate the new version of Node.js.
:::warning
At the time this guide was written the latest version of Node.js/npm has
a bug in the dependency resolution process which causes the freesewing project
to fail to build. Use the latest LTS version (currently 18.17.0) or the specific
version used by the main project to avoid this issue.
:::
Node.js comes with the Node Package Manager (npm) by default which can be used to
set up the project. The default package manager uses a fairly simplistic approach
to dependency resolution which can make builds take a long time. Yarn is an
alternative package manager which makes builds faster, especially for monolithic
projects like FreeSewing. If you'd like to install yarn run (`npm install yarn
-g`) (optional).
## Setting up the FreeSewing development environment
In VSCode or in a terminal, navigate to the folder you wish to contain your new patterns (e.g. `D:\Documents\my-freesewing-patterns`). Inside this directory run `npx @freesewing/new-design`.
After you've answered [some questions](#questions), it will take a while to set everything up.
When it's done, you will have a new folder with the development environment inside.
:::tipThe folder will have the name you chose above.:::
:::note
### Questions
#### What template to use
Use `From scratch` unless you want to start from our of our blocks:
- Use `Extend Brian` to start from [Brian](https://freesewing.org/designs/brian)
- Use `Extend Bent` to start from [Bent](https://freesewing.org/designs/bent)
- Use `Extend Bella` to start from [Bella](https://freesewing.org/designs/bella)
- Use `Extend Breanna` to start from [Breanna](https://freesewing.org/designs/breanna)
- Use `Extend Titan` to start from [Titan](https://freesewing.org/designs/titan)
#### What name to use
This will become the name of your design. Stick to \[a-z] here to avoid problems.
If you're not certain what to pick, just mash some keys, it doesn't matter.
#### What package manager to use
You may wish to choose `yarn` since that is the package manager
that we use when doing work in the monorepo,
and many of our tutorials are written to use `yarn`.
However, it doesn't really matter.
You can choose either `yarn` or `npm` as you wish.
:::
## Start the development environment
You will have a new folder that has the name you picked for your design.
If you chose `test`, you will have a folder named `test`.
If you chose `banana`, you'll have a folder named `banana`.
(Within this new folder, the `design` subfolder holds your design's configuration file and source code.
You can ignore all other subfolders and files; they are part of the development environment.)
To start the development environment, navigate to the folder that was created
and run `yarn dev` (or `npm run dev` if you're using npm as a package manager).
Then open your browser and go to http://localhost:8000
:::tip
The development environment will watch for any changes you make to
the pattern's source code or configuration.
When you do, it will update automatically in your browser.
:::
:::note
##### Yay, you're done!
All you have to do now is design your pattern.
Thankfully, we have a tutorial for that too:
- [Pattern design tutorial](/tutorials/pattern-design/): A step-by-step guide to designing your first pattern
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

View file

@ -0,0 +1,75 @@
---
title: Setting up the development environment
order: 20
---
FreeSewing provides a development environment that visualizes your design for
you. This tutorial is for the stand-alone development environment, not the
monorepo development environment (which you may have set up if you followed a
getting started tutorial).
To set it up, I will open a terminal and enter the following command:
```sh
npx @freesewing/new-design
```
It will ask if it is ok to install the development environment in a new folder
named `freesewing`. You can accept the default, or pick a different folder name
if you prefer.
It will also ask what package manager you would like to use.
Here too the default (`npm`) is fine., unless you are certain you have **yarn** installed.
After answering these questions, files will be downloaded, dependencies installed,
and it will also initialize a git repository for you (if you have git on your system).
:::note
This will take a few minutes because the development environment has a number
of dependencies that need to be downloaded.
:::
When it's ready, you can enter the `freesewing` directory that was just created and run `npm run dev`:
```sh
cd freesewing
npm run dev
```
Or if you want to use yarn as package manager:
```sh
cd freesewing
yarn dev
```
Now open a browser and go to http://localhost:8000
If all goes well, we'll should see this landing page:
![The FreeSewing development environment](./nd.png)
:::tip
##### More detailed setup tutorials are available
This pattern design tutorial contains only an abbreviated overview
of the setup process.
For more detailed instructions, please refer to one of our setup tutorials:
- [Getting started on Linux](/tutorials/getting-started-linux)
- [Getting started on Mac](/tutorials/getting-started-mac)
- [Getting started on Windows](/tutorials/getting-started-windows)
:::
:::tip
##### Need help?
If you run into any issues, head over to [FreeSewing.org/support](https://next.freesewing.org/support)
which lists the various ways in which you can get help.
:::

View file

@ -0,0 +1,41 @@
---
title: Installing NodeJS
order: 10
---
FreeSewing is a JavaScript project, so you need JavaScript to work with it.
You certainly already have JavaScript on your system. In your browser to be
precise. You can switch this website theme from light to dark mode, and
that would not work without JavaScript.
As a **user** of FreeSewing, this is all you need. To develop with FreeSewing
you are going to need to be able to run JavaScript *outside* the browser using
a JavaScript *runtime*. Which just means a thing that can *run* JavaScript.
We are going to be using [NodeJS](https://nodejs.org/) in this tutorial. It is
the most established of the different JavaScript runtimes. But there's also
other runtimes like [Deno](https://deno.com/) or [Bun](https://bun.sh/).
## Install
If you don't have NodeJS on your system, you can go to
[NodeJS.org](https://nodejs.org/) and follow the install instructions.
:::tip
##### NodeJS versions
You need Node.js 18 (lts/hydrogen) or higher to use FreeSewing
If you're looking to use different versions, I can recommend using `nvm` which makes this very easy: https://github.com/nvm-sh/nvm
:::
## Test
To test whether NodeJS is installed, and see it's version, you can run this command:
```sh
node -v
```
If you get the Node.js version number, that means NodeJs is installed. Yay!

View file

@ -0,0 +1,33 @@
---
title: "Part 1: Prerequisites"
---
In this first part, I will get your up and running with the FreeSewing
development environment.
If you are familiar with JavaScript and its ecosystem, you can probably skip
this section. If not, I have good news and bad news (and then some more good
news) for you.
The good news is that JavaScript is an easy language to pick up. It is also a
very popular and versatile language and the skills you learn here will serve
you well.
The bad news is that the JavaScript ecosystem is vast, and unfortunately
somewhat fractured. Most of the problems people need help with are not so much
in the code itself, but rather getting everything to work together.
This is true not just for FreeSewing, but pretty much all modern JavaScript.
But, no need to despair, FreeSewing provides a development environment that
will take care of all of this for you. So you can focus on designing patterns.
If you have NodeJS on your system, getting that development environment up
and running takes only a single command:
```sh
npx @freesewing/new-design
```
If you don't have NodeJS on your system --- or if you're not sure what
NodeJS is to begin with --- read on to learn how to install it.

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -0,0 +1,47 @@
---
title: The FreeSewing development environment
order: 30
---
If you have been to FreeSewing.org the FreeSewing development environment will look familiar.
That's because under the hood, it re-uses the same building blocks.
At the top of the page is the header with a row of icons that lay out what is available to you.
![The icons in the header of the FreeSewing development environment](./header.png)
From left to right you can see:
- **Home** will take you to the home page / welcome page
- **Design** will offer you a list of templates to start a design from (more on this below)
- **Documentation** will show a page with links to our documentation
- **Code** will show a page with links to our source code
- **Support** will show a page with the various ways you can get help
- **Theme** allows you to change the theme (in other works the color scheme)
- **Language** allows you to change the language
- **Sign In** allows you to sign in to your FreeSewing account so you can use
your (and our) measurements sets while designing
## Design templates
If you click the **Design** icon it will show this menu:
![Design templates provided by the FreeSewing development environment](./templates.png)
It allows you to choose a design template to start from. The following templates are included:
- **From scratch**: Start with an (almost) empty design
- **Tutorial**: Start with the end result of this very tutorial
- **From Brian**: Start with a design that extends [Brian](https://freesewing.org/designs/brian)
- **From Bent**: Start with a design that extends [Bent](https://freesewing.org/designs/bent)
- **From Titan**: Start with a design that extends [Titan](https://freesewing.org/designs/titan)
- **From Bella**: Start with a design that extends [Bella](https://freesewing.org/designs/bella)
- **From Breanna**: Start with a design that extends [Breanna](https://freesewing.org/designs/breanna)
For the following along this tutorial, you have two options:
- Pick **Tutorial** if you prefer to read along, make small changes, and see how they affect the design.
- Pick **From scratch** if you prefer to actively participate by recreating the design in this tutorial.
I recommend the latter. You will learn (and remember) a lot more if you are actively engaging.

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

View file

@ -0,0 +1,32 @@
---
title: Folder structure
order: 40
---
Inside the `freesewing` folder -- which might have a different name if that is
the choice you made -- you will find a bunch of files and folders.
The one that matter is the `design` folder. In it, you will find the following
subfolders:
- `from-bella`
- `from-bent`
- `from-breanna`
- `from-brian`
- `from-scratch`
- `from-titan`
- `tutorial`
Remember when you click the **Design** icon in the header it would bring up
this menu:
![Design templates provided by the FreeSewing development environment](./templates.png)
As you might have guessed by now, each of these options is contained in its
own subfolder under `designs`.
You can edit the files under `designs/[template]/src/` and the changes you make
will be reflected in the development environment.
Don't take my word for it though. Let's start doing exactly that
in [Part 2](/tutorials/pattern-design/part2).

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

View file

@ -0,0 +1,68 @@
---
title: Adding measurements
order: 30
---
FreeSewing is all about _bespoke_ sewing patterns -- or *parametric
design* to use a more generic term.
That means that when drafting our pattern, I will take the measurements provided
by the user into account.
Which begs the question, which measurements?
As the pattern designers, you get to decide which measurements are used
to draft the pattern. For this bib, I am going to use the
_head circumference_.
So let's add it as a required measurement.
## Adding required measurements
In our `src/bib.mjs` file, we will add a `measurements` property to the `bib` object.
This property will be an Array (a list) holding all required measurements for this part.
I am using [*the official name* of the measurement](/reference/measurements) here. For head
circumference, that name is `head`.
:::note [FIXME]
The `design/src/bib.mjs` "language" title on the code snippets is out of date. It is used in the tutorial from this point forward to maintain syntax-highlight not yet available for the `src/bib.mjs` title, but should be replaced with `src/bib.mjs`.
:::
```design/src/bib.mjs
function draftBib({ part }) {
return part
}
export const bib = {
name: 'fromscratch.bib',
draft: draftBib,
// highlight-start
measurements: [ 'head' ],
// highlight-end
}
```
From now on, this part requires the `head` measurement.
This change will also get picked up by the development environment, which will now complain that it is missing some measurements.
Since it's just one measurement, I will simply enter a value by hand.
For example `38` as 38 cm is a realistic head circumference measurement for a baby.
:::tip
##### Why using standard measurements names matters
In principle, I can use any name I want for our measurements.
The FreeSewing core library does not care.
However, if everybody uses their own (names for) measurements, then people
aren't able to re-use their measurements across designs.
So if you have any intention at all to play nice with the FreeSewing ecosystem,
please make sure to re-use the names of existing measurements, rather than
invent your own.
See the [best practices](/guides/best-practices#reuse-measurements) on this
topic for details.
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

View file

@ -0,0 +1,119 @@
---
title: Adding options
order: 40
---
I have shown what our bib should look like, and added the _head_ measurement
to work with. But there's still a number of choices I have to make:
- How large should the neck opening be?
- How wide should the bib be?
- How long should the bib be?
I could make all of these choices for the user and set them in stone, so to speak.
But since the pattern I am designing is code, it is trivial (and _IMHO_ very satisfying)
to make a pattern flexible and let the user choose.
All I need to do to give control to the user is add _options_ to the part.
## Add the neckRatio option
The first option I will add controls the ratio between the neck opening
and the head circumference. Let's call it `neckRatio`.
For this, I will add the `options` property to our `bib` object:
```design/src/bib.mjs
function draftBib({ part }) {
return part
}
export const bib = {
name: 'fromscratch.bib',
draft: draftBib,
measurements: [ 'head' ],
// highlight-start
options: {
neckRatio: {
pct: 80,
min: 70,
max: 90,
menu: 'fit'
},
},
// highlight-end
}
```
Can you guess what it means?
- We've added the `options` property to our `bib` object
- On the `options` property, we have added `neckRatio` which holds the configuration for our option
- It is a `pct` option -- which means it's a percentage
- Its default value is 80%
- Its minimum value is 70%
- Its maximum value is 90%
There are different types of options, but percentages are by far the most common ones.
They are all documented [in the part reference docs](/reference/api/part/config/options).
:::note
##### What is `menu` and why should you care?
The `menu` property on our option is *extra*.
It will be ignored by FreeSewing's core library and if we leave it out, our design will produce the same result.
Instead, this `menu` property is there for the benefit of FreeSewing's development environment which will use this to build a menu structure for the various
options.
This is covered in more detail in [Part 3](/tutorials/pattern-design/part3) of this tutorial.
:::
## Add the widthRatio and lengthRatio options
Let's do something similar for the width and length of our bib:
```design/src/bib.mjs
function draftBib({ part }) => {
return part
}
export const bib = {
name: 'fromScratch.bib',
draft: draftBib,
measurements: [ 'head' ],
options: {
neckRatio: {
pct: 80,
min: 70,
max: 90,
menu: 'fit'
},
// highlight-start
widthRatio: {
pct: 45,
min: 35,
max: 55,
menu: 'style'
},
lengthRatio: {
pct: 75,
min: 55,
max: 85,
menu: 'style'
},
// highlight-end
},
}
```
This is pretty much the exact same thing, except that are placing these in the `style` menu.
Later, I will test-drive our pattern to see how it behaves when we adapt the options
between their minimum and maximum values. At that time, I may need to tweak these values.
With that out of the way, I will start drawing the bib.

View file

@ -0,0 +1,426 @@
---
title: Avoiding overlap
order: 92
---
While we've only drawn the end of one strap, it's pretty obvious they overlap,
which makes it impossible to cut out, so we're going to have to address
that.
Specifically, we're going to rotate our strap out of the way until it no longer overlaps.
The rest of our bib should stay as it is, so let's start by making a list of points we need
to rotate.
However, there is a catch.
## Macros and auto-generated IDs
We have used the `round` macro to help us round the corners
of our strap, and it added a bunch of auto-generated points to our pattern. We need to
rotate these points too, but what are their names?
A macro will return the names of the things it created. So far, we have not captured
that return value, but if we did, it would look like this:
```mjs
{
"tipRightTop": {
"points": {
"start": "__macro_@freesewing/plugin-round_tipRightTop_start",
"cp1": "__macro_@freesewing/plugin-round_tipRightTop_cp1",
"cp2": "__macro_@freesewing/plugin-round_tipRightTop_cp2",
"end": "__macro_@freesewing/plugin-round_tipRightTop_end"
},
"paths": {
"path": "__macro_@freesewing/plugin-round_tipRightTop_path"
}
},
"tipRightBottom": {
"points": {
"start": "__macro_@freesewing/plugin-round_tipRightBottom_start",
"cp1": "__macro_@freesewing/plugin-round_tipRightBottom_cp1",
"cp2": "__macro_@freesewing/plugin-round_tipRightBottom_cp2",
"end": "__macro_@freesewing/plugin-round_tipRightBottom_end"
},
"paths": {
"path": "__macro_@freesewing/plugin-round_tipRightBottom_path"
}
}
}
```
Those names aren't very handy to remember. So I will rewrite this code a bit to
we'll capture these return values from the `round` macros and create
easy-to-remember points from them:
<Example tutorial caption="It looks the same as before, but now those macro points are accessible to us">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
// highlight-start
utils,
// highlight-end
macro,
part,
}) {
/*
* Construct the quarter neck opening
*/
let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta
do {
points.right = new Point(
tweak * measurements.head / 10,
0
)
points.bottom = new Point(
0,
tweak * measurements.head / 12
)
points.rightCp1 = points.right.shift(
90,
points.bottom.dy(points.right) / 2
)
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path()
.move(points.right)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
.hide()
delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
/*
* Construct the complete neck opening
*/
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
paths.neck = new Path()
.move(points.top)
.curve(points.topCp2, points.leftCp1, points.left)
.curve(points.leftCp2, points.bottomCp1, points.bottom)
.curve(points.bottomCp2, points.rightCp1, points.right)
.curve(points.rightCp2, points.topCp1, points.top)
.close()
.addClass('fabric')
/*
* Drawing the bib outline
*/
const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio
points.topLeft = new Point(
width / -2,
points.top.y - (width / 2 - points.right.x)
)
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
/*
* Shape the straps
*/
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
points.topLeft,
0.5
)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
// Round the straps
const strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
// highlight-start
/*
* Macros will return the auto-generated IDs
*/
const ids1 = {
tipRightTop: macro("round", {
id: "tipRightTop",
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
hide: false
}),
tipRightBottom: macro("round", {
id: "tipRightBottom",
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
hide: false
})
}
/*
* Create points from them with easy names
*/
for (const side in ids1) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
}
}
// highlight-end
/*
* Now, adapt our `rect` path so it's no longer a rectangle:
*/
paths.rect = new Path()
.move(points.edgeTop)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.line(points.bottomLeft)
.line(points.bottomRight)
.line(points.edgeRight)
.curve(points.edgeRightCp, points.edgeTopRightCp, points.edgeTop)
.close()
return part
}
```
</Example>
Once we have our list of points to rotate, we can rotate them. How far? Until the strap no longer overlaps.
<Example tutorial caption="It is looking pretty wonky now, but we'll deal with that next">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
utils,
macro,
part,
}) {
/*
* Construct the quarter neck opening
*/
let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta
do {
points.right = new Point(
tweak * measurements.head / 10,
0
)
points.bottom = new Point(
0,
tweak * measurements.head / 12
)
points.rightCp1 = points.right.shift(
90,
points.bottom.dy(points.right) / 2
)
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path()
.move(points.right)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
.hide()
delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
/*
* Construct the complete neck opening
*/
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
paths.neck = new Path()
.move(points.top)
.curve(points.topCp2, points.leftCp1, points.left)
.curve(points.leftCp2, points.bottomCp1, points.bottom)
.curve(points.bottomCp2, points.rightCp1, points.right)
.curve(points.rightCp2, points.topCp1, points.top)
.close()
.addClass('fabric')
// Drawing the bib outline
const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio
points.topLeft = new Point(
width / -2,
points.top.y - (width / 2 - points.right.x)
)
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
/*
* Shape the straps
*/
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
points.topLeft,
0.5
)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
// Round the straps
const strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
/*
* Macros will return the auto-generated IDs
*/
const ids1 = {
tipRightTop: macro("round", {
id: "tipRightTop",
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
hide: false
}),
tipRightBottom: macro("round", {
id: "tipRightBottom",
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
hide: false
})
}
/*
* Create points from them with easy names
*/
for (const side in ids1) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
}
}
// highlight-start
/*
* This is the list of points we need to rotate
* to move our strap out of the way
*/
const rotateThese = [
"edgeTopLeftCp",
"edgeTop",
"tipRight",
"tipRightTop",
"tipRightTopStart",
"tipRightTopCp1",
"tipRightTopCp2",
"tipRightTopEnd",
"tipRightBottomStart",
"tipRightBottomCp1",
"tipRightBottomCp2",
"tipRightBottomEnd",
"tipRightBottom",
"top",
"topCp2"
]
/*
* We're rotating all the points in
* the `rotateThese` array around
* the `edgeLeft` point.
*
* We're using increments of 1 degree
* until the `tipRightBottomStart` point
* is 1 mm beyond the center of our bib.
*/
while (points.tipRightBottomStart.x > -1) {
for (const p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
/*
* This is not needed
* we are merely adding it to show
* what the rotated path looks like
*/
macro("round", {
id: "showRightTop",
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
hide: false,
classes: 'contrast dotted',
})
macro("round", {
id: "showRightBottom",
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
hide: false,
classes: 'contrast dotted',
})
// highlight-end
/*
* Now, adapt our `rect` path so it's no longer a rectangle:
*/
paths.rect = new Path()
.move(points.edgeTop)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.line(points.bottomLeft)
.line(points.bottomRight)
.line(points.edgeRight)
.curve(points.edgeRightCp, points.edgeTopRightCp, points.edgeTop)
.close()
return part
}
```
</Example>

View file

@ -0,0 +1,172 @@
---
title: Completing the neck opening
order: 80
---
We've constructed the perfectly sized quarter neck, and we're going to use this
to create our complete neck path by flipping and mirroring it.
## Hiding our quarter neck opening
To make our code easier to understand, we're going to leave the `quarterNeck` path
as it is, and simply chose to not show it.
To accomplish this, we'll call the `hide()` method on our path:
<Example tutorial caption="A hidden path is not shown">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
part,
}) {
/*
* Construct the quarter neck opening
*/
let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta
do {
points.right = new Point(
tweak * measurements.head / 10,
0
)
points.bottom = new Point(
0,
tweak * measurements.head / 12
)
points.rightCp1 = points.right.shift(
90,
points.bottom.dy(points.right) / 2
)
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path()
.move(points.right)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
// highlight-start
.hide()
// highlight-end
delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
return part
}
```
</Example>
We're saying: _hide this path_. In other words, don't show it.
The path is still known, and we can still use it to calculate the length of the neck opening.
But it won't show up on screen or on the page.
## Create the complete neck opening
Now that we've hidden our homework, let's create the complete neck path.
As the neck opening is symmetrical, there's no need to re-calculate the points
on the other side. We can just flip them over, so to speak. And that's exactly
what we'll do.
Let's add some more points, and then construct the complete path for the neck
opening.
<Example tutorial caption="Our completed neck opening">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
part,
}) {
/*
* Construct the quarter neck opening
*/
let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta
do {
points.right = new Point(
tweak * measurements.head / 10,
0
)
points.bottom = new Point(
0,
tweak * measurements.head / 12
)
points.rightCp1 = points.right.shift(
90,
points.bottom.dy(points.right) / 2
)
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path()
.move(points.right)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
.hide()
delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
// highlight-start
/*
* Construct the complete neck opening
*/
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
paths.neck = new Path()
.move(points.top)
.curve(points.topCp2, points.leftCp1, points.left)
.curve(points.leftCp2, points.bottomCp1, points.bottom)
.curve(points.bottomCp2, points.rightCp1, points.right)
.curve(points.rightCp2, points.topCp1, points.top)
.close()
.addClass('fabric')
// highlight-end
return part
}
```
</Example>
To add the points, we're using the `Point.flipX()` and `Point.flipY()` methods
here. There's a few new Path methods too, like `close()` and `addClass()`.
Perhaps you can figure out what they do? If not, both [the Point
documentation](/reference/api/point/) and [the Path
documentation](/reference/api/path) have detailed info on all the methods
available, including these.

View file

@ -0,0 +1,22 @@
---
title: Conclusion (of part 2)
order: 99
---
You made it to the end of part 2, in which we got to do some real parametric design.
Wile our bib is a rather simple design, it did allow us to gain familiarity with FreeSewing's core API.
You have learned how to create points. And seen various way to manipulate them,
including some more advanced things like rotating a bunch of them out of the
way.
You've also learned how to draw paths, which are the lines and curves that make up our pattern.
And we've used macros which can help us with repetitive tasks.
What we've gotten so far is a perfectly suitable sewing pattern. You can print this,
and make a nice bib out of it.
But when we stick to these basics, FreeSewing doesn't really get a chance to shine.
For that, I recommend [Part 3 of this tutorial](/tutorials/pattern-design/part3) where
we'll go beyond the basics.

View file

@ -0,0 +1,176 @@
---
title: Constructing the neck opening
order: 60
---
Our goal is to construct an oval neck opening that has a circumference
that is the `head` measurements multiplied by the `neckRatio` option.
That might involve some trial and error. But since the neck opening will be symmetric
both horizontal and vertical, we only need to construct one quadrant.
## Destructuring measurements and options
We'll be adding some points to our pattern to do just that. But we want to have
access to our measurements and options to do so. For this, we first destructure
`measurements` and `options` so we can access them:
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
// highlight-start
measurements,
options,
// highlight-end
part,
}) {
return part
}
```
Great. Now let's get to work.
## Drawing our first path
Let's add some points, and use them to draw our first curve:
<Example tutorial caption="Our very first path forms a quarter of our neck opening">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
part,
}) {
// highlight-start
/*
* Construct the quarter neck opening
*/
points.right = new Point(
measurements.head / 10,
0
)
points.bottom = new Point(
0,
measurements.head / 12
)
points.rightCp1 = points.right.shift(
90,
points.bottom.dy(points.right) / 2
)
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path()
.move(points.right)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
// highlight-end
return part
}
```
</Example>
We've added some points to our part, and drawn our first path.
Let's look at each line in detail.
## Adding points
```js
points.right = new Point(
measurements.head / 10,
0
)
```
- We're adding a point named `right` to the `points` object which holds our
part's points
- We're using the Point constructor, which takes two arguments: The point's X
and Y coordinates in the 2-dimensional space
- The X value is `measurements.head / 10`
- The Y value is `0`
The creation of `points.bottom` is very similar, so let's skip to the next line:
```js
points.rightCp1 = points.right.shift(
90,
points.bottom.dy(points.right) / 2
)
```
- We're adding a point named `rightCp1`, which will become the _control point_
of the right part
- Instead of using the Point constructor, we're calling the `Point.shift()`
method on an existing point
- It takes two arguments: The angle to shift towards, and the distance
- We can see that we're shifting 90 degrees (that means up) but the distance
uses another method
- The `Point.dy()` method returns the delta along the Y axis between the point
we call it on and the point we pass it
- We shift half of the Y-delta
The next point is very similar again, except that this time we're shifting to
the right (0 degrees) for half of the X-delta between points `bottom` and
`right`.
:::tip
##### Further reading
The `Point.shift()` and `Point.dy()` are just the tip of the iceberg.
Points come with a bunch of these methods.
You can find them all in [the Point API docs](/reference/api/point/).
:::
## Adding paths
Adding points is typically merely a means to an end. And that end gets
introduced on the next line: Paths.
```js
paths.quarterNeck = new Path()
.move(points.right)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
```
- We're adding a path named `quarterNeck` to the `paths` object which holds our
part's paths
- We're using the Path constructor, which takes no arguments
- We're following up with a `Path.move()` call that takes one Point as argument
- Then, there's a `Path.curve()` call that takes 3 points as arguments
If you've read through the high-level [Design guide](/guides/designs) you
will have learned that paths always start with a `move()` operation. In this
case, we moved to our `right` points.
From there, we drew a cubic Bézier curve to our `bottom` point by using
`rightCp1` and `bottomCp2` as control points.
:::tip
Many of the methods in the FreeSewing API are *chainable* allowing you
to string them together like in this example.
:::
When all is said and done, we now have a quarter of our neck opening.
The only problem is, we have no guarantee whatsoever that this opening is the correct size.
Rather than hope it is the correct size, we'll make sure it is next.

View file

@ -0,0 +1,174 @@
---
title: Creating the closure
order: 91
---
Things are starting to look good, but we can't fit the bib over the baby's head like this.
So we must create a closure. We'll let the straps overlap at the end, and put in a snap
later.
## Using macros
To round the straps, we'll use something new: **a macro**. To use macros, we
need the `macro` method, which we can destructure to get access to it.
Macros are little helpers that automate things that would otherwise get rather
tedious. There are macros to add titles to our pattern, or grainline
indicators, a scalebox, and there's a macro to round corners. The `round`
macro.
:::note You can find more information on the `round` macro in [the macros docs](/reference/macros/round/).:::
We need a half circle here, but the `round` macro works on 90° angles, so
we'll use it twice. As such, we'll add some points to guide the macro, and
then put it to work.
Like our neck opening, we've only drawn half since we can simply copy the
points to the other side.
<Example tutorial caption="Now the straps overlap. Which doesn't work for a pattern as it would make it impossible to cut it out of a single piece of fabric. So let's deal with the overlap next.">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
// highlight-start
macro,
// highlight-end
part,
}) {
/*
* Construct the quarter neck opening
*/
let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta
do {
points.right = new Point(
tweak * measurements.head / 10,
0
)
points.bottom = new Point(
0,
tweak * measurements.head / 12
)
points.rightCp1 = points.right.shift(
90,
points.bottom.dy(points.right) / 2
)
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path()
.move(points.right)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
.hide()
delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
/*
* Construct the complete neck opening
*/
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
paths.neck = new Path()
.move(points.top)
.curve(points.topCp2, points.leftCp1, points.left)
.curve(points.leftCp2, points.bottomCp1, points.bottom)
.curve(points.bottomCp2, points.rightCp1, points.right)
.curve(points.rightCp2, points.topCp1, points.top)
.close()
.addClass('fabric')
/*
* Drawing the bib outline
*/
const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio
points.topLeft = new Point(
width / -2,
points.top.y - (width / 2 - points.right.x)
)
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
/*
* Shape the straps
*/
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
points.topLeft,
0.5
)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
// highlight-start
// Round the straps
const strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
macro("round", {
id: "tipRightTop",
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
hide: false
})
macro("round", {
id: "tipRightBottom",
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
hide: false
})
// highlight-end
/*
* Now, adapt our `rect` path so it's no longer a rectangle:
*/
paths.rect = new Path()
.move(points.edgeTop)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.line(points.bottomLeft)
.line(points.bottomRight)
.line(points.edgeRight)
.curve(points.edgeRightCp, points.edgeTopRightCp, points.edgeTop)
.close()
return part
}
```
</Example>
Notice that we always draw our path at the end after we've manipulated our points.

View file

@ -0,0 +1,99 @@
---
title: A part's draft method
order: 50
---
Time to turn our attention to the draft method of our part.
Inside our `src/bib.mjs` file, this is what it currently looks like:
```design/src/bib.mjs
function draftBib({ part }) {
return part
}
```
This is an empty skeleton for a draft method. A draft method should always
return the part object, and that's effectively the only thing it currently
does.
## Destructuring the function parameter
If you're not familiar with the `({ part })` syntax you see above, this is a
technique called *parameter destructuring* or more generally, [object
destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment).
The draft method receives only 1 parameter: An object that holds everything we
need to draft our method. Destructuring is a way to *pull things out of the
object into their own variable*. It saves us a bunch of typing as these two are
equivalent:
<Tabs>
<TabItem value='without' label="Without descructuring">
```design/src/bib.mjs
function draftBib(props) {
return props.part
}
```
</TabItem>
<TabItem value='with' label="With descructuring">
```design/src/bib.mjs
function draftBib({ part }) {
return part
}
```
</TabItem>
</Tabs>
As we'll make our way through this tutorial, we'll need more and more stuff, so
we'll be pulling it out of the object passed to the draft method via
*destructuring*.
:::note
If you're new to JavaScript, and don't intuitively _get this_, stick with it. It will become second nature soon enough.
:::
## Destructuring what we need to start drawing our bib
Change the function to look like this:
```design/src/bib.mjs
function draftBib({
// highlight-start
Path,
Point,
paths,
points,
// highlight-end
part,
}) {
return part
}
```
That's a bunch of new lines, but each of one gives us something we'll use in this
tutorial.
For a complete list of what you can access via destructuring like this, refer
to [the draft method reference documentation](/reference/api/part/draft).
Here's a brief summary of the things we've added above:
- `Path`: The Path constructor, allows us to create new Paths
- `Point`: The Point constructor, allows us to create new Points
- `points`: A container object to hold the part's points
- `paths`: A container object to hold the part's paths
:::tipRemember: Constructors start with a **C**apital letter:::
Long story short: These will make it possible for us to draw points and paths easily.
So let's go ahead and do that.

View file

@ -0,0 +1,145 @@
---
title: Drawing the bib outline
order: 88
---
With our neck opening in place, let us draw the basic outline of our bib.
<Example tutorial caption="Note how the neck opening is the same distance from the left, right, and top edge">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
part,
}) {
// Construct the quarter neck opening
let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta
do {
points.right = new Point(
tweak * measurements.head / 10,
0
)
points.bottom = new Point(
0,
tweak * measurements.head / 12
)
points.rightCp1 = points.right.shift(
90,
points.bottom.dy(points.right) / 2
)
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path()
.move(points.right)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
.hide()
delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
/*
* Construct the complete neck opening
*/
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
paths.neck = new Path()
.move(points.top)
.curve(points.topCp2, points.leftCp1, points.left)
.curve(points.leftCp2, points.bottomCp1, points.bottom)
.curve(points.bottomCp2, points.rightCp1, points.right)
.curve(points.rightCp2, points.topCp1, points.top)
.close()
.addClass('fabric')
// highlight-start
/*
* Drawing the bib outline
*/
const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio
points.topLeft = new Point(
width / -2,
points.top.y - (width / 2 - points.right.x)
)
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
paths.rect = new Path()
.move(points.topLeft)
.line(points.bottomLeft)
.line(points.bottomRight)
.line(points.topRight)
.line(points.topLeft)
.close()
.addClass('fabric')
// highlight-end
return part
}
```
</Example>
First thing we did was create the `width` and `length` variables to
save ourselves some typing:
```js
const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio
```
Both the length and width of our bib are a factor of the head circumference.
This way, our bib size will adapt to the size of the baby, and the user can tweak
the length and width by playing with the options we added to the pattern.
Once we have our variables, we're adding some new points, and a second path called `rect`.
```js
points.topLeft = new Point(
width / -2,
points.top.y - (width / 2 - points.right.x)
)
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
paths.rect = new Path()
.move(points.topLeft)
.line(points.bottomLeft)
.line(points.bottomRight)
.line(points.topRight)
.line(points.topLeft)
.close()
.addClass('fabric')
```
We're calculating the `topLeft` point so that the top edge of our bib
and the sides are equidistant from the neck opening.
We didn't have to do that. But it looks nicely balanced this way.

View file

@ -0,0 +1,311 @@
---
title: Drawing the straps
order: 93
---
All we have to do now is flip a bunch of points on the other side,
and create one single path that follows our bib outline.
And as we now have one path to draw the bib, we can (and should)
remove the earlier paths we drew to see what we are doing.
The `round` macro we added earlier is still required to calculate the points we
need to construct the half-circle. But we don't want it to draw the half-circle
path. As it happens, that is the default behaviour, so we merely have to remove
its `hidden: false` property.
<Example tutorial caption="It is starting to look good. But this sharp corners at the bottom don't exactly say baby, do they?">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
utils,
macro,
part,
}) {
/*
* Construct the neck opening
*/
let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta
do {
points.right = new Point(
tweak * measurements.head / 10,
0
)
points.bottom = new Point(
0,
tweak * measurements.head / 12
)
points.rightCp1 = points.right.shift(
90,
points.bottom.dy(points.right) / 2
)
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path()
.move(points.right)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
.hide()
delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
/*
* Construct the complete neck opening
*/
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
// strikeout-start
/* Remove this path
paths.neck = new Path()
.move(points.top)
.curve(points.topCp2, points.leftCp1, points.left)
.curve(points.leftCp2, points.bottomCp1, points.bottom)
.curve(points.bottomCp2, points.rightCp1, points.right)
.curve(points.rightCp2, points.topCp1, points.top)
.close()
.addClass('fabric')
*/
// strikeout-end
// Drawing the bib outline
const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio
points.topLeft = new Point(
width / -2,
points.top.y - (width / 2 - points.right.x)
)
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
/*
* Shape the straps
*/
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
points.topLeft,
0.5
)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
// Round the straps
const strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
/*
* Macros will return the auto-generated IDs
*/
const ids1 = {
tipRightTop: macro("round", {
id: "tipRightTop",
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
// strikeout-start
/* Remove this to have the macro
* only create the points we need
* and not draw a path
hide: false
*/
// strikeout-end
}),
tipRightBottom: macro("round", {
id: "tipRightBottom",
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
// strikeout-start
/* Remove this to have the macro
* only create the points we need
* and not draw a path
hide: false
*/
// strikeout-end
})
}
/*
* Create points from them with easy names
*/
for (const side in ids1) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
}
}
/*
* This is the list of points we need to rotate
* to move our strap out of the way
*/
const rotateThese = [
"edgeTopLeftCp",
"edgeTop",
"tipRight",
"tipRightTop",
"tipRightTopStart",
"tipRightTopCp1",
"tipRightTopCp2",
"tipRightTopEnd",
"tipRightBottomStart",
"tipRightBottomCp1",
"tipRightBottomCp2",
"tipRightBottomEnd",
"tipRightBottom",
"top",
"topCp2"
]
/*
* We're rotating all the points in
* the `rotateThese` array around
* the `edgeLeft` point.
*
* We're using increments of 1 degree
* until the `tipRightBottomStart` point
* is 1 mm beyond the center of our bib.
*/
while (points.tipRightBottomStart.x > -1) {
for (const p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
// strikeout-start
/* Remove this repetition
macro("round", {
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
prefix: "tipRightTop",
hide: false,
class: 'contrast dotted',
})
macro("round", {
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
prefix: "tipRightBottom",
hide: false,
class: 'contrast dotted',
})
paths.rect = new Path()
.move(points.edgeTop)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.line(points.bottomLeft)
.line(points.bottomRight)
.line(points.edgeRight)
.curve(points.edgeRightCp, points.edgeTopRightCp, points.edgeTop)
.close()
*/
// strikeout-end
// highlight-start
// Add points for second strap
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX()
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
// Create one path for the bib outline
paths.seam = new Path()
.move(points.edgeLeft)
.line(points.bottomLeft)
.line(points.bottomRight)
.line(points.edgeRight)
.curve(
points.edgeRightCp,
points.edgeTopRightCp,
points.tipLeftTopStart
)
.curve(
points.tipLeftTopCp1,
points.tipLeftTopCp2,
points.tipLeftTopEnd
)
.curve(
points.tipLeftBottomCp1,
points.tipLeftBottomCp2,
points.tipLeftBottomEnd
)
.curve(
points.topCp1,
points.rightCp2,
points.right
)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
.curve(
points.bottomCp1,
points.leftCp2,
points.left
)
.curve(
points.leftCp1,
points.topCp2,
points.tipRightBottomEnd
)
.curve(
points.tipRightBottomCp2,
points.tipRightBottomCp1,
points.tipRightBottomStart
)
.curve(
points.tipRightTopCp2,
points.tipRightTopCp1,
points.tipRightTopStart
)
.curve(
points.edgeTopLeftCp,
points.edgeLeftCp,
points.edgeLeft
)
.close()
.addClass("fabric")
// highlight-end
return part
}
```
</Example>

View file

@ -0,0 +1,96 @@
---
title: Fitting the neck opening
order: 70
---
We are not going to create some opening that we _hope_ is the right size, we're
going to make sure it is. Here's how we'll make sure the neck opening is _just
right_:
<Example tutorial caption="It might look the same as before, but now it's just right">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
part,
}) {
/*
* Construct the quarter neck opening
*/
// highlight-start
let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta
do {
// highlight-end
points.right = new Point(
// highlight-start
tweak * measurements.head / 10,
// highlight-end
0
)
points.bottom = new Point(
0,
// highlight-start
tweak * measurements.head / 12
// highlight-end
)
points.rightCp1 = points.right.shift(
90,
points.bottom.dy(points.right) / 2
)
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path()
.move(points.right)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
// highlight-start
delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
// highlight-end
return part
}
```
</Example>
We've added a few new variables:
- `tweak`: A _tweak factor_ that we'll use to increase or decrease the neck
opening by making it more or less than 1
- `target`: How long our (quarter) neck opening should be
- `delta`: How far we're off. Positive numbers mean it's too long, negative
means too short
Now that we know what `target` is, we construct our path as we did before. But
this time around, we multiply our point coordinates with our `tweak` variable
(1 at the start).
Then, we compare our `target` to the result of `paths.neck.length()` which —
you guessed it — returns the length of our neck path.
If the delta is positive, our path is too long and we reduce the tweak factor.
If the delta is negative, our path is too short and we increase the tweak
factor.
We keep on doing this until `Math.abs(delta)` is less than 1. Meaning that we
are within 1 mm of our target value.
Now that we're happy with the length of our quarter neck opening, let's
complete the entire neck opening.

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

View file

@ -0,0 +1,132 @@
---
title: Creating a new design
order: 10
---
The development environment has already setup various designs for us.
Since I am using the **From scratch** template, the files I want to edit live
in `design/from-scratch`.
The design's main file is `design/from-scratch/src/index.mjs`, and our bib part
will live in `design/from-scratch/src/bib.mjs`.
This `bib.mjs` file is where will be doing most of the work here in part 2 of this
tutorial. But let's start with the `index.mjs` file as an appetizer, because this
is where a new FreeSewing design is brought to life. It looks like this:
```src/index.mjs
import { Design } from '@freesewing/core'
import { i18n } from '../i18n/index.mjs'
import { bib } from './bib.mjs'
/*
* Create the design
*/
const FromScratch = new Design({
data: {
name: 'fromScratch',
version: '0.0.1',
},
parts: [bib],
})
export { bib, FromScratch, i18n }
```
Not too intimidating, is it?
## Imports
At the top of the file, we have a bunch of *imports*:
```src/index.mjs
import { Design } from '@freesewing/core'
import { i18n } from '../i18n/index.mjs'
import { bib } from './bib.mjs'
```
An `import` is how JavaScript loads code from a different file. \
It essentially says: _import **something** from **somewhere**_:
| Something | Somewhere | Description |
| ---------:|:--------- |:----------- |
| `Design` | `@freesewing/core` | Loads the `Design` constructor from FreeSewing's core library |
| `i18n` | `../i18n/index.mjs` | Loads `i18n` from the `index.mjs` file in the `i18n` one level higher (these are the translations) |
| `bib` | `./bib.mjs` | Loads `bib` from the `bib.mjs` file in the same folder (this is our part) |
As you can see, the *somewhere* can be different things. A local file like in
lines 2 and 3, or a package published on
[NPM](https://www.npmjs.com/package/@freesewing/core), in line 1.
It's nothing for you to worry about too much at this point, but it does help us
understand what's going on at the bottom of our file.
## Exports
The bottom holds a single `export` line:
```src/index.mjs
export { bib, FromScratch, i18n }
```
When we `export` things from our code, we allow others to `import` those things
for re-use in their code. That's how it works.
These are named exports. We are exporting three things:
- `bib` is our part. We are exporting it so people can re-use just this part.
- `FromScratch` is our complete design. Exporting it means people can use it.
- `i18n` are the translations. We are exporting it so people can load them to use with their own translation solution.
If you are not familiar with this syntax, you'll get the hang of it soon enough.
## Design constructor
Finally, the most interesting part of this file is the middle part where we are
creating a new design:
```src/index.mjs
const FromScratch = new Design({
data: {
name: 'fromScratch',
version: '0.0.1',
},
parts: [bib],
})
```
The `Design` that we imported on line 1 is a so-called **constructor**.
A constructor is a function that can create things out of nothing. Or,
to be more accurate, that you can use with the `new` keyword.
:::tip
It's a convention that constructor names start with an **C**apital letter.
:::
We are passing some info to this `Design` function, but the `data` we are
passing is optional. If we strip that away for a moment, and don't bother
assigning the result to a variable, we reveal the essence of what it takes to
create a new FreeSewing design:
```src/index.mjs
new Design({
parts: [bib],
})
```
In several places in this documentation, I will mention that *a design is not
much more than a thin wrapper around parts*. But I feel nothing drives
that point home like seeing it in code like this.
To create a new design, we call the `Design` constructor and give it a list
(an array) or parts.
:::note RELATED
Refer to [the design reference documentation](/reference/api/design) for
all details about what you can pass to the Design constructor.
:::
That's it. So let's look at where the real action is next: Our first part.

View file

@ -0,0 +1,89 @@
---
title: Creating a part
order: 20
---
Much like garments themselves, patterns are made up of _parts_.
Most patterns will have multiple parts. A sleeve, a back part, the collar, and
so on. The pattern you create today is very simple, and only has one part: the bib.
:::tip
It's a good idea to keep each part in its own file. You don't *have to* do
this, but it's a good habit to get into.
:::
## bib.mjs
I am going to use the **From scratch** template. So the files I want to edit live
in `design/from-scratch`.
Our part lives in `design/from-scratch/src/bib.mjs`, and it currently looks like this:
```src/bib.mjs
function draftBib({ part }) {
return part
}
export const bib = {
name: 'fromscratch.bib',
draft: draftBib,
}
```
## The part object
Each part in FreeSewing is an object that describes the part, and has a `draft`
method to do the actual work of drafting the part.
The only mandatory keys on a part object are `name` and `draft`.
:::note RELATED
Refer to [the part reference documentation](/reference/api/part) for
all details about configuring the part object
:::
:::note
A design in FreeSewing is a thin wrapper around a collection of parts.
Each parts stands on its own, and parts from various designs can be combined
to create other designs.
:::
### The part name
```src/bib.mjs
name: 'fromscratch.bib',
```
A part's `name` should be unique in a design. I used `fromscratch.bib` as the
name, because that makes sense.
:::warning
I **strongly** recommend that you apply the same `designName.partName` naming scheme in all your parts.
This avoids naming conflicts when mixing parts from various designs to create a new design.
:::
### The part's draft method
```src/bib.mjs
draft: draftBib,
```
The second mandatory key on the part object is `draft` which should hold the method that drafts the part.
In the example above, it refers to the `draftBib` function that was defined at the top of the same `bib.mjs` file.
For now this function doesn't do much, but that will change soon enough.
:::note
This structure of putting the draft method at the top of the file and
the part object at the bottom is a bit of a convention in FreeSewing.
:::
We'll take a deeper look at our part's draft method soon. For now, let's look at adding measurements to our part.

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

View file

@ -0,0 +1,47 @@
---
title: "Part 2: Parametric design"
---
Welcome to part 2 of this FreeSewing pattern design tutorial.
In this part I will show you how to design a bespoke
sewing pattern, start to finish.
:::tip
##### Before you start
This tutorial assumes you are familiar with the following:
- Scalable Vector Graphics
- The coordinate system
- Units in FreeSewing
- Cubic Bézier curves
Which is a lot to assume. So if you'd like you can take a quick detour
via our [Before you start guide](/guides/prerequisites). \
It's very short, but covers some basic
terminology and concepts that we'll use throughout this guide.
:::
## Pick a template
The FreeSewing development environment ships with several templates you
can start from. I recommend you start **From scratch** as you'll learn the most
that way. But you can also start from the **Tutorial** template in which case
you will already have the end result we are aiming for today:
<div class="grid grid-cols-2 gap-2">
!['From scratch' template](./fromscratch.png)
!['Tutorial' template](./tutorial.png)
</div>
Depending on the choice you made, you will need to edit files in a different folder.
- Edit files in `design/from-scratch` if you are using the **From scratch** template
- Edit files in `design/tutorial` if you are using the **Tutorial** template
You can choose either, or even switch back and forth between both.
To follow along step-by-step with the tutorial, go to the `design/from-scratch` folder.

View file

@ -0,0 +1,288 @@
---
title: Rounding the corners
order: 94
---
We already know how to round corners, we'll have the `round` macro take care of that for us.
With our corners rounded, we should also update our path.
Fortunately, we merely have to update the start of it.
<Example tutorial caption="The shape of our bib is now completed">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
utils,
macro,
part,
}) {
/*
* Construct the quarter neck opening
*/
let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta
do {
points.right = new Point(
tweak * measurements.head / 10,
0
)
points.bottom = new Point(
0,
tweak * measurements.head / 12
)
points.rightCp1 = points.right.shift(
90,
points.bottom.dy(points.right) / 2
)
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path()
.move(points.right)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
.hide()
delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
/*
* Construct the complete neck opening
*/
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
// Drawing the bib outline
const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio
points.topLeft = new Point(
width / -2,
points.top.y - (width / 2 - points.right.x)
)
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
/*
* Shape the straps
*/
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
points.topLeft,
0.5
)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
// Round the straps
const strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
/*
* Macros will return the auto-generated IDs
*/
const ids1 = {
tipRightTop: macro("round", {
id: "tipRightTop",
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
}),
tipRightBottom: macro("round", {
id: "tipRightBottom",
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
})
}
/*
* Create points from them with easy names
*/
for (const side in ids1) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
}
}
/*
* This is the list of points we need to rotate
* to move our strap out of the way
*/
const rotateThese = [
"edgeTopLeftCp",
"edgeTop",
"tipRight",
"tipRightTop",
"tipRightTopStart",
"tipRightTopCp1",
"tipRightTopCp2",
"tipRightTopEnd",
"tipRightBottomStart",
"tipRightBottomCp1",
"tipRightBottomCp2",
"tipRightBottomEnd",
"tipRightBottom",
"top",
"topCp2"
]
/*
* We're rotating all the points in
* the `rotateThese` array around
* the `edgeLeft` point.
*
* We're using increments of 1 degree
* until the `tipRightBottomStart` point
* is 1 mm beyond the center of our bib.
*/
while (points.tipRightBottomStart.x > -1) {
for (const p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
// Add points for second strap
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX()
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
// highlight-start
/*
* Round the bottom corners
* Macros will return the auto-generated IDs
*/
const ids2 = {
bottomLeft: macro("round", {
id: "bottomLeft",
from: points.topLeft,
to: points.bottomRight,
via: points.bottomLeft,
radius: points.bottomRight.x / 4,
}),
bottomRight: macro("round", {
id: "bottomRight",
from: points.bottomLeft,
to: points.topRight,
via: points.bottomRight,
radius: points.bottomRight.x / 4,
})
}
/*
* Create points from them with easy names
*/
for (const side in ids2) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
}
}
// highlight-end
// Create one path for the bib outline
paths.seam = new Path()
.move(points.edgeLeft)
// strikeout-start
/* We only need to replace the start
* with the new lines below
.line(points.bottomLeft)
.line(points.bottomRight)
*/
// strikeout-end
// highlight-start
.line(points.bottomLeftStart)
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
.line(points.bottomRightStart)
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
// highlight-end
.line(points.edgeRight)
.curve(
points.edgeRightCp,
points.edgeTopRightCp,
points.tipLeftTopStart
)
.curve(
points.tipLeftTopCp1,
points.tipLeftTopCp2,
points.tipLeftTopEnd
)
.curve(
points.tipLeftBottomCp1,
points.tipLeftBottomCp2,
points.tipLeftBottomEnd
)
.curve(
points.topCp1,
points.rightCp2,
points.right
)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
.curve(
points.bottomCp1,
points.leftCp2,
points.left
)
.curve(
points.leftCp1,
points.topCp2,
points.tipRightBottomEnd
)
.curve(
points.tipRightBottomCp2,
points.tipRightBottomCp1,
points.tipRightBottomStart
)
.curve(
points.tipRightTopCp2,
points.tipRightTopCp1,
points.tipRightTopStart
)
.curve(
points.edgeTopLeftCp,
points.edgeLeftCp,
points.edgeLeft
)
.close()
.addClass("fabric")
return part
}
```
</Example>

View file

@ -0,0 +1,153 @@
---
title: Shaping the straps
order: 90
---
Our straps should follow the neck opening, which isn't that hard to do.
We just need to keep the control points of our curves at similar proportions.
Which means, halfway between the start of the curve, and the corner of our rectangle.
:::note
For this, we'll be using a new method: `Point.shiftFractionTowards()`. We've already
used `Point.shift()` and there's also `Point.shiftTowards()` and `Point.shiftOutwards()`.
As always, [the API docs](/reference/api/point/) have all the details.
:::
<Example tutorial caption="All of a sudden, things are starting to look like a bib">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
part,
}) {
/*
* Construct the quarter neck opening
*/
let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta
do {
points.right = new Point(
tweak * measurements.head / 10,
0
)
points.bottom = new Point(
0,
tweak * measurements.head / 12
)
points.rightCp1 = points.right.shift(
90,
points.bottom.dy(points.right) / 2
)
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path()
.move(points.right)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
.hide()
delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
/*
* Construct the complete neck opening
*/
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
paths.neck = new Path()
.move(points.top)
.curve(points.topCp2, points.leftCp1, points.left)
.curve(points.leftCp2, points.bottomCp1, points.bottom)
.curve(points.bottomCp2, points.rightCp1, points.right)
.curve(points.rightCp2, points.topCp1, points.top)
.close()
.addClass('fabric')
/*
* Drawing the bib outline
*/
const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio
points.topLeft = new Point(
width / -2,
points.top.y - (width / 2 - points.right.x)
)
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
// strikeout-start
/*
* Remove this path
paths.rect = new Path()
.move(points.topLeft)
.line(points.bottomLeft)
.line(points.bottomRight)
.line(points.topRight)
.line(points.topLeft)
.close()
.addClass('fabric')
*/
// strikeout-end
// highlight-start
/*
* Shape the straps
*/
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
points.topLeft,
0.5
)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
/*
* Now, adapt our `rect` path so it's no longer a rectangle:
*/
paths.rect = new Path()
.move(points.edgeTop)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.line(points.bottomLeft)
.line(points.bottomRight)
.line(points.edgeRight)
.curve(points.edgeRightCp, points.edgeTopRightCp, points.edgeTop)
.close()
// highlight-end
return part
}
```
</Example>

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

View file

@ -0,0 +1,527 @@
---
title: Adding annotations
order: 20
---
Our pattern is still a little bit *bare*. It would be nice to add some *annotations* to it.
When I say *annotations* it's an umbrella term for things like text or other
bits of information that help the user understand the pattern.
## Adding snippets
Snippets are little re-useable things to embellish our pattern with.
Things like buttons or buttonholes, a logo, or snaps.
To use them, much like points and paths, we need to destructure both
the `Snippet` constructor as well as the `snippets` object to hold
our snippets.
We'll be using them below to add a `snap-stud`, `snap-socket`, and a `logo`
snippet.
:::note
You can find all possible snippets in [our documentation](/reference/api/snippet/).
:::
<Example previewFirst tutorial caption="This looks better">
```design/src/bib.mjs
function draftBib({
Point,
points,
Path,
paths,
utils,
measurements,
options,
macro,
// highlight-start
Snippet,
snippets,
// highlight-end
part,
}) {
/*
* Construct the neck opening
*/
const target = (measurements.head * options.neckRatio) / 4
let tweak = 1
let delta
do {
points.right = new Point((tweak * measurements.head) / 10, 0)
points.bottom = new Point(0, (tweak * measurements.head) / 12)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
paths.neck = new Path()
.move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
delta = paths.neck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
/*
* Construct the outline
*/
let width = measurements.head * options.widthRatio
let length = measurements.head * options.lengthRatio
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
/*
* Round the end of the straps
*/
let strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
/*
* Macros will return the auto-generated IDs
*/
const ids1 = {
tipRightTop: macro('round', {
id: 'tipRightTop',
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
}),
tipRightBottom: macro('round', {
id: 'tipRightBottom',
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids1) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
}
}
/*
* Rotate straps so they don't overlap
*/
let rotateThese = [
'edgeTopLeftCp',
'edgeTop',
'tipRight',
'tipRightTop',
'tipRightTopStart',
'tipRightTopCp1',
'tipRightTopCp2',
'tipRightTopEnd',
'tipRightBottomStart',
'tipRightBottomCp1',
'tipRightBottomCp2',
'tipRightBottomEnd',
'tipRightBottom',
'top',
'topCp2',
]
while (points.tipRightBottomStart.x > -1) {
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
/*
* Add points to anchor snaps on
*/
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
/*
* Mirror points to the other side
*/
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX()
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
points.snapRight = points.snapLeft.flipX()
/*
* Round the bottom of the bib
* Radius is fixed, but you could use an option for it)
*
* Macros will return the auto-generated IDs
*/
const ids2 = {
bottomLeft: macro('round', {
id: 'bottomLeft',
from: points.topLeft,
to: points.bottomRight,
via: points.bottomLeft,
radius: points.bottomRight.x / 4,
}),
bottomRight: macro('round', {
id: 'bottomRight',
from: points.bottomLeft,
to: points.topRight,
via: points.bottomRight,
radius: points.bottomRight.x / 4,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids2) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
}
}
/*
* Construct the path
*/
paths.seam = new Path()
.move(points.edgeLeft)
.line(points.bottomLeftStart)
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
.line(points.bottomRightStart)
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
.line(points.edgeRight)
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
.curve(points.topCp1, points.rightCp2, points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
.curve(points.bottomCp1, points.leftCp2, points.left)
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.close()
.attr('class', 'fabric')
// highlight-start
/*
*
* Annotations
*
*/
/*
* Add the snaps
*/
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
/*
* Add the logo
*/
points.logo = new Point(0, 0)
snippets.logo = new Snippet('logo', points.logo)
// highlight-end
return part
}
```
</Example>
## Setting the cutlist, adding a title and scalebox
We are also going to add a title and scalebox. For both of these we will use a macro.
The `title` and `scalebox` macros to be precise.
Before we add the title, we will also set the cutlist via a method that was added to the
store by one of the core plugins.
As a matter of fact, all of these snippets, macros, and store methods are provided by plugins.
For more details, [refer to the plugin guide](/guides/plugins).
<Example previewFirst tutorial caption="This looks way better">
```design/src/bib.mjs
function draftBib({
Point,
points,
Path,
paths,
utils,
measurements,
options,
macro,
complete,
snippets,
Snippet,
// highlight-start
store,
// highlight-end
part,
}) {
/*
* Construct the neck opening
*/
const target = (measurements.head * options.neckRatio) / 4
let tweak = 1
let delta
do {
points.right = new Point((tweak * measurements.head) / 10, 0)
points.bottom = new Point(0, (tweak * measurements.head) / 12)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
paths.neck = new Path()
.move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
delta = paths.neck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
/*
* Construct the outline
*/
let width = measurements.head * options.widthRatio
let length = measurements.head * options.lengthRatio
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
/*
* Round the end of the straps
*/
let strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
/*
* Macros will return the auto-generated IDs
*/
const ids1 = {
tipRightTop: macro('round', {
id: 'tipRightTop',
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
}),
tipRightBottom: macro('round', {
id: 'tipRightBottom',
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids1) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
}
}
/*
* Rotate straps so they don't overlap
*/
let rotateThese = [
'edgeTopLeftCp',
'edgeTop',
'tipRight',
'tipRightTop',
'tipRightTopStart',
'tipRightTopCp1',
'tipRightTopCp2',
'tipRightTopEnd',
'tipRightBottomStart',
'tipRightBottomCp1',
'tipRightBottomCp2',
'tipRightBottomEnd',
'tipRightBottom',
'top',
'topCp2',
]
while (points.tipRightBottomStart.x > -1) {
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
/*
* Add points to anchor snaps on
*/
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
/*
* Mirror points to the other side
*/
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX()
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
points.snapRight = points.snapLeft.flipX()
/*
* Round the bottom of the bib
* Radius is fixed, but you could use an option for it)
*
* Macros will return the auto-generated IDs
*/
const ids2 = {
bottomLeft: macro('round', {
id: 'bottomLeft',
from: points.topLeft,
to: points.bottomRight,
via: points.bottomLeft,
radius: points.bottomRight.x / 4,
}),
bottomRight: macro('round', {
id: 'bottomRight',
from: points.bottomLeft,
to: points.topRight,
via: points.bottomRight,
radius: points.bottomRight.x / 4,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids2) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
}
}
/*
* Construct the path
*/
paths.seam = new Path()
.move(points.edgeLeft)
.line(points.bottomLeftStart)
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
.line(points.bottomRightStart)
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
.line(points.edgeRight)
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
.curve(points.topCp1, points.rightCp2, points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
.curve(points.bottomCp1, points.leftCp2, points.left)
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.close()
.attr('class', 'fabric')
/*
*
* Annotations
*
*/
// highlight-start
/*
* Cut list
*/
store.cutlist.addCut({ cut: 1, from: 'fabric' })
// highlight-end
/*
* Add the snaps
*/
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
// highlight-start
/*
* Add the title
*/
points.title = points.bottom.shift(-90, 45)
macro('title', {
at: points.title,
nr: 1,
title: 'bib',
align: 'center',
scale: 0.8,
})
/*
* Add the scalebox
*/
points.scalebox = points.title.shift(-90, 65)
macro('scalebox', { at: points.scalebox })
// highlight-end
/*
* Add the logo
*/
points.logo = new Point(0, 0)
snippets.logo = new Snippet('logo', points.logo)
return part
}
```
</Example>

View file

@ -0,0 +1,561 @@
---
title: Dealing with laser cutters
order: 25
---
Laser cutters is merely an example of a situation where your user wants not the
complete detailed pattern with all annotations, but just the outlines.
Essentially what we had at the end of part 2 of this tutorial.
Since then, we've added a bunch of embellishments, and perhaps the user does
not want those.
Well, good news: there is a setting for that too. That setting is `complete`,
and more annotations will automatically take it into account.
For example, if you put a logo or title on the pattern, it will check the
`complete` setting and if it is `false` it will do nothing.
When we say we're going to *complete* our pattern, we mean we're going to add
things like a title and a scalebox and so on.
So while in most scenarios you don't have to worry about `complete`, you
should keep it in mind when you are adding text or paths to the design
that should not be shown on a non-complete pattern.
An example will make this more clear:
## Adding an indicator for bias tape
<Example tutorial previewFirst caption="A nice note about bias tape">
```design/src/bib.mjs
function draftBib({
Point,
points,
Path,
paths,
utils,
measurements,
options,
macro,
Snippet,
snippets,
store,
// highlight-start
complete,
// highlight-end
part,
}) {
/*
* Construct the neck opening
*/
const target = (measurements.head * options.neckRatio) / 4
let tweak = 1
let delta
do {
points.right = new Point((tweak * measurements.head) / 10, 0)
points.bottom = new Point(0, (tweak * measurements.head) / 12)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
paths.neck = new Path()
.move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
delta = paths.neck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
/*
* Construct the outline
*/
let width = measurements.head * options.widthRatio
let length = measurements.head * options.lengthRatio
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
/*
* Round the end of the straps
*/
let strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
/*
* Macros will return the auto-generated IDs
*/
const ids1 = {
tipRightTop: macro('round', {
id: 'tipRightTop',
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
}),
tipRightBottom: macro('round', {
id: 'tipRightBottom',
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids1) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
}
}
/*
* Rotate straps so they don't overlap
*/
let rotateThese = [
'edgeTopLeftCp',
'edgeTop',
'tipRight',
'tipRightTop',
'tipRightTopStart',
'tipRightTopCp1',
'tipRightTopCp2',
'tipRightTopEnd',
'tipRightBottomStart',
'tipRightBottomCp1',
'tipRightBottomCp2',
'tipRightBottomEnd',
'tipRightBottom',
'top',
'topCp2',
]
while (points.tipRightBottomStart.x > -1) {
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
/*
* Add points to anchor snaps on
*/
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
/*
* Mirror points to the other side
*/
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX()
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
points.snapRight = points.snapLeft.flipX()
/*
* Round the bottom of the bib
* Radius is fixed, but you could use an option for it)
*
* Macros will return the auto-generated IDs
*/
const ids2 = {
bottomLeft: macro('round', {
id: 'bottomLeft',
from: points.topLeft,
to: points.bottomRight,
via: points.bottomLeft,
radius: points.bottomRight.x / 4,
}),
bottomRight: macro('round', {
id: 'bottomRight',
from: points.bottomLeft,
to: points.topRight,
via: points.bottomRight,
radius: points.bottomRight.x / 4,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids2) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
}
}
/*
* Construct the path
*/
paths.seam = new Path()
.move(points.edgeLeft)
.line(points.bottomLeftStart)
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
.line(points.bottomRightStart)
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
.line(points.edgeRight)
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
.curve(points.topCp1, points.rightCp2, points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
.curve(points.bottomCp1, points.leftCp2, points.left)
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.close()
.attr('class', 'fabric')
/*
*
* Annotations
*
*/
/*
* Cut list
*/
store.cutlist.addCut({ cut: 1, from: 'fabric' })
/*
* Add the snaps
*/
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
// highlight-start
/*
* Add the bias tape
*/
if (complete)
paths.bias = paths.seam
.offset(-5)
.addClass('note dashed')
.addText('tutorial:finishWithBiasTape', 'center fill-note')
// highlight-end
/*
* Add the title
*/
points.title = points.bottom.shift(-90, 45)
macro('title', {
at: points.title,
nr: 1,
title: 'bib',
align: 'center',
scale: 0.8,
})
/*
* Add the scalebox
*/
points.scalebox = points.title.shift(-90, 65)
macro('scalebox', { at: points.scalebox })
/*
* Add the logo
*/
points.logo = new Point(0, 0)
snippets.logo = new Snippet('logo', points.logo)
return part
}
```
</Example>
And here is the exact same thing, but withe `complete` setting set to `false`:
<Example tutorial previewFirst settings="complete: false" caption="Same code, but with complete disabled">
```design/src/bib.mjs
function draftBib({
Point,
points,
Path,
paths,
utils,
measurements,
options,
macro,
Snippet,
snippets,
store,
// highlight-start
complete,
// highlight-end
part,
}) {
/*
* Construct the neck opening
*/
const target = (measurements.head * options.neckRatio) / 4
let tweak = 1
let delta
do {
points.right = new Point((tweak * measurements.head) / 10, 0)
points.bottom = new Point(0, (tweak * measurements.head) / 12)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
paths.neck = new Path()
.move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
delta = paths.neck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
/*
* Construct the outline
*/
let width = measurements.head * options.widthRatio
let length = measurements.head * options.lengthRatio
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
/*
* Round the end of the straps
*/
let strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
/*
* Macros will return the auto-generated IDs
*/
const ids1 = {
tipRightTop: macro('round', {
id: 'tipRightTop',
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
}),
tipRightBottom: macro('round', {
id: 'tipRightBottom',
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids1) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
}
}
/*
* Rotate straps so they don't overlap
*/
let rotateThese = [
'edgeTopLeftCp',
'edgeTop',
'tipRight',
'tipRightTop',
'tipRightTopStart',
'tipRightTopCp1',
'tipRightTopCp2',
'tipRightTopEnd',
'tipRightBottomStart',
'tipRightBottomCp1',
'tipRightBottomCp2',
'tipRightBottomEnd',
'tipRightBottom',
'top',
'topCp2',
]
while (points.tipRightBottomStart.x > -1) {
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
/*
* Add points to anchor snaps on
*/
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
/*
* Mirror points to the other side
*/
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX()
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
points.snapRight = points.snapLeft.flipX()
/*
* Round the bottom of the bib
* Radius is fixed, but you could use an option for it)
*
* Macros will return the auto-generated IDs
*/
const ids2 = {
bottomLeft: macro('round', {
id: 'bottomLeft',
from: points.topLeft,
to: points.bottomRight,
via: points.bottomLeft,
radius: points.bottomRight.x / 4,
}),
bottomRight: macro('round', {
id: 'bottomRight',
from: points.bottomLeft,
to: points.topRight,
via: points.bottomRight,
radius: points.bottomRight.x / 4,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids2) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
}
}
/*
* Construct the path
*/
paths.seam = new Path()
.move(points.edgeLeft)
.line(points.bottomLeftStart)
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
.line(points.bottomRightStart)
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
.line(points.edgeRight)
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
.curve(points.topCp1, points.rightCp2, points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
.curve(points.bottomCp1, points.leftCp2, points.left)
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.close()
.attr('class', 'fabric')
/*
*
* Annotations
*
*/
/*
* Cut list
*/
store.cutlist.addCut({ cut: 1, from: 'fabric' })
/*
* Add the snaps
*/
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
// highlight-start
/*
* Add the bias tape
*/
if (complete)
paths.bias = paths.seam
.offset(-5)
.addClass('note dashed')
.addText('tutorial:finishWithBiasTape', 'center fill-note')
// highlight-end
/*
* Add the title
*/
points.title = points.bottom.shift(-90, 45)
macro('title', {
at: points.title,
nr: 1,
title: 'bib',
align: 'center',
scale: 0.8,
})
/*
* Add the scalebox
*/
points.scalebox = points.title.shift(-90, 65)
macro('scalebox', { at: points.scalebox })
/*
* Add the logo
*/
points.logo = new Point(0, 0)
snippets.logo = new Snippet('logo', points.logo)
return part
}
```
</Example>

View file

@ -0,0 +1,39 @@
---
title: Conclusion
order: 90
---
Congratulations, we have created our first pattern. And while it's arguably
rather simple, we have learned a bunch of things along the way.
## More reading material
- If you haven't done so already, read through [the design
guide](/guides/designs/) which provides a good overview of how designs work
under the hood
- Bookmark [the FreeSewing API docs](/reference/api/), they are your reference
every time you're not entirely certain how something works
- Have a look at [the pattern design best practices](/guides/best-practices/) for best practices
that will help you make the best possible patterns
## What to do next
Now that you have learned how to create a pattern, why don't you make one?
Think about what it is you would like to create, and just do it. If you get
stuck at any moment, or need some help or advice, you can [join our chat
room](https://discord.freesewing.org/) and we'll help you out.
:::note COMMENT (by joost)
##### How did I do?
You could do me a real favor by letting me know what you loved or hated about
this tutorial.
Were there areas that were not clear? Did I dwell too long on one topic, or
rushed through another one too quickly? Your feedback helps improve things,
so don't be shy and tell me what you think.
You can reach me at joost@freesewing.org
:::

View file

@ -0,0 +1,53 @@
---
title: Saving space (and trees) with exand
order: 40
---
There is one more way we like to save space (and trees): The `expand` setting.
The `expand` setting (which is true by default) indicates that user wants pattern
parts to be fully expanded.
It's common to reduce the amount of space required by reducing simple shapes like
rectangles to a description or cut things on the fold.
The `exand` setting gives the user control over this, at least insofar the designer
supports it. It's not relevant to our bib, but it's good to know it's there.
Here is an example snippets from [Aaron's arm binding](https://github.com/freesewing/freesewing/blob/develop/designs/aaron/src/arm-binding.mjs#L24):
```mjs
const w = store.get('bindingWidth')
const l = store.get('armBindingLength')
if (expand) {
store.flag.preset('expandIsOn')
} else {
// Expand is off, do not draw the part but flag this to the user
store.flag.note({
msg: `aaron:cutArmBinding`,
replace: {
width: units(w),
length: units(l),
},
suggest: {
text: 'flag:show',
icon: 'expand',
update: {
settings: ['expand', 1],
},
},
})
// Also hint about expand
store.flag.preset('expand')
return part.hide()
}
```
There's some stuff going on here that we'll learn next, but essentially if
`expand` is falsy, the part will hide itself with `part.hide()` and so it won't
be included on the pattern.
However, to avoid misunderstandings we want to inform the user about this.
That's what all those `store.flag` lines are about. We'll learn about then next.

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View file

@ -0,0 +1,343 @@
---
title: How to communicate to the user
order: 50
---
As a designer, there are times you want to bring something to the attention of
the user. I am not talking about generic information that can go in the
documentation, but rather a message that is tailored specifically to this
pattern, much like this pattern is specifically tailored to the user.
Doing so is possible with the various `store.flag` methods, and below is
our updated bib making use of this. It's important to realize that things
will look the same here. But if you load this pattern in the development
environment (or on FreeSewing.org for that matter) the user will see this:
![A message for the user](./flag.png)
It's a simple example, but I hope it gets the point across.
Finally, keep in mind that we are now straddling the world of the core library
and frontend integration. These messages won't do anything unless you have a
frontend the shows them.
In other words **core does not care**. We are merely storing data in the store
and relying on the frontend to show this data to the user. We merely offer
standard methods to do so, but you can choose to ignore this info, or show it
in a different way in your own frontend implementation.
<Example previewFirst tutorial caption="We flagged something for the user">
```design/src/bib.mjs
function draftBib({
Point,
points,
Path,
paths,
utils,
store,
measurements,
options,
macro,
Snippet,
snippets,
complete,
// highlight-start
units,
// highlight-end
part,
}) {
/*
* Construct the neck opening
*/
const target = (measurements.head * options.neckRatio) / 4
let tweak = 1
let delta
do {
points.right = new Point((tweak * measurements.head) / 10, 0)
points.bottom = new Point(0, (tweak * measurements.head) / 12)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
paths.neck = new Path()
.move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
delta = paths.neck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
/*
* Construct the outline
*/
let width = measurements.head * options.widthRatio
let length = measurements.head * options.lengthRatio
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
/*
* Round the end of the straps
*/
let strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
/*
* Macros will return the auto-generated IDs
*/
const ids1 = {
tipRightTop: macro('round', {
id: 'tipRightTop',
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
}),
tipRightBottom: macro('round', {
id: 'tipRightBottom',
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids1) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
}
}
/*
* Rotate straps so they don't overlap
*/
let rotateThese = [
'edgeTopLeftCp',
'edgeTop',
'tipRight',
'tipRightTop',
'tipRightTopStart',
'tipRightTopCp1',
'tipRightTopCp2',
'tipRightTopEnd',
'tipRightBottomStart',
'tipRightBottomCp1',
'tipRightBottomCp2',
'tipRightBottomEnd',
'tipRightBottom',
'top',
'topCp2',
]
while (points.tipRightBottomStart.x > -1) {
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
/*
* Add points to anchor snaps on
*/
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
/*
* Mirror points to the other side
*/
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX()
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
points.snapRight = points.snapLeft.flipX()
/*
* Round the bottom of the bib
* Radius is fixed, but you could use an option for it)
*
* Macros will return the auto-generated IDs
*/
const ids2 = {
bottomLeft: macro('round', {
id: 'bottomLeft',
from: points.topLeft,
to: points.bottomRight,
via: points.bottomLeft,
radius: points.bottomRight.x / 4,
}),
bottomRight: macro('round', {
id: 'bottomRight',
from: points.bottomLeft,
to: points.topRight,
via: points.bottomRight,
radius: points.bottomRight.x / 4,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids2) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
}
}
/*
* Construct the path
*/
paths.seam = new Path()
.move(points.edgeLeft)
.line(points.bottomLeftStart)
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
.line(points.bottomRightStart)
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
.line(points.edgeRight)
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
.curve(points.topCp1, points.rightCp2, points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
.curve(points.bottomCp1, points.leftCp2, points.left)
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.close()
.attr('class', 'fabric')
/*
*
* Annotations
*
*/
// highlight-start
/*
* Let the user know about the bias tape and fabric requirements
*/
store.flag.note({
msg: 'tutorial:biasTapeLength',
replace: {
l: units(paths.seam.length()),
},
})
// highlight-end
/*
* Cut list
*/
store.cutlist.addCut({ cut: 1, from: 'fabric' })
/*
* Add the snaps
*/
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
/*
* Add the bias tape
*/
if (complete)
paths.bias = paths.seam
.offset(-5)
.addClass('note dashed')
.addText('finishWithBiasTape', 'center fill-note')
/*
* Add the title
*/
points.title = points.bottom.shift(-90, 45)
macro('title', {
at: points.title,
nr: 1,
title: 'bib',
align: 'center',
scale: 0.8,
})
/*
* Add the scalebox
*/
points.scalebox = points.title.shift(-90, 65)
macro('scalebox', { at: points.scalebox })
/*
* Add the logo
*/
points.logo = new Point(0, 0)
snippets.logo = new Snippet('logo', points.logo)
/*
* Add dimensions
*/
macro('hd', {
id: 'wFull',
from: points.bottomLeftStart,
to: points.bottomRightEnd,
y: points.bottomLeft.y + 15,
})
macro('vd', {
id: 'hBottomToOpeningBottom',
from: points.bottomRightStart,
to: points.bottom,
x: points.bottomRight.x + 15,
})
macro('vd', {
id: 'hBottomToOpeningCenter',
from: points.bottomRightStart,
to: points.right,
x: points.bottomRight.x + 30,
})
macro('vd', {
id: 'hTotal',
from: points.bottomRightStart,
to: points.tipLeftTopStart,
x: points.bottomRight.x + 45,
})
macro('hd', {
id: 'wOpening',
from: points.left,
to: points.right,
y: points.left.y + 25,
})
macro('ld', {
id: 'wStrap',
from: points.tipLeftBottomEnd,
to: points.tipLeftTopStart,
d: -15,
})
return part
}
```
</Example>

View file

@ -0,0 +1,7 @@
---
title: Supporting translation
order: 80
---
:::note [FIXME]Write this section for v3:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View file

@ -0,0 +1,68 @@
---
title: Facilitating frontend integration
order: 60
---
Strictly speaking, this tutorial is about learning to use FreeSewing's core
library to do parametric design, and we made great strides in that regard.
But FreeSewing is a lot more than its core library, and you might be wondering
how your pattern options magically end up in the development environment under
**Design options**:
![Design options menu](./options.png)
To make this happen, we add extra information to the options configuration.
You can add anything you want, here is a made-up example:
```mjs
options: {
shipWith: {
dflt: 'pickUp',
list: ['pickUp', 'post', 'courier'],
menu: 'shipping',
extraNote: 'Pick-up Monday to Friday 10:00, to 19:00'
},
}
```
It's just a silly example, but there's two important take-aways here:
- You don't have to use options in your design. You can add options for things
that are not about the design, but that you still want to capture the user's
input for (like shipping preferences in this case).
- You can add extra properties to an option. Each option type has it's required
properties. But you can add more and use them as you see fit.
Which is exactly what we do at FreeSewing, so I'd like to mention the `menu` one:
## Setting `menu` on your option
If you set a `menu` property on your option, the FreeSewing frontend will use
this to organize your various options in a menu structure.
### Sub menus
You can a nested menu structure with dot-notation. So `style.pockets` will
create a `pockets` submenu under the `style` menu and put your option there.
### Hiding options
If your `menu` property holds a falsy value, the option will be hidden from the
menu.
### Hiding options conditionally
If the `menu` property of your option holds a function, that function will be called with the following signature:
```mjs
function(
settings, // The settings provided by the user
mergedOptions, // The user-provided options merged with the defaults
) {
// return menu value here
}
```
This is typically used to hide options conditionally.
:::note [FIXME]Include example:::

View file

@ -0,0 +1,344 @@
---
title: Supporting paperless patterns
order: 30
---
The goal of paperless patterns is to create a pattern that we don't need to
print to be able to use it. Saving paper is always a good thing, but it's
also a way to democratize access to patterns.
While more and more of humanity is on the internet, access to printers and
printing paper is often harder to come by, especially in developing countries.
So let's make the extra effort to make our bib design support paperless.
## The paperless setting
Users can request paperless patterns by setting [the `paperless`
setting](/reference/settings/paperless) to a *truthy* value.
With paperless enabled, FreeSewing will automatically render a grid for each
pattern part with metric or imperial markings, depending on the units requested
by the user.
Such a grid is already a good starting point. In addition, we'll be using
different macros to add *dimensions* to the pattern.
While the grid gets added automatically, the dimensions we have to add ourselves.
Thankfully, there's macros that can help us with that, specifically:
- The `hd` macro adds a horizontal dimension
- The `vd` macro adds a vertical dimension
- The `ld` macro adds a linear dimension
- The `pd` macro adds a path dimension that follows a given path
These macros will also adapt to the units chosen by the user (metric or imperial).
:::note
Refer to [the list of macros](/reference/macros/) for more details.
:::
<Example previewFirst tutorial paperless caption="Suddenly, a printer is very much optional">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
utils,
measurements,
options,
macro,
complete,
snippets,
Snippet,
store,
// highlight-start
paperless,
// highlight-end
part,
}) {
/*
* Construct the neck opening
*/
const target = (measurements.head * options.neckRatio) / 4
let tweak = 1
let delta
do {
points.right = new Point((tweak * measurements.head) / 10, 0)
points.bottom = new Point(0, (tweak * measurements.head) / 12)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
paths.neck = new Path()
.move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
delta = paths.neck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
/*
* Construct the outline
*/
let width = measurements.head * options.widthRatio
let length = measurements.head * options.lengthRatio
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
/*
* Round the end of the straps
*/
let strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
/*
* Macros will return the auto-generated IDs
*/
const ids1 = {
tipRightTop: macro('round', {
id: 'tipRightTop',
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
}),
tipRightBottom: macro('round', {
id: 'tipRightBottom',
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids1) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
}
}
/*
* Rotate straps so they don't overlap
*/
let rotateThese = [
'edgeTopLeftCp',
'edgeTop',
'tipRight',
'tipRightTop',
'tipRightTopStart',
'tipRightTopCp1',
'tipRightTopCp2',
'tipRightTopEnd',
'tipRightBottomStart',
'tipRightBottomCp1',
'tipRightBottomCp2',
'tipRightBottomEnd',
'tipRightBottom',
'top',
'topCp2',
]
while (points.tipRightBottomStart.x > -1) {
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
/*
* Add points to anchor snaps on
*/
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
/*
* Mirror points to the other side
*/
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX()
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
points.snapRight = points.snapLeft.flipX()
/*
* Round the bottom of the bib
* Radius is fixed, but you could use an option for it)
*
* Macros will return the auto-generated IDs
*/
const ids2 = {
bottomLeft: macro('round', {
id: 'bottomLeft',
from: points.topLeft,
to: points.bottomRight,
via: points.bottomLeft,
radius: points.bottomRight.x / 4,
}),
bottomRight: macro('round', {
id: 'bottomRight',
from: points.bottomLeft,
to: points.topRight,
via: points.bottomRight,
radius: points.bottomRight.x / 4,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids2) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
}
}
/*
* Construct the path
*/
paths.seam = new Path()
.move(points.edgeLeft)
.line(points.bottomLeftStart)
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
.line(points.bottomRightStart)
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
.line(points.edgeRight)
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
.curve(points.topCp1, points.rightCp2, points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
.curve(points.bottomCp1, points.leftCp2, points.left)
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.close()
.attr('class', 'fabric')
/*
*
* Annotations
*
*/
/*
* Cut list
*/
store.cutlist.addCut({ cut: 1, from: 'fabric' })
/*
* Add the snaps
*/
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
/*
* Add the bias tape
*/
if (complete)
paths.bias = paths.seam
.offset(-5)
.addClass('note dashed')
.addText('fronscratch:finishWithBiasTape', 'center fill-note')
/*
* Add the title
*/
points.title = points.bottom.shift(-90, 45)
macro('title', {
at: points.title,
nr: 1,
title: 'bib',
align: 'center',
scale: 0.8,
})
/*
* Add the scalebox
*/
points.scalebox = points.title.shift(-90, 65)
macro('scalebox', { at: points.scalebox })
/*
* Add the logo
*/
points.logo = new Point(0, 0)
snippets.logo = new Snippet('logo', points.logo)
// highlight-start
/*
* Add dimensions
*/
macro('hd', {
id: 'wFull',
from: points.bottomLeftStart,
to: points.bottomRightEnd,
y: points.bottomLeft.y + 15,
})
macro('vd', {
id: 'hBottomToOpeningBottom',
from: points.bottomRightStart,
to: points.bottom,
x: points.bottomRight.x + 15,
})
macro('vd', {
id: 'hBottomToOpeningCenter',
from: points.bottomRightStart,
to: points.right,
x: points.bottomRight.x + 30,
})
macro('vd', {
id: 'hTotal',
from: points.bottomRightStart,
to: points.tipLeftTopStart,
x: points.bottomRight.x + 45,
})
macro('hd', {
id: 'wOpening',
from: points.left,
to: points.right,
y: points.left.y + 25,
})
macro('ld', {
id: 'wStrap',
from: points.tipLeftBottomEnd,
to: points.tipLeftTopStart,
d: -15,
})
// highlight-end
return part
}
```
</Example>

View file

@ -0,0 +1,18 @@
---
title: "Part 3: Beyond the basics"
---
I'm excited about diving into part 3 with you, and I hope that a brief
overview of what we'll cover will get you excited too:
- Adding seam allowance
- Dealing with laser cutters
- Providing paperless patterns
- Limiting output through expand
- Facilitating frontend integration
- How to communicate to the user
- Testing your designs
- Supporting translation
If that sounds like something you'd like to learn more about, then let's dive
right in.

View file

@ -0,0 +1,77 @@
---
title: Adding seam allowance
order: 10
---
When adding seam allowance to a pattern, there are 3 things that come into play:
- Does the user want seam allowance?
- How much seam allowance does the user want?
- How can we add seam allowance?
Let's whip up a quick example for a moment to clarify how we will handle this:
<Example settings="sa: 10"caption="Some straight lines and some curves">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
// highlight-start
sa,
// highlight-end
part,
}) {
points.topLeft = new Point(0,0)
points.bottomRight = new Point(100,40)
points.topRight = new Point(points.bottomRight.x, points.topLeft.y)
points.bottomLeft = new Point(points.topLeft.x, points.bottomRight.y)
points.cp1 = new Point(50, 20)
points.cp2 = new Point(70, 60)
paths.shape = new Path()
.move(points.topLeft)
.line(points.bottomLeft)
.curve(points.cp1, points.cp2, points.bottomRight)
.line(points.topRight)
.line(points.topLeft)
.close()
.addClass('fabric')
// highlight-start
if (sa) paths.sa = paths.shape.offset(sa).addClass('fabric sa')
// highlight-end
return part
}
```
</Example>
As you can see from the source, we can destructure an `sa` variable (short for
seam allowance) that will hold either:
- `false` if the user does not want seam allowance
- A value in `mm` indicating how much seam allowance the user wants
To add seam allowance to our path, we just `offset` it by `sa` and add
some classes to it to style it. But, crucially, only if the user wants
seam allowance:
```mjs
if (sa) paths.sa = paths.shape
.offset(sa)
.addClass('fabric sa')
```
To refer back to our three question: Whether the user wants seam allowance, and
if so how much seam allowance is answered by the `sa` value passed to our draft
method.
And we add it with the `path.offset()` method which will offset the path.
Our bib pattern does not require seam allowance as it will be finished with
bias tape. But we can use this same technique to indicate that.
Before we do so though, we should talk about `complete`.

View file

@ -0,0 +1,147 @@
---
title: Testing your designs
order: 70
---
:::note [FIXME] Update this for v3 :::
With the basic outline of our pattern ready, now would be a good time
to test it to see how well it adapts to different measurements,
and the range of options we provided.
:::note [FIXME]
This page needs to be updated with screenshots from the v3 development
environment
:::
:::tip
###### No more grading
FreeSewing patterns are _bespoke_, which means that we don't need to
grade our pattern to provide a range of sizes. We should test our pattern
for different measurements and options to see how well it adapts.
:::
If testing our pattern sounds like a lot of work, we're in luck. FreeSewing can do it
for us. Click the **Test Design** link in the sidebar under the **View** title.
We have a number of ways to test our pattern:
- Test design options
- Test measurements
- Test measurements sets
The [API docs on sampling](/reference/api/pattern/sample) have all the details
on how this works, but for now we'll just look at the end result of each of
these.
## Testing pattern options
We used percentage options, which can vary between their minimum and maximum
value. For these tests, FreeSewing will divide that range into 10 steps and
draft our pattern for each step.
Click on any of the options we've added to our pattern, and our bib will be
drawn with that option sampled.
### lengthRatio
The `lengthRatio` option controls the length of our bib. Testing it confirms
that it only influences the length:
![This is what it should look like when we test the `lengthRatio`
option](test-option-lengthratio.png)
:::note [FIXME]Update screenshot for v3:::
### neckRatio
The `neckRatio` option will determine the size of the neck opening. For the
same `head` measurement, varying this option should result in bibs with
increasingly larger neck opening.
Testing it confirms this. We can also see that as the neck opening gets
smaller, we will rotate the straps further out of the way to avoid overlap:
![This is what it should look like when we test the `neckRatio`
option](test-option-neckratio.png)
:::note [FIXME]Update screenshot for v3:::
### widthRatio
The `widthRatio` option will determine the width of our bib. For the same
`head` measurement, varying this option should result in increasingly wider
bibs.
If we test it, we can see that it works as intended. But there's one thing that
perhaps requires our attention. Making the bib wider shortens the length from
the bottom of the neck opening to the bottom of the bib. Thereby making the
bib shorter when it's worn.
![This is what it should look like when we test the `widthRatio`
option](test-option-widthratio.png)
:::note [FIXME]Update screenshot for v3:::
:::note
Even if the _total length_ of the bib stays the same, the _useable length_
shortens when the bib is made wider. Users will not expect this, so it's
something that we should fix in our pattern.
Adjusting the pattern to make the `widthRatio` not influence the _useable
length_ of the bib is not covered in this tutorial. It is left _as an exercise
to the reader_.
:::
## Testing measurements
Testing a measurement will vary that measurement 10% up or down while leaving
everything else the same. This gives us the option to determine how any given
measurement is influencing the pattern.
For our bib, we only use one measurement, so it influences the entire pattern.
:::note [FIXME]Add screenshot:::
## Testing measurments sets
Whereas testing a measurement will only vary one individual measurement,
testing measurements sets will draft our pattern for different sets of
measurements.
On the surface, the result below is the same as our measurement test. But that
is because our bib only uses one measurement. So testing that one measurement
ends up being the same as testing a complete set of measurements.
But most patterns use multiple measurements, and we'll find this test gives
us insight into how our pattern will adapt to differently sized bodies.
:::note [FIXME]Add screenshot:::
## The antperson test
A special case of model testing is the so-called _antperson test_. It drafts
our pattern with a set of _typical_ measurements , and then drafts it again
with measurements that are 1/10th of those _typical_ measurements.
It is named after [the cartoon
character](https://en.wikipedia.org/wiki/Ant-Man_\(film\)) who can shrink, yet
somehow his suit still fits.
The purpose of the antperson test is to bring out areas in our pattern where
we made assumptions that will not properly scale. Many drafting books will
tell us to _add 3 cm there_ or _measure 2 inch to the right_. Those
instructions don't scale, and we should avoid them.
The best patterns will pass the antperson test with 2 patterns exactly the
same, where one will simply be 1/10th the scale of the other.
:::note [FIXME]Add screenshot:::
When we're happy with how our pattern passes these tests, it's time to
complete our design.

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 KiB

View file

@ -0,0 +1,48 @@
---
title: Pattern design tutorial
---
Hello there, and welcome to this FreeSewing pattern design tutorial.
My name is Joost, and in this tutorial I will show you
how to design a bespoke sewing pattern, start to finish.
This tutorial is divided into three parts, allowing you to speedrun or entirely
skip certain parts depending on your interests or prior experience:
## Part 1: Prerequisites
The first sections of this tutorial, [Part 1](/tutorials/pattern-design/part1),
deals with the prerequisites. Installing node, setting up the FreeSewing
development environment on your system, and so on. If you are familiar with
the JavaScript ecosystem, I can summarize that entire section in this one-liner
that sets up the FreeSewing development environment on your system:
```sh
npx @freesewing/new-design
```
## Part 2: Parametric design
In [Part 2](/tutorials/pattern-design/part2) I will show you how to design a
parametric sewing pattern with FreeSewing. We'll create a part, add a bunch of
points, draw lines and curves, and so on.
All the basic skills required to create a sewing pattern in code.
This is probably a part you do not want to skip, unless you have prior
experience with FreeSewing and are looking for a refresh on some of the more
advanced features covered in the next section.
## Part 3: Beyond the basics
There is more to FreeSewing patterns than meets the eye, and in [Part
3](/tutorials/pattern-design/part3) I will cover some of the ways you can add
further value to your designs.
This includes things like translation, supporting laser cutters, avoiding the
need to printing with so-called *paperless patterns*, as well as how you can
configure your pattern to integrate with FreeSewing.org, or your own
frontend.
You can follow this tutorial start to finish, or skip ahead and back, the
choice is yours.

View file

@ -0,0 +1,17 @@
---
title: Tutorials
order: zaa
---
You can find a list of all FreeSewing tutorials below:
<ReadMore />
:::note RELATED
##### What makes a tutorial a tutorial?
Tutorials are lessons that take you by the hand through a series of steps to complete a project.
:::