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.
226
sites/dev/docs/tutorials/getting-started-codespaces/readme.mdx
Normal 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)
|
||||
:::
|
17
sites/dev/docs/tutorials/getting-started-gitpod/readme.mdx
Normal 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
|
||||
:::
|
|
@ -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.
|
||||
|
||||
:::
|
|
@ -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
|
||||
|
||||
:::
|
|
@ -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.
|
|
@ -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.
|
||||
|
||||
:::
|
|
@ -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!
|
13
sites/dev/docs/tutorials/getting-started-linux/readme.mdx
Normal 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:::
|
|
@ -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.
|
||||
|
||||
:::
|
|
@ -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
|
||||
|
||||
:::
|
|
@ -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.
|
|
@ -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.
|
||||
|
||||
:::
|
|
@ -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.
|
|
@ -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!
|
18
sites/dev/docs/tutorials/getting-started-mac/readme.mdx
Normal 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 />
|
257
sites/dev/docs/tutorials/getting-started-vercel/readme.mdx
Normal 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
|
227
sites/dev/docs/tutorials/getting-started-windows/readme.mdx
Normal 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
|
||||
|
||||
:::
|
BIN
sites/dev/docs/tutorials/pattern-design/part1/new-design/nd.png
Normal file
After Width: | Height: | Size: 269 KiB |
|
@ -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:
|
||||
|
||||

|
||||
|
||||
:::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.
|
||||
|
||||
:::
|
|
@ -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!
|
33
sites/dev/docs/tutorials/pattern-design/part1/readme.mdx
Normal 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.
|
||||
|
BIN
sites/dev/docs/tutorials/pattern-design/part1/sde/header.png
Normal file
After Width: | Height: | Size: 34 KiB |
47
sites/dev/docs/tutorials/pattern-design/part1/sde/readme.mdx
Normal 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.
|
||||
|
||||

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

|
||||
|
||||
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.
|
||||
|
BIN
sites/dev/docs/tutorials/pattern-design/part1/sde/templates.png
Normal file
After Width: | Height: | Size: 217 KiB |
|
@ -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:
|
||||
|
||||

|
||||
|
||||
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).
|
After Width: | Height: | Size: 217 KiB |
|
@ -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.
|
||||
|
||||
:::
|
After Width: | Height: | Size: 229 KiB |
|
@ -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.
|
||||
|
|
@ -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>
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
@ -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>
|
|
@ -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.
|
BIN
sites/dev/docs/tutorials/pattern-design/part2/fromscratch.png
Normal file
After Width: | Height: | Size: 219 KiB |
|
@ -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.
|
||||
|
|
@ -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.
|
After Width: | Height: | Size: 277 KiB |
47
sites/dev/docs/tutorials/pattern-design/part2/readme.mdx
Normal 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">
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
</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.
|
|
@ -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>
|
|
@ -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>
|
BIN
sites/dev/docs/tutorials/pattern-design/part2/tutorial.png
Normal file
After Width: | Height: | Size: 308 KiB |
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
||||
:::
|
|
@ -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.
|
BIN
sites/dev/docs/tutorials/pattern-design/part3/flag/flag.png
Normal file
After Width: | Height: | Size: 47 KiB |
343
sites/dev/docs/tutorials/pattern-design/part3/flag/readme.mdx
Normal 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:
|
||||
|
||||

|
||||
|
||||
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>
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: Supporting translation
|
||||
order: 80
|
||||
---
|
||||
|
||||
:::note [FIXME]Write this section for v3:::
|
||||
|
BIN
sites/dev/docs/tutorials/pattern-design/part3/menu/options.png
Normal file
After Width: | Height: | Size: 75 KiB |
|
@ -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**:
|
||||
|
||||

|
||||
|
||||
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:::
|
|
@ -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>
|
18
sites/dev/docs/tutorials/pattern-design/part3/readme.mdx
Normal 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.
|
77
sites/dev/docs/tutorials/pattern-design/part3/sa/readme.mdx
Normal 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`.
|
147
sites/dev/docs/tutorials/pattern-design/part3/tests/readme.mdx
Normal 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:
|
||||
|
||||

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

|
||||
|
||||
:::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.
|
||||
|
||||

|
||||
|
||||
:::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.
|
After Width: | Height: | Size: 208 KiB |
After Width: | Height: | Size: 523 KiB |
After Width: | Height: | Size: 649 KiB |
48
sites/dev/docs/tutorials/pattern-design/readme.mdx
Normal 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.
|
17
sites/dev/docs/tutorials/readme.mdx
Normal 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.
|
||||
|
||||
:::
|
||||
|