1
0
Fork 0

chore: Updated tutorial for v3

This commit is contained in:
joostdecock 2023-09-30 14:04:18 +02:00
parent b5e1554e4f
commit 3bc5fe28de
47 changed files with 2635 additions and 1526 deletions

View file

@ -1,6 +1,6 @@
--- ---
title: Setting up the development environment title: Setting up the development environment
order: 100 order: 20
--- ---
FreeSewing provides a development environment that visualizes your design for you. FreeSewing provides a development environment that visualizes your design for you.
@ -12,13 +12,15 @@ npx @freesewing/new-design@next
``` ```
<Fixme compact>Remove `@next` suffix once v3 is in production</Fixme> <Fixme compact>Remove `@next` suffix once v3 is in production</Fixme>
It will ask some questions. It will ask if it is ok to install the development environment in a new folder
All the defaults will do, but here are the details: named `freesewing`. You can accept the default, or pick a different folder name
if you prefer.
- *What template would you like to use?* — Pick the default: **Tutorial** It will also ask what package manager you would like to use.
- *What package manager should we use?* — Pick the default: **npm**, unless you are certain you have **yarn** installed Here too the default (`npm`) is fine., unless you are certain you have **yarn** installed.
After answering these questions, files will be copied, dependencies installed, and requirements downloaded. 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> <Note>
@ -27,17 +29,17 @@ of dependencies that need to be downloaded.
</Note> </Note>
When it's ready, you can enter the `tutorial` directory that was just created and run `npm run dev`: When it's ready, you can enter the `freesewing` directory that was just created and run `npm run dev`:
```sh ```sh
cd tutorial cd freesewing
npm run dev npm run dev
``` ```
Or if you want to use yarn as package manager: Or if you want to use yarn as package manager:
```sh ```sh
cd tutorial cd freesewing
yarn dev yarn dev
``` ```
@ -47,10 +49,11 @@ If all goes well, we'll should see this landing page:
![The FreeSewing development environment](./nd.png) ![The FreeSewing development environment](./nd.png)
## Notes <Tip>
### Need help? ##### Need help?
If you run into any issues, head over to [FreeSewing.org/support](https://next.freesewing.org/support) 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. which lists the various ways in which you can get help.
</Tip>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

After

Width:  |  Height:  |  Size: 269 KiB

Before After
Before After

View file

@ -0,0 +1,47 @@
---
title: The FreeSewing development environment
order: 30
---
If you have been to FreeSewing.org the FreeSewing development environment will look familiar.
That's because under the hood, it re-uses the same building blocks.
At the top of the page is the header with a row of icons that lay out what is available to you.
![The icons in the header of the FreeSewing development environment](./header.png)
From left to right you can see:
- **Home** will take you to the home page / welcome page
- **Design** will offer you a list of templates to start a design from (more on this below)
- **Documentation** will show a page with links to our documentation
- **Code** will show a page with links to our source code
- **Support** will show a page with the various ways you can get help
- **Theme** allows you to change the theme (in other works the color scheme)
- **Language** allows you to change the language
- **Sign In** allows you to sign in to your FreeSewing account so you can use
your (and our) measurements sets while designing
## Design templates
If you click he **Design** icon it will show this menu:
![Design templates provided by the FreeSewing development environment](./templates.png)
It allows you to choose a desing 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 be recreating the desing in this tutorial.
I recommend the latter. You will learn (and remember) a lot more if you are activele engaging.

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

View file

@ -1,45 +1,32 @@
--- ---
title: Files and folder structure title: Folder structure
order: 110 order: 40
--- ---
Inside our `tutorial` folder, the `design/src` folder holds the source code for Inside the `freesewing` folder -- which might have a different name if that is
the new pattern we will create. the choice you made -- you will find a bunch of files and folders.
If you want to support internationalization in your design, your translations The one that matter is the `design` folder. In it, you will find the followin
go in the `design/i18n` folder. subfolders:
We can safely ignore all other files and folders, as they are part of the - `from-bella`
FreeSewing development environment. - `from-bent`
So feel free to skip ahead to [Our first part](/tutorials/pattern-design/our-first-part). - `from-breanna`
- `from-brian`
- `from-scratch`
- `from-titan`
- `tutorial`
## Notes Remember when you click the **Design** icon in the header it would bring up
this menu:
If you'd like to learn about those other files and folders, here's what they do: ![Design templates provided by the FreeSewing development environment](./templates.png)
### folders As you might have guessed by now, each of these options is contained in its
own subfolder under `designs`.
- `design`: Holds the source code for our design You can edit the files under `designs/[template]/src/` and the changes you make
- `lab`: Holds [React][react] hooks and components specific to the development environment will be reflected in the development environment.
- `node_modules`: Holds installed dependencies
- `pages`: Holds [NextJS][next] client-side routes, aka pages
- `public`: Holds pre-generated translation files
- `shared`: Holds files from FreeSewing's shared codebase for frontend development
### files Don't take my word for it though. Let's start doing exactly that
in [Part 2](/tutorials/pattern-design/part2).
- `next.config.mjs`: The [NextJS][next] configuration file
- `next-i18next.config.js`: The configuration file for [next-i18next][i18n] which handles translation within NextJS
- `package.json`: Every Node.js project has a [package.json][pkg] file which holds important metadata and lists dependencies
- `package-lock.json`: This *lockfile* will only exist if we use the npm package manager
- `postcss.config.js`: Configuration file for [PostCSS][postcss], a tool to transform CSS with JavaScript
- `tailwind.config.js`: Configuration file for the [TailwindCSS][tailwind] framework
- `yarn.lock`: This *lockfile* will only exist if we use [the yarn package manager][yarn]
[next]: https://nextjs.org/
[tailwind]: https://tailwindcss.com/
[postcss]: https://postcss.org/
[yarn]: https://yarnpkg.com/
[pkg]: https://docs.npmjs.com/cli/v8/configuring-npm/package-json
[react]: https://reactjs.org/
[i18n]: https://next.i18next.com

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

View file

@ -1,6 +1,6 @@
--- ---
title: Adding measurements title: Adding measurements
order: 130 order: 30
--- ---
FreeSewing is all about _made-to-measure_ sewing patterns -- or *parametric FreeSewing is all about _made-to-measure_ sewing patterns -- or *parametric
@ -18,19 +18,19 @@ So let's add it as a required measurement.
## Adding required measurements ## Adding required measurements
In our `design/src/bib.mjs` file, we will add a `measurements` property to the `bib` object. 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. This property will be an Array (a list) holding all required measurements for this part.
I am usign [*the official name* of the measurement](/reference/measurements) here. For head I am usign [*the official name* of the measurement](/reference/measurements) here. For head
circumference, that name is `head`. circumference, that name is `head`.
```design/src/bib.mjs ```src/bib.mjs
function draftBib({ part }) => { function draftBib({ part }) => {
return part return part
} }
export const bib = { export const bib = {
name: 'tutorial.bib', name: 'fromscratch.bib',
draft: draftBib, draft: draftBib,
// highlight-start // highlight-start
measurements: [ 'head' ], measurements: [ 'head' ],

View file

@ -1,6 +1,6 @@
--- ---
title: Adding options title: Adding options
order: 140 order: 40
--- ---
I have shown what our bib should look like, and added the _head_ measurement I have shown what our bib should look like, and added the _head_ measurement
@ -23,13 +23,13 @@ and the head circumference. Let's call it `neckRatio`.
For this, I will add the `options` property to our `bib` object: For this, I will add the `options` property to our `bib` object:
```design/src/bib.mjs ```src/bib.mjs
function draftBib({ part }) => { function draftBib({ part }) => {
return part return part
} }
export const bib = { export const bib = {
name: 'tutorial.bib', name: 'fromscratch.bib',
draft: draftBib, draft: draftBib,
measurements: [ 'head' ], measurements: [ 'head' ],
// highlight-start // highlight-start
@ -68,13 +68,7 @@ Instead, this `menu` property is there for the benefit FreeSewing's development
environment which will use this to build a menu structure for the various environment which will use this to build a menu structure for the various
options. options.
Each option type has a number of required properties. But in addition to that, This is covered in more detail in [Part 3](/tutorials/pattern-design/part3) of this tutorial.
you can add more to facilitate integrating with a front-end or other user
interface.
You will see that after adding this option, the development environment will
have a `fit` section under **Design Options**. This `menu` property is where
that is based on.
</Note> </Note>
@ -82,13 +76,13 @@ that is based on.
Let's do something similar for the width and length of our bib: Let's do something similar for the width and length of our bib:
```design/src/bib.mjs ```src/bib.mjs
function draftBib({ part }) => { function draftBib({ part }) => {
return part return part
} }
export const bib = { export const bib = {
name: 'tutorial.bib', name: 'fromScratch.bib',
draft: draftBib, draft: draftBib,
measurements: [ 'head' ], measurements: [ 'head' ],
options: { options: {

View file

@ -1,6 +1,6 @@
--- ---
title: Avoiding overlap title: Avoiding overlap
order: 220 order: 92
--- ---
While we've only drawn the end of one strap, it's pretty obvious they overlap, While we've only drawn the end of one strap, it's pretty obvious they overlap,
@ -11,9 +11,48 @@ Specifically, we're going to rotate our strap out of the way until it no longer
The rest of our bib should stay as it is, so let's start by making a list of points we need The rest of our bib should stay as it is, so let's start by making a list of points we need
to rotate. to rotate.
Once we have our list of points to rotate, we can rotate them. How far? Until the strap no longer overlaps. However, there is a catch.
<Example tutorial caption="It is looking pretty wonky now, but we'll deal with that next"> ## Macros and auto-gerenated 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 not those macro points are accessible to us">
```design/src/bib.mjs ```design/src/bib.mjs
function draftBib({ function draftBib({
Path, Path,
@ -22,6 +61,9 @@ function draftBib({
points, points,
measurements, measurements,
options, options,
// highlight-start
utils,
// highlight-end
macro, macro,
part, part,
}) { }) {
@ -107,20 +149,176 @@ function draftBib({
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y) points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y) points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
macro("round", { // highlight-start
from: points.edgeTop, /*
to: points.tipRight, * Macros will return the auto-generated IDs
via: points.tipRightTop, */
prefix: "tipRightTop", const ids1 = {
hide: false tipRightTop: macro("round", {
}) id: "tipRightTop",
macro("round", { from: points.edgeTop,
from: points.tipRight, to: points.tipRight,
to: points.top, via: points.tipRightTop,
via: points.tipRightBottom, hide: false
prefix: "tipRightBottom", }),
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
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() // Add this line
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)
paths.rect = new Path()
.move(points.topLeft)
.line(points.bottomLeft)
.line(points.bottomRight)
.line(points.topRight)
.line(points.topLeft)
.close()
.addClass('fabric')
// 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 // highlight-start
/* /*
@ -165,20 +363,20 @@ function draftBib({
* what the rotated path looks like * what the rotated path looks like
*/ */
macro("round", { macro("round", {
id: "showRightTop",
from: points.edgeTop, from: points.edgeTop,
to: points.tipRight, to: points.tipRight,
via: points.tipRightTop, via: points.tipRightTop,
prefix: "tipRightTop",
hide: false, hide: false,
class: 'contrast dotted', classes: 'contrast dotted',
}) })
macro("round", { macro("round", {
id: "showRightBottom",
from: points.tipRight, from: points.tipRight,
to: points.top, to: points.top,
via: points.tipRightBottom, via: points.tipRightBottom,
prefix: "tipRightBottom",
hide: false, hide: false,
class: 'contrast dotted', classes: 'contrast dotted',
}) })
// highlight-end // highlight-end

View file

@ -1,6 +1,6 @@
--- ---
title: Completing the neck opening title: Completing the neck opening
order: 180 order: 80
--- ---
We've constructed the perfectly sized quarter neck, and we're going to use this We've constructed the perfectly sized quarter neck, and we're going to use this
@ -25,20 +25,38 @@ function draftBib({
part, part,
}) { }) {
// Construct the quarter neck opening /*
* Construct the quarter neck opening
*/
const target = (measurements.head * options.neckRatio) /4
let tweak = 1 let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta let delta
do { do {
points.right = new Point(tweak * measurements.head / 10, 0) points.right = new Point(
points.bottom = new Point(0, tweak * measurements.head / 12) 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.rightCp1 = points.right.shift(
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2) 90,
points.bottom.dy(points.right) / 2
)
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path() paths.quarterNeck = new Path()
.move(points.right) .move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom) .curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
// highlight-start // highlight-start
.hide() .hide()
// highlight-end // highlight-end
@ -79,16 +97,17 @@ function draftBib({
part, part,
}) { }) {
// Construct the quarter neck opening /*
* Construct the quarter neck opening
*/
const target = (measurements.head * options.neckRatio) /4
let tweak = 1 let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta let delta
do { do {
points.right = new Point(tweak * measurements.head / 10, 0) points.right = new Point(tweak * measurements.head / 10, 0)
points.bottom = new Point(0, tweak * measurements.head / 12) points.bottom = new Point(0, tweak * measurements.head / 12)
points.rightCp1 = points.right.shift( 90, points.bottom.dy(points.right) / 2)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right)/2) points.bottomCp2 = points.bottom.shift( 0, points.bottom.dx(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2)
paths.quarterNeck = new Path() paths.quarterNeck = new Path()
.move(points.right) .move(points.right)
@ -101,7 +120,9 @@ function draftBib({
} while (Math.abs(delta) > 1) } while (Math.abs(delta) > 1)
// highlight-start // highlight-start
// Construct the complete neck opening /*
* Construct the complete neck opening
*/
points.rightCp2 = points.rightCp1.flipY() points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX() points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX() points.left = points.right.flipX()

View file

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

View file

@ -1,6 +1,6 @@
--- ---
title: Constructing the neck opening title: Constructing the neck opening
order: 160 order: 60
--- ---
Our goal is to construct an oval neck opening that has a circumference Our goal is to construct an oval neck opening that has a circumference
@ -51,18 +51,34 @@ function draftBib({
}) { }) {
// highlight-start // highlight-start
// Construct the quarter neck opening /*
points.right = new Point(measurements.head / 10, 0) * Construct the quarter neck opening
points.bottom = new Point(0, measurements.head / 12) */
points.right = new Point(
measurements.head / 10,
0
)
points.bottom = new Point(
0,
measurements.head / 12
)
points.rightCp1 = points.right points.rightCp1 = points.right.shift(
.shift(90, points.bottom.dy(points.right)/2) 90,
points.bottomCp2 = points.bottom points.bottom.dy(points.right) / 2
.shift(0, points.bottom.dx(points.right)/2) )
points.bottomCp2 = points.bottom.shift(
0,
points.bottom.dx(points.right) / 2
)
paths.quarterNeck = new Path() paths.quarterNeck = new Path()
.move(points.right) .move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom) .curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
// highlight-end // highlight-end
return part return part
@ -76,21 +92,26 @@ Let's look at each line in detail.
## Adding points ## Adding points
```js ```js
points.right = new Point(measurements.head / 10, 0) points.right = new Point(
measurements.head / 10,
0
)
``` ```
- We're adding a point named `right` to the `points` object which holds our - We're adding a point named `right` to the `points` object which holds our
part's points part's points
- We're using the Point constructor, which takes two arguments: The point's X - We're using the Point constructor, which takes two arguments: The point's X
and Y values and Y coordinates in the 2-dimensional space
- The X value is `measurements.head / 10` - The X value is `measurements.head / 10`
- The Y value is `0` - The Y value is `0`
The creation of `points.bottom` is very similar, so let's skip to the next line: The creation of `points.bottom` is very similar, so let's skip to the next line:
```js ```js
points.rightCp1 = points.right points.rightCp1 = points.right.shift(
.shift(90, points.bottom.dy(points.right)/2) 90,
points.bottom.dy(points.right) / 2
)
``` ```
- We're adding a point named `rightCp1`, which will become the _control point_ - We're adding a point named `rightCp1`, which will become the _control point_
@ -123,7 +144,11 @@ introduced on the next line: Paths.
```js ```js
paths.quarterNeck = new Path() paths.quarterNeck = new Path()
.move(points.right) .move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom) .curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
``` ```
- We're adding a path named `quarterNeck` to the `paths` object which holds our - We're adding a path named `quarterNeck` to the `paths` object which holds our
@ -139,6 +164,12 @@ case, we moved to our `right` points.
From there, we drew a cubic Bézier curve to our `bottom` point by using From there, we drew a cubic Bézier curve to our `bottom` point by using
`rightCp1` and `bottomCp2` as control points. `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.
</Tip>
When all is said and done, we now have a quarter of our neck opening. 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. The only problem is, we have no guarantee whatsoever that this opening is the correct size.

View file

@ -1,6 +1,6 @@
--- ---
title: Creating the closure title: Creating the closure
order: 210 order: 91
--- ---
Things are starting to look good, but we can't fit the bib over the baby's head like this. Things are starting to look good, but we can't fit the bib over the baby's head like this.
@ -41,28 +41,31 @@ function draftBib({
part, part,
}) { }) {
// Construct the quarter neck opening /*
* Construct the quarter neck opening
*/
const target = (measurements.head * options.neckRatio) /4
let tweak = 1 let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta let delta
do { do {
points.right = new Point(tweak * measurements.head / 10, 0) points.right = new Point(tweak * measurements.head / 10, 0)
points.bottom = new Point(0, tweak * measurements.head / 12) points.bottom = new Point(0, tweak * measurements.head / 12)
points.rightCp1 = points.right.shift( 90, points.bottom.dy(points.right) / 2)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right)/2) points.bottomCp2 = points.bottom.shift( 0, points.bottom.dx(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2)
paths.quarterNeck = new Path() paths.quarterNeck = new Path()
.move(points.right) .move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom) .curve(points.rightCp1, points.bottomCp2, points.bottom)
.hide() // Add this line .hide()
delta = paths.quarterNeck.length() - target delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99 if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02 else tweak = tweak * 1.02
} while (Math.abs(delta) > 1) } while (Math.abs(delta) > 1)
// Construct the complete neck opening /*
* Construct the complete neck opening
*/
points.rightCp2 = points.rightCp1.flipY() points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX() points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX() points.left = points.right.flipX()
@ -81,7 +84,9 @@ function draftBib({
.close() .close()
.addClass('fabric') .addClass('fabric')
// Drawing the bib outline /*
* Drawing the bib outline
*/
const width = measurements.head * options.widthRatio const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio const length = measurements.head * options.lengthRatio
@ -102,7 +107,9 @@ function draftBib({
.close() .close()
.addClass('fabric') .addClass('fabric')
// Shape the straps /*
* Shape the straps
*/
points.edgeLeft = new Point(points.topLeft.x, points.left.y) points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y) points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y) points.edgeTop = new Point(0, points.topLeft.y)
@ -115,7 +122,9 @@ function draftBib({
) )
points.edgeTopRightCp = points.edgeTopLeftCp.flipX() points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
// Now, adapt our `rect` path so it's no longer a rectangle: /*
* Now, adapt our `rect` path so it's no longer a rectangle:
*/
paths.rect = new Path() paths.rect = new Path()
.move(points.edgeTop) .move(points.edgeTop)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft) .curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
@ -134,17 +143,17 @@ function draftBib({
points.tipRightBottom = new Point(points.tipRight.x, points.top.y) points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
macro("round", { macro("round", {
id: "tipRightTop",
from: points.edgeTop, from: points.edgeTop,
to: points.tipRight, to: points.tipRight,
via: points.tipRightTop, via: points.tipRightTop,
prefix: "tipRightTop",
hide: false hide: false
}) })
macro("round", { macro("round", {
id: "tipRightBottom",
from: points.tipRight, from: points.tipRight,
to: points.top, to: points.top,
via: points.tipRightBottom, via: points.tipRightBottom,
prefix: "tipRightBottom",
hide: false hide: false
}) })
// highlight-end // highlight-end

View file

@ -1,12 +1,12 @@
--- ---
title: A part's draft method title: A part's draft method
order: 150 order: 50
--- ---
Time to turn our attention to the draft method of our part. Time to turn our attention to the draft method of our part.
Inside our `design/src/bib.mjs` file, this is what it currently looks like: Inside our `src/bib.mjs` file, this is what it currently looks like:
```design/src/bib.mjs ```src/bib.mjs
function draftBib({ part }) => { function draftBib({ part }) => {
return part return part
} }
@ -29,7 +29,7 @@ equivalent:
<Tabs tabs="Without destructuring, With destructuring"> <Tabs tabs="Without destructuring, With destructuring">
<Tab> <Tab>
```design/src/bib.mjs ```src/bib.mjs
function draftBib(props) { function draftBib(props) {
return props.part return props.part
@ -38,7 +38,7 @@ function draftBib(props) {
``` ```
</Tab> </Tab>
<Tab> <Tab>
```design/src/bib.mjs ```src/bib.mjs
function draftBib({ part }) { function draftBib({ part }) {
return part return part
@ -61,7 +61,7 @@ If you're new to JavaScript, and don't intuitively _get this_, stick with it. It
Change the function to look like this: Change the function to look like this:
```design/src/bib.mjs ```src/bib.mjs
function draftBib({ function draftBib({
// highlight-start // highlight-start
Path, Path,
@ -88,6 +88,8 @@ Here's a brief summary of the things we've added above:
- `points`: A container object to hold the part's points - `points`: A container object to hold the part's points
- `paths`: A container object to hold the part's paths - `paths`: A container object to hold the part's paths
<Tip compact>Remember: Constructures start with a **C**apital letter</Tip>
Long story short: These will make it possible for us to draw points and paths easily. Long story short: These will make it possible for us to draw points and paths easily.
So let's go ahead and do that. So let's go ahead and do that.

View file

@ -1,12 +1,12 @@
--- ---
title: Drawing the bib outline title: Drawing the bib outline
order: 190 order: 88
--- ---
With our neck opening in place, let us draw the basic outline of our bib. 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"> <Example tutorial caption="Note how the neck opening is the same distance from the left, right, and top edge">
```design/src/bib.mjs ```src/bib.mjs
function draftBib({ function draftBib({
Path, Path,
Point, Point,

View file

@ -23,13 +23,16 @@ function draftBib({
points, points,
measurements, measurements,
options, options,
utils,
macro, macro,
part, part,
}) { }) {
// Construct the quarter neck opening /*
* Construct the neck opening
*/
const target = (measurements.head * options.neckRatio) / 4
let tweak = 1 let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta let delta
do { do {
points.right = new Point(tweak * measurements.head / 10, 0) points.right = new Point(tweak * measurements.head / 10, 0)
@ -116,32 +119,47 @@ function draftBib({
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y) points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y) points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
macro("round", { /*
from: points.edgeTop, * Macros will return the auto-generated IDs
to: points.tipRight, */
via: points.tipRightTop, const ids1 = {
prefix: "tipRightTop", tipRightTop: macro("round", {
// strikeout-start id: "tipRightTop",
/* Remove this to have the macro from: points.edgeTop,
* only create the points we need to: points.tipRight,
* and not draw a path via: points.tipRightTop,
hide: false // strikeout-start
*/ /* Remove this to have the macro
// strikeout-end * only create the points we need
}) * and not draw a path
macro("round", { hide: false
from: points.tipRight, */
to: points.top, // strikeout-end
via: points.tipRightBottom, }),
prefix: "tipRightBottom", tipRightBottom: macro("round", {
// strikeout-start id: "tipRightBottom",
/* Remove this to have the macro from: points.tipRight,
* only create the points we need to: points.top,
* and not draw a path via: points.tipRightBottom,
hide: false // strikeout-start
*/ /* Remove this to have the macro
// strikeout-end * 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()
}
}
const rotateThese = [ const rotateThese = [
"edgeTopLeftCp", "edgeTopLeftCp",
"edgeTop", "edgeTop",

View file

@ -2,267 +2,44 @@
title: "Part 2: Parametric design" title: "Part 2: Parametric design"
--- ---
Hello there, and welcome to this FreeSewing pattern design tutorial. Welcome to part 2 of this FreeSewing pattern design tutorial.
My name is Joost, and in this tutorial I will show you In this part I will show you how to design a made-to-measure
how to design a made-to-measure sewing pattern, start to finish. sewing pattern, start to finish.
<Tip> <Tip>
##### Before you start ##### Before you start
If you haven't done so yet, read the [Before you start This tutorial assumes you are familiar with the following:
guide](/guides/prerequisites). It's very short, but covers some basic
- 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. terminology and concepts that we'll use throughout this guide.
</Tip> </Tip>
I will be designing a pattern for a baby bib. It's a very simple pattern, but ## Pick a template
that's ok. It is a tutorial after all. This will give us plenty to work with.
At the end of this tutorial, I will have created this pattern, and if you The FreeSewing development environment ships with several templates you
follow along, so will 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">
<Example tutorial="1" previewFirst="1" caption="Our end result"> !['From scratch' template](./fromscratch.png)
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
macro,
store,
complete,
snippets,
Snippet,
part,
}) {
/* !['Tutorial' template](./tutorial.png)
* 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) </div>
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2)
paths.quarterNeck = new Path() Depending on the choice you made, you will need to edit files in a different folder.
.move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
.hide() // Add this line
delta = paths.quarterNeck.length() - target - Edit files in `design/from-scratch` if you are using the **From scratch** template
if (delta > 0) tweak = tweak * 0.99 - Edit files in `design/tutorial` if you are using the **Tutorial** template
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
/* You can choose either, or even switch back and forth between both.
* 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)
macro("round", {
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
prefix: "tipRightTop",
})
macro("round", {
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
prefix: "tipRightBottom",
})
const rotateThese = [
"edgeTopLeftCp",
"edgeTop",
"tipRight",
"tipRightTop",
"tipRightTopStart",
"tipRightTopCp1",
"tipRightTopCp2",
"tipRightTopEnd",
"tipRightBottomStart",
"tipRightBottomCp1",
"tipRightBottomCp2",
"tipRightBottomEnd",
"tipRightBottom",
"top",
"topCp2"
]
while (points.tipRightBottomStart.x > -1) {
for (const p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
/*
* Snap anchor
*/
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
/*
* 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()
points.snapRight = points.snapLeft.flipX()
/*
* Round the bottom corners
*/
macro("round", {
from: points.topLeft,
to: points.bottomRight,
via: points.bottomLeft,
radius: points.bottomRight.x / 4,
prefix: "bottomLeft"
})
macro("round", {
from: points.bottomLeft,
to: points.topRight,
via: points.bottomRight,
radius: points.bottomRight.x / 4,
prefix: "bottomRight"
})
/*
* Create one path for the bib outline
*/
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()
.addClass("fabric")
/*
* Mark the bias tape, but only if complete is set
*/
if (complete) paths.bias = paths.seam
.offset(-5)
.addClass("various dashed")
.addText("finishWithBiasTape", "center fill-various")
/*
* Annotations
*/
// Cutlist
store.cutlist.setCut({ cut: 1, from: 'fabric' })
// Snaps
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
points.snapRight = points.snapLeft.flipX()
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
// Logo
points.logo = new Point(0, 0)
snippets.logo = new Snippet("logo", points.logo)
// Title
points.title = points.bottom.shift(-90, 45)
macro("title", {
at: points.title,
nr: 1,
title: "bib",
scale: 0.7
})
// Scalbox
points.scalebox = points.title.shift(-90, 55)
macro("scalebox", { at: points.scalebox })
return part
}
```
</Example>
## Prerequisites
Before I can get started, I want to make sure I have the required software
installed on my computer.
FreeSewing is a JavaScript library that can run in the browser, on
[Node.js](https://nodejs.org/), or a variety of other runtimes such as Bun,
Deno, AWS Lambda, and so on.
For development, I will use Node.js. If you don't have Node.js on our system,
follow the link above and install it.
<Tip compact>You need Node.js 18 (lts/hydrogen) or higher to use FreeSewing</Tip>
To test whether NodeJS is installed, and see it's version, you can run this command:
```sh
node -v
```
If you get the Node.js version number, that means NodeJs is installed. Yay!

View file

@ -1,6 +1,6 @@
--- ---
title: Fitting the neck opening title: Fitting the neck opening
order: 170 order: 70
--- ---
We are not going to create some opening that we _hope_ is the right size, we're We are not going to create some opening that we _hope_ is the right size, we're

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

View file

@ -0,0 +1,132 @@
---
title: Creating a new design
order: 10
---
The development environment has already setup various designs for us.
Since I am using the **From scratch** template, the files I want to edit live
in `design/from-scratch`.
The design's main file is `design/from-scratch/src/index.mjs`, and our bib part
will live in `design/from-scratch/src/bib.mjs`.
This `bib.mjs` file is where will be doing most of the work here in part 2 of this
tutorial. But let's start with the `index.mjs` file as an appetizer, because this
is where a new FreeSewing design is brought to live. 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 compact>
It's a convention that constructor names start with an **C**apital letter.
</Tip>
We are passing some info to a 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.
<Related>
Refer to [the design reference documentation](/reference/api/design) for
all details about what you can pass to the Design constructor.
</Related>
That's it. So let's look at where the real action is next: Our first part.

View file

@ -1,6 +1,6 @@
--- ---
title: Creating a part title: Creating a part
order: 120 order: 20
--- ---
Much like garments themselves, patterns are made up of _parts_. Much like garments themselves, patterns are made up of _parts_.
@ -16,34 +16,23 @@ this, but it's a good habit to get into.
## bib.mjs ## bib.mjs
Since I chose the `tutorial` preset, the development environment is preconfigured for this tutorial. I am going to use the **From scratch** template. So the files I want to edit live
in `design/from-scratch`.
The design's main file lives in `design/src/index.mjs`, and the bib part lives in `design/src/bib.mjs`. Our part lives in `design/from-scratch/src/bib.mjs`, and it currently looks like this:
This `bib.mjs` is where I will be doing most of the work. ```src/bib.mjs
The file that was created includes a lot of comments to provide guidance to those not using this tutorial. function draftBib({ part }) {
I have removed those comments from the inline examples in this tutorial for clarity in our example.
The `bib.mjs` file currently looks like this:
```design/src/bib.mjs
/*
* This function drafts the part
*/
function draftBib ({ part }) => {
return part return part
} }
/*
* This is the part object
*/
export const bib = { export const bib = {
name: 'tutorial.bib', name: 'fromscratch.bib',
draft: draftBib, draft: draftBib,
} }
``` ```
### The part object ## The part object
Each part in FreeSewing is an object that describes the part, and has a `draft` 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. method to do the actual work of drafting the part.
@ -64,13 +53,13 @@ to create other designs.
</Note> </Note>
#### The part name ### The part name
```design/src/bib.mjs ```src/bib.mjs
name: 'tutorial.bib', name: 'fromscratch.bib',
``` ```
A part's `name` should be unique in a design. I used `tutorial.bib` as the A part's `name` should be unique in a design. I used `fromscratch.bib` as the
name, because that makes sense. name, because that makes sense.
<Warning> <Warning>
@ -80,9 +69,9 @@ This avoids naming conflicts when mixing parts from various designs to create a
</Warning> </Warning>
#### The part's draft method ### The part's draft method
```design/src/bib.mjs ```src/bib.mjs
draft: draftBib, draft: draftBib,
``` ```
@ -97,86 +86,4 @@ 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. the part object at the bottom is a bit of a convention in FreeSewing.
</Note> </Note>
## index.mjs
<Tip>
Feel free to skip to [Adding
measurements](/tutorials/pattern-design/adding-measurements) if you're itching
to get started. Or, read on for an explanation of what's going on in the
`index.mjs` file.
</Tip>
The `index.mjs` file is already complete and we won't be making any changes to
it. But if you are curious about what's going on inside `index.mjs`,
this is all we need:
```design/src/index.mjs
import { Design } from '@freesewing/core'
import { bib } from './bib.mjs'
import { i18n } from '../i18n/index.mjs'
const Tutorial = new Design({
parts: [ bib ],
})
export { bib, Tutorial, i18n }
```
If you are familiar with Javascript, I hope you are happy to see that FreeSewing uses ESM modules, and named exports.
If you are not familiar with Javascript, these `import` statements are how we load code from other files.
There's three of them:
```design/src/index.mjs
import { Design } from '@freesewing/core'
```
This loads the `Design` constructure from FreeSewing's core library.
A constructor is a function that creates something. So the `Design` constructor creates a Design.
```design/src/index.mjs
import { bib } from './bib.mjs'
```
This loads the `bib` part from the `bib.mjs` file in the same folder.
This is what we will be working on.
```design/src/index.mjs
import { i18n } from '../i18n/index.mjs'
```
And this loads something named `i18n` from the `index.mjs` file in the `i18n`
folder that's one level higher. These are the translations.
I will show you how you can provide translations for your designs towards the
end of this tutorial.
```design/src/index.mjs
const Tutorial = new Design({
parts: [ bib ],
})
```
This is where the magic happens. We create a new Design by passing the Design
constructure a configuration object. All it holds is the `parts` key that is
an array of our parts.
Which goes to show that a design isn't much more than a bunch of parts.
```design/src/index.mjs
export { bib, Tutorial, i18n }
```
And then all that's left to do is export things so that people can use them.
These are named exports. We are exporting three things:
- `Tutorial` is our complete design. Exporting it means people can use it.
- `bib` is our part. We are exporting it so people can re-use just this part.
- `i18n` are the translations. We are exporting it so people can load them when using our Tutorial.
<Related>
Refer to [the design reference documentation](/reference/api/design) for
all details about what you can pass to the Design constructor.
</Related>

View file

@ -1,6 +1,6 @@
--- ---
title: Rounding the corners title: Rounding the corners
order: 240 order: 94
--- ---
We already know how to round corners, we'll have the `round` macro take care of that for us. We already know how to round corners, we'll have the `round` macro take care of that for us.
@ -17,13 +17,16 @@ function draftBib({
points, points,
measurements, measurements,
options, options,
utils,
macro, macro,
part, part,
}) { }) {
// Construct the quarter neck opening /*
* Construct the neck opening
*/
const target = (measurements.head * options.neckRatio) / 4
let tweak = 1 let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta let delta
do { do {
points.right = new Point(tweak * measurements.head / 10, 0) points.right = new Point(tweak * measurements.head / 10, 0)
@ -84,18 +87,33 @@ function draftBib({
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y) points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y) points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
macro("round", { /*
from: points.edgeTop, * Macros will return the auto-generated IDs
to: points.tipRight, */
via: points.tipRightTop, const ids1 = {
prefix: "tipRightTop", tipRightTop: macro("round", {
}) id: "tipRightTop",
macro("round", { from: points.edgeTop,
from: points.tipRight, to: points.tipRight,
to: points.top, via: points.tipRightTop,
via: points.tipRightBottom, }),
prefix: "tipRightBottom", 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()
}
}
const rotateThese = [ const rotateThese = [
"edgeTopLeftCp", "edgeTopLeftCp",
"edgeTop", "edgeTop",
@ -130,21 +148,35 @@ function draftBib({
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX() points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
// highlight-start // highlight-start
// Round the bottom corners /*
macro("round", { * Round the bottom corners
from: points.topLeft, * Macros will return the auto-generated IDs
to: points.bottomRight, */
via: points.bottomLeft, const ids2 = {
radius: points.bottomRight.x / 4, bottomLeft: macro("round", {
prefix: "bottomLeft" id: "bottomLeft",
}) from: points.topLeft,
macro("round", { to: points.bottomRight,
from: points.bottomLeft, via: points.bottomLeft,
to: points.topRight, radius: points.bottomRight.x / 4,
via: points.bottomRight, }),
radius: points.bottomRight.x / 4, bottomRight: macro("round", {
prefix: "bottomRight" 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 // highlight-end
// Create one path for the bib outline // Create one path for the bib outline

View file

@ -1,6 +1,6 @@
--- ---
title: Shaping the straps title: Shaping the straps
order: 200 order: 90
--- ---
Our straps should follow the neck opening, which isn't that hard to do. Our straps should follow the neck opening, which isn't that hard to do.
@ -28,28 +28,31 @@ function draftBib({
part, part,
}) { }) {
// Construct the quarter neck opening /*
* Construct the quarter neck opening
*/
const target = (measurements.head * options.neckRatio) /4
let tweak = 1 let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta let delta
do { do {
points.right = new Point(tweak * measurements.head / 10, 0) points.right = new Point(tweak * measurements.head / 10, 0)
points.bottom = new Point(0, tweak * measurements.head / 12) points.bottom = new Point(0, tweak * measurements.head / 12)
points.rightCp1 = points.right.shift( 90, points.bottom.dy(points.right) / 2)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right)/2) points.bottomCp2 = points.bottom.shift( 0, points.bottom.dx(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2)
paths.quarterNeck = new Path() paths.quarterNeck = new Path()
.move(points.right) .move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom) .curve(points.rightCp1, points.bottomCp2, points.bottom)
.hide() // Add this line .hide()
delta = paths.quarterNeck.length() - target delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99 if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02 else tweak = tweak * 1.02
} while (Math.abs(delta) > 1) } while (Math.abs(delta) > 1)
// Construct the complete neck opening /*
* Construct the complete neck opening
*/
points.rightCp2 = points.rightCp1.flipY() points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX() points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX() points.left = points.right.flipX()
@ -68,7 +71,9 @@ function draftBib({
.close() .close()
.addClass('fabric') .addClass('fabric')
// Drawing the bib outline /*
* Drawing the bib outline
*/
const width = measurements.head * options.widthRatio const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio const length = measurements.head * options.lengthRatio
@ -90,7 +95,9 @@ function draftBib({
.addClass('fabric') .addClass('fabric')
// highlight-start // highlight-start
// Shape the straps /*
* Shape the straps
*/
points.edgeLeft = new Point(points.topLeft.x, points.left.y) points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y) points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y) points.edgeTop = new Point(0, points.topLeft.y)
@ -103,7 +110,9 @@ function draftBib({
) )
points.edgeTopRightCp = points.edgeTopLeftCp.flipX() points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
// Now, adapt our `rect` path so it's no longer a rectangle: /*
* Now, adapt our `rect` path so it's no longer a rectangle:
*/
paths.rect = new Path() paths.rect = new Path()
.move(points.edgeTop) .move(points.edgeTop)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft) .curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

View file

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

View file

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

View file

@ -1,397 +0,0 @@
---
title: Completing our pattern
order: 260
---
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.
These things or not always wanted on a pattern. For example, if you're cutting
out patterns with a laser cutter, or tracing them with a projects, you
typically only want to have the pattern outline.
## The complete setting
Users can indicate their desire to have either a bare-bones pattern, or rather
when that's completed with all bells and whistles with [the
`complete` setting](/reference/settings/complete).
To make sure we respect the user's choice, we must wrap all of these
embellishments in a code block that only executes when the `complete` setting
is *truthy*.
To access the setting, we can destructure it:
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
macro,
// highlight-start
complete,
// highlight-end
part,
}) {
// Our earlier work is not shown for brevety
// highlight-start
if (complete) {
// Complete pattern here
}
// highlight-end
return part
}
```
## 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:
```design/src/bib.mjs
function draftBib({
Path,
Point,
//highlight-start
Snippet,
//highlight-end
paths,
points,
// highlight-start
snippets,
// highlight-end
measurements,
options,
macro,
complete,
part,
}) {
// Our earlier work is not shown for brevety
if (complete) {
// Complete pattern here
}
return part
}
```
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/).
</Note>
## The completed design
Let's put this and few other things together to complete our design:
<Example tutorial caption="Almost done. But there's one more thing the user can ask for: a **paperless** pattern">
```design/src/bib.mjs
function draftBib({
Path,
Point,
Snippet,
paths,
points,
snippets,
measurements,
options,
macro,
complete,
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() // Add this line
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)
macro("round", {
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
prefix: "tipRightTop",
})
macro("round", {
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
prefix: "tipRightBottom",
})
const rotateThese = [
"edgeTopLeftCp",
"edgeTop",
"tipRight",
"tipRightTop",
"tipRightTopStart",
"tipRightTopCp1",
"tipRightTopCp2",
"tipRightTopEnd",
"tipRightBottomStart",
"tipRightBottomCp1",
"tipRightBottomCp2",
"tipRightBottomEnd",
"tipRightBottom",
"top",
"topCp2"
]
while (points.tipRightBottomStart.x > -1) {
for (const p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
// Snap anchor
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
// 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()
points.snapRight = points.snapLeft.flipX()
// Round the bottom corners
macro("round", {
from: points.topLeft,
to: points.bottomRight,
via: points.bottomLeft,
radius: points.bottomRight.x / 4,
prefix: "bottomLeft"
})
macro("round", {
from: points.bottomLeft,
to: points.topRight,
via: points.bottomRight,
radius: points.bottomRight.x / 4,
prefix: "bottomRight"
})
// Create one path for the bib outline
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()
.addClass("fabric")
// highlight-start
if (complete) {
/*
* Let's add the points where
* the closure's snaps should go.
*/
// Add snaps
points.snapLeft = points.top
.shiftFractionTowards(points.edgeTop, 0.5)
points.snapRight = points.snapLeft
.flipX()
/*
* Now, let's use our newfound snippet powers
* to add the snaps to these points.
* First the left snap (the stud part)
*/
snippets.snapStud = new Snippet(
'snap-stud',
points.snapLeft
)
/*
* The right snap (the socket part)
* should go on the back of the fabric.
* To try to make that more obvious to the user
* let's set its `opacity` attribute to 0.5
* this way it will be semi-transparent.
*/
snippets.snapSocket = new Snippet(
'snap-socket',
points.snapRight
).attr('opacity', 0.5)
/*
* Another snippet we should add is our logo.
* A logo is not required, but we love skully.
*/
// Add a logo
points.logo = new Point(0, 0)
snippets.logo = new Snippet(
"logo",
points.logo
)
/*
* We already know how to use macros
* This one adds a title to our part
*/
// Add a title
points.title = points.bottom
.shift(-90, 45)
macro("title", {
at: points.title,
nr: 1,
title: "bib",
scale: 0.7
})
/*
* This macro adds a scalebox to our part
*/
// Add a scalbox
points.scalebox = points.title
.shift(-90, 55)
macro(
"scalebox",
{ at: points.scalebox }
)
/*
* Our bib should be finished with bias tape.
* To add it, we're using Path.offset()
* You will learn to LOVE this methid
* when designing pattern with seam allowance
* as it draws a path parallel to another one
*/
paths.bias = paths.seam
.offset(-5)
.addClass("various dashed")
.addText(
"finishWithBiasTape",
"center fill-various"
)
}
// highlight-end
return part
}
```
</Example>

View file

@ -1,49 +1,10 @@
--- ---
title: Conclusion title: Conclusion
order: 280 order: 90
--- ---
Congratulations, we have created our first pattern. And while it's arguably Congratulations, we have created our first pattern. And while it's arguably
rather simple, we have learned a bunch of things along the way. Let's list rather simple, we have learned a bunch of things along the way.
some of the things we've learned:
- We learned how to [setup our development environment][new-design] with `npx
@freesewing/new-design`
- We learned about [the different files and folders][structure] in the
development environment and how `design/src` holds our source code.
- We learned about [parts][our-first-part] and how they are structured.
- We learned how to add [measurements][adding-measurements], and
[options][adding-options] to our design.
- We learned about [a part's draft method][draft-method] and how to destructure
what we need to design our part.
- We learned [how to add points and draw paths][constructing-the-neck-opening]
- We learned how we can make changes in a loop to [adapt the neck
opening][fitting-the-neck-opening] or [rotate the straps][avoiding-overlap]
until they were just right
- We learned about [macros and how to use them][creating-the-closure]
- We learned there are many methods that allow us to manipulate
[points](/reference/api/point/) and [paths](/reference/api/path/)
- We learned about what it means to draft [a complete
pattern][completing-our-pattern]
- We learned about [snippets and how to add them][completing-our-pattern]
- We learned [how to offset a path][completing-our-pattern] to create seam
allowance, or in our case, mark the bias tape line
- We learned how to support a [paperless pattern][paperless] by adding
dimensions
[new-design]: /tutorials/pattern-design/new-design
[structure]: /tutorials/pattern-design/new-design
[our-first-part]: /tutorials/pattern-design/our-first-part
[adding-measurements]: /tutorials/pattern-design/adding-measurements
[adding-options]: /tutorials/pattern-design/adding-options
[draft-method]: /tutorials/pattern-design/draft-method
[constructing-the-neck-opening]: /tutorials/pattern-design/constructing-the-neck-opening
[fitting-the-neck-opening]: /tutorials/pattern-design/fitting-the-neck-opening
[avoiding-overlap]: /tutorials/pattern-design/avoiding-overlap
[creating-the-closure]: /tutorials/pattern-design/creating-the-closure
[completing-our-pattern]: /tutorials/pattern-design/completing-our-pattern
[paperless]: /tutorials/pattern-design/paperless
## More reading material ## More reading material
@ -71,7 +32,7 @@ this tutorial.
Were there areas that were not clear? Did I dwell too long on one topic, or 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, rushed through another one too quickly? Your feedback helps improve things,
so don't be shy and [tell me what you think](https://discord.freesewing.org/). so don't be shy and tell me what you think.
If you don't like Discord, you can reach me at joost@freesewing.org You can reach me at joost@freesewing.org
</Comment> </Comment>

View file

@ -2,267 +2,17 @@
title: "Part 3: Beyond the basics" title: "Part 3: Beyond the basics"
--- ---
Hello there, and welcome to this FreeSewing pattern design tutorial. I'm excited about diving into part 3 with you, and I hope that a brief
My name is Joost, and in this tutorial I will show you overview of what we'll cover will get you excited too:
how to design a made-to-measure sewing pattern, start to finish.
<Tip> - Adding seam allowance
##### Before you start - 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 you haven't done so yet, read the [Before you start If that sounds like something you'd like to learn more about, then let's dive
guide](/guides/prerequisites). It's very short, but covers some basic right in.
terminology and concepts that we'll use throughout this guide.
</Tip>
I will be designing a pattern for a baby bib. It's a very simple pattern, but
that's ok. It is a tutorial after all. This will give us plenty to work with.
At the end of this tutorial, I will have created this pattern, and if you
follow along, so will you:
<Example tutorial="1" previewFirst="1" caption="Our end result">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
macro,
store,
complete,
snippets,
Snippet,
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() // Add this line
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)
macro("round", {
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
prefix: "tipRightTop",
})
macro("round", {
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
prefix: "tipRightBottom",
})
const rotateThese = [
"edgeTopLeftCp",
"edgeTop",
"tipRight",
"tipRightTop",
"tipRightTopStart",
"tipRightTopCp1",
"tipRightTopCp2",
"tipRightTopEnd",
"tipRightBottomStart",
"tipRightBottomCp1",
"tipRightBottomCp2",
"tipRightBottomEnd",
"tipRightBottom",
"top",
"topCp2"
]
while (points.tipRightBottomStart.x > -1) {
for (const p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
/*
* Snap anchor
*/
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
/*
* 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()
points.snapRight = points.snapLeft.flipX()
/*
* Round the bottom corners
*/
macro("round", {
from: points.topLeft,
to: points.bottomRight,
via: points.bottomLeft,
radius: points.bottomRight.x / 4,
prefix: "bottomLeft"
})
macro("round", {
from: points.bottomLeft,
to: points.topRight,
via: points.bottomRight,
radius: points.bottomRight.x / 4,
prefix: "bottomRight"
})
/*
* Create one path for the bib outline
*/
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()
.addClass("fabric")
/*
* Mark the bias tape, but only if complete is set
*/
if (complete) paths.bias = paths.seam
.offset(-5)
.addClass("various dashed")
.addText("finishWithBiasTape", "center fill-various")
/*
* Annotations
*/
// Cutlist
store.cutlist.setCut({ cut: 1, from: 'fabric' })
// Snaps
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
points.snapRight = points.snapLeft.flipX()
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
// Logo
points.logo = new Point(0, 0)
snippets.logo = new Snippet("logo", points.logo)
// Title
points.title = points.bottom.shift(-90, 45)
macro("title", {
at: points.title,
nr: 1,
title: "bib",
scale: 0.7
})
// Scalbox
points.scalebox = points.title.shift(-90, 55)
macro("scalebox", { at: points.scalebox })
return part
}
```
</Example>
## Prerequisites
Before I can get started, I want to make sure I have the required software
installed on my computer.
FreeSewing is a JavaScript library that can run in the browser, on
[Node.js](https://nodejs.org/), or a variety of other runtimes such as Bun,
Deno, AWS Lambda, and so on.
For development, I will use Node.js. If you don't have Node.js on our system,
follow the link above and install it.
<Tip compact>You need Node.js 18 (lts/hydrogen) or higher to use FreeSewing</Tip>
To test whether NodeJS is installed, and see it's version, you can run this command:
```sh
node -v
```
If you get the Node.js version number, that means NodeJs is installed. Yay!

View file

@ -0,0 +1,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.

View file

@ -0,0 +1,340 @@
---
title: How to communicate to the user
order: 50
---
As a designer, there are times you want to bring something to the attention of
the user. I am not talking about generic information that can go in the
documentation, but rather a message that is tailored specifically to this
pattern, much like this pattern is specifically tailored to the user.
Doing so is possible with the various `store.flag` methods, and below is
our updated bib making use of this. It's important to realize that things
will look the same here. But if you load this pattern in the development
environment (or on FreeSewing.org for that matter) the user will see this:
![A message for the user](./flag.png)
It's a simple example, but I hope it gets the point across.
Finally, keep in mind that we are now straddling the world of the core library
and frontend integration. These messages won't do anything unless you have a
frontend the shows them.
<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>
Now the first thing you should know is: **core does not care**. We are

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View file

@ -0,0 +1,7 @@
---
title: Supporting translation
order: 80
---
<Fixme compact>Write this section for v3</Fixme>

View file

@ -0,0 +1,68 @@
---
title: Facilitating frontend integration
order: 60
---
Strictly speaking, this tutorial is about learning to use FreeSewing's core
library to do parametric design, and we made great strides in that regard.
But FreeSewing is a lot more than its core library, and you might be wondering
how your pattern options magically end up in the development environmnt under
**Design options**:
![Design options menu](./options.png)
To make this happen, we add extra information to the options configuration.
You can add anything you want, here is a made-up example:
```mjs
options: {
shipWith: {
dflt: 'pickUp',
list: ['pickUp', 'post', 'courier'],
menu: 'shipping',
extraNote: 'Pick-up Monday to Friday 10:00, to 19:00'
},
}
```
It's just a silly example, but there's two important take-aways here:
- You don't have to use options in your design. You can add options for things
that are not about the desing, 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.
<Fixme compact>Include example</Fixme>

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View file

@ -1,28 +1,21 @@
--- ---
title: Supporting paperless patterns title: Supporting paperless patterns
order: 270 order: 30
--- ---
The goal of paperless patterns is to create a pattern that we don't need to 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 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. also a way to democratize access to patterns.
While more and more of humanity is on the internet, access to printers and 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. printing paper is often harder to come by, especially in developing countries.
So before wrapping up, let's make the extra effort to make our bib design So let's make the extra effort to make our bib design support paperless.
support paperless pattern.
## The paperless setting ## The paperless setting
Users can request paperless patterns by setting [the `paperless` Users can request paperless patterns by setting [the `paperless`
setting](/reference/settings/paperless) to a *truthy* value. setting](/reference/settings/paperless) to a *truthy* value.
Like other settings, we can destructure it to get access to it.
Then, much like with the `complete` setting, we will wrap our
paperless-specific code in a condition so it only runs when the user wants
that.
With paperless enabled, FreeSewing will automatically render a grid for each With paperless enabled, FreeSewing will automatically render a grid for each
pattern part with metric or imperial markings, depending on the units requested pattern part with metric or imperial markings, depending on the units requested
by the user. by the user.
@ -38,129 +31,155 @@ Thankfully, there's macros that can help us with that, specifically:
- The `ld` macro adds a linear dimension - The `ld` macro adds a linear dimension
- The `pd` macro adds a path dimension that follows a given path - 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> <Note>
Refer to [the list of macros](/reference/macros/) for more details. Refer to [the list of macros](/reference/macros/) for more details.
</Note> </Note>
<Example tutorial paperless caption="Making our pattern paperless is the icing on the cake. Time to wrap up, go over what we've learned, and give some pointers on where to go from here"> <Example previewFirst tutorial paperless caption="Suddenly, a printer is very much optional">
```design/src/bib.mjs ```design/src/bib.mjs
function draftBib({ function draftBib({
Path, Path,
Point, Point,
paths, paths,
points, points,
utils,
measurements, measurements,
options, options,
macro, macro,
complete, complete,
snippets, snippets,
Snippet, Snippet,
store,
// highlight-start // highlight-start
paperless, paperless,
// highlight-end // highlight-end
part, part,
}) { }) {
// Construct the quarter neck opening /*
* Construct the neck opening
*/
const target = (measurements.head * options.neckRatio) / 4
let tweak = 1 let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta let delta
do { do {
points.right = new Point(tweak * measurements.head / 10, 0) points.right = new Point((tweak * measurements.head) / 10, 0)
points.bottom = new Point(0, tweak * measurements.head / 12) points.bottom = new Point(0, (tweak * measurements.head) / 12)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right)/2) points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2) points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
paths.quarterNeck = new Path() paths.neck = new Path()
.move(points.right) .move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom) .curve(points.rightCp1, points.bottomCp2, points.bottom)
.hide() // Add this line
delta = paths.quarterNeck.length() - target delta = paths.neck.length() - target
if (delta > 0) tweak = tweak * 0.99 if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02 else tweak = tweak * 1.02
} while (Math.abs(delta) > 1) } while (Math.abs(delta) > 1)
// Construct the complete neck opening
points.rightCp2 = points.rightCp1.flipY() points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX() points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX() points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX() points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX() points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY() points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY() points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY() points.topCp2 = points.bottomCp1.flipY()
// Drawing the bib outline /*
const width = measurements.head * options.widthRatio * Construct the outline
const length = measurements.head * options.lengthRatio */
let width = measurements.head * options.widthRatio
let length = measurements.head * options.lengthRatio
points.topLeft = new Point( points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
width / -2,
points.top.y - (width / 2 - points.right.x)
)
points.topRight = points.topLeft.shift(0, width) points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length) points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length) points.bottomRight = points.topRight.shift(-90, length)
// Shape the straps
points.edgeLeft = new Point(points.topLeft.x, points.left.y) points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y) points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y) points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5) points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX() points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards( points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
points.topLeft,
0.5
)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX() points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
// Round the straps /*
const strap = points.edgeTop.dy(points.top) * Round the end of the straps
*/
let strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2) points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y) points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y) points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
macro("round", { /*
from: points.edgeTop, * Macros will return the auto-generated IDs
to: points.tipRight, */
via: points.tipRightTop, const ids1 = {
prefix: "tipRightTop", tipRightTop: macro('round', {
}) id: 'tipRightTop',
macro("round", { from: points.edgeTop,
from: points.tipRight, to: points.tipRight,
to: points.top, via: points.tipRightTop,
via: points.tipRightBottom, }),
prefix: "tipRightBottom", tipRightBottom: macro('round', {
}) id: 'tipRightBottom',
const rotateThese = [ from: points.tipRight,
"edgeTopLeftCp", to: points.top,
"edgeTop", via: points.tipRightBottom,
"tipRight", }),
"tipRightTop",
"tipRightTopStart",
"tipRightTopCp1",
"tipRightTopCp2",
"tipRightTopEnd",
"tipRightBottomStart",
"tipRightBottomCp1",
"tipRightBottomCp2",
"tipRightBottomEnd",
"tipRightBottom",
"top",
"topCp2"
]
while (points.tipRightBottomStart.x > -1) {
for (const p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
} }
// Snap anchor /*
* 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) points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
// Add points for second strap /*
* Mirror points to the other side
*/
points.edgeTopRightCp = points.edgeTopLeftCp.flipX() points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX() points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX() points.tipLeftTopStart = points.tipRightTopStart.flipX()
@ -173,23 +192,40 @@ function draftBib({
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX() points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
points.snapRight = points.snapLeft.flipX() points.snapRight = points.snapLeft.flipX()
// Round the bottom corners /*
macro("round", { * Round the bottom of the bib
from: points.topLeft, * Radius is fixed, but you could use an option for it)
to: points.bottomRight, *
via: points.bottomLeft, * Macros will return the auto-generated IDs
radius: points.bottomRight.x / 4, */
prefix: "bottomLeft" const ids2 = {
}) bottomLeft: macro('round', {
macro("round", { id: 'bottomLeft',
from: points.bottomLeft, from: points.topLeft,
to: points.topRight, to: points.bottomRight,
via: points.bottomRight, via: points.bottomLeft,
radius: points.bottomRight.x / 4, radius: points.bottomRight.x / 4,
prefix: "bottomRight" }),
}) 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()
}
}
// Create one path for the bib outline /*
* Construct the path
*/
paths.seam = new Path() paths.seam = new Path()
.move(points.edgeLeft) .move(points.edgeLeft)
.line(points.bottomLeftStart) .line(points.bottomLeftStart)
@ -197,150 +233,111 @@ function draftBib({
.line(points.bottomRightStart) .line(points.bottomRightStart)
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd) .curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
.line(points.edgeRight) .line(points.edgeRight)
.curve( .curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
points.edgeRightCp, .curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
points.edgeTopRightCp, .curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
points.tipLeftTopStart .curve(points.topCp1, points.rightCp2, points.right)
) .curve(points.rightCp1, points.bottomCp2, points.bottom)
.curve( .curve(points.bottomCp1, points.leftCp2, points.left)
points.tipLeftTopCp1, .curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
points.tipLeftTopCp2, .curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
points.tipLeftTopEnd .curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
) .curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.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() .close()
.addClass("fabric") .attr('class', 'fabric')
if (complete) { /*
// Add snaps *
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5) * Annotations
points.snapRight = points.snapLeft.flipX() *
snippets.snapStud = new Snippet('snap-stud', points.snapLeft) */
snippets.snapSocket = new Snippet('snap-socket', points.snapRight)
.attr('opacity', 0.5)
// Add a logo /*
points.logo = new Point(0, 0) * Cut list
snippets.logo = new Snippet("logo", points.logo) */
store.cutlist.addCut({ cut: 1, from: 'fabric' })
// Add a title /*
points.title = points.bottom.shift(-90, 45) * Add the snaps
macro("title", { */
at: points.title, snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
nr: 1, snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
title: "bib",
scale: 0.7
})
// Add a scalbox
points.scalebox = points.title.shift(-90, 55)
macro("scalebox", { at: points.scalebox })
/*
* Add the bias tape
*/
if (complete)
paths.bias = paths.seam paths.bias = paths.seam
.offset(-5) .offset(-5)
.addClass("various dashed") .addClass('note dashed')
.addText("finishWithBiasTape", "center fill-various") .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 // highlight-start
/* /*
* Note that we are putting our paperless block * Add dimensions
* inside our `complete` block since we should */
* only add this when the users wants a compete pattern macro('hd', {
*/ id: 'wFull',
if (paperless) { from: points.bottomLeftStart,
// Add dimensions to: points.bottomRightEnd,
/* y: points.bottomLeft.y + 15,
* The `hd` macro adds a Horizontal Dimension (hd) })
* It takes a from and to point, and a y value macro('vd', {
* at which to place the dimension id: 'hBottomToOpeningBottom',
*/ from: points.bottomRightStart,
macro("hd", { to: points.bottom,
from: points.bottomLeftStart, x: points.bottomRight.x + 15,
to: points.bottomRightEnd, })
y: points.bottomLeft.y + 15 macro('vd', {
}) id: 'hBottomToOpeningCenter',
/* from: points.bottomRightStart,
* The `vd` macro adds a Vertical Dimension (vd) to: points.right,
* It takes a from and to point, and an x value x: points.bottomRight.x + 30,
* at which to place the dimension })
*/ macro('vd', {
macro("vd", { id: 'hTotal',
from: points.bottomRightStart, from: points.bottomRightStart,
to: points.bottom, to: points.tipLeftTopStart,
x: points.bottomRight.x + 15 x: points.bottomRight.x + 45,
}) })
/* macro('hd', {
* Let's do a few more of these id: 'wOpening',
*/ from: points.left,
macro("vd", { to: points.right,
from: points.bottomRightStart, y: points.left.y + 25,
to: points.right, })
x: points.bottomRight.x + 30 macro('ld', {
}) id: 'wStrap',
macro("vd", { from: points.tipLeftBottomEnd,
from: points.bottomRightStart, to: points.tipLeftTopStart,
to: points.tipLeftTopStart, d: -15,
x: points.bottomRight.x + 45 })
})
macro("hd", {
from: points.left,
to: points.right,
y: points.left.y + 25
})
/*
* The `ld` macro adds a Linear Dimension (ld)
* It takes a from and to point, and a d value
* that sets its offset from the line from -> to
*/
macro("ld", {
from: points.tipLeftBottomEnd,
to: points.tipLeftTopStart,
d: 15
})
}
// highlight-end // highlight-end
}
return part return part
} }
``` ```

View file

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

View file

@ -1,6 +1,6 @@
--- ---
title: "Testing a design" title: Testing your designs
order: 1250 order: 70
--- ---
<Fixme> Update this for v3 </Fixme> <Fixme> Update this for v3 </Fixme>

View file

@ -0,0 +1,8 @@
function draftBib({ part }) {
return part
}
export const bib = {
name: 'fromscratch.bib',
draft: draftBib,
}

View file

@ -1,6 +1,6 @@
import { Design } from '@freesewing/core' import { Design } from '@freesewing/core'
import { i18n } from '../i18n/index.mjs' import { i18n } from '../i18n/index.mjs'
import { scratch } from './scratch.mjs' import { bib } from './bib.mjs'
/* /*
* Create the design * Create the design
@ -10,7 +10,7 @@ const FromScratch = new Design({
name: 'fromScratch', name: 'fromScratch',
version: '0.0.1', version: '0.0.1',
}, },
parts: [scratch], parts: [bib],
}) })
export { scratch, FromScratch, i18n } export { bib, FromScratch, i18n }

View file

@ -1,30 +0,0 @@
function draftScratch({ part, points, Point, paths, Path }) {
/*
* Do your magic here
*/
points.topLeft = new Point(0, 0)
points.topRight = new Point(200, 0)
points.bottomRight = new Point(200, 100)
points.bottomLeft = new Point(0, 100)
points.mid = points.topLeft
.shiftFractionTowards(points.bottomRight, 0.5)
.addText('fromscratch:whatWillYouCreateToday', 'fill-note center')
paths.box = new Path()
.move(points.topLeft)
.line(points.bottomLeft)
.line(points.bottomRight)
.line(points.topRight)
.line(points.topLeft)
.close()
.addClass('help note')
return part
}
export const scratch = {
name: 'fromscratch.front',
draft: draftScratch,
}

View file

@ -24,20 +24,15 @@ export const bib = {
units, units,
part, part,
}) => { }) => {
/*
* Head size
*/
const head = (`head` in measurements ? measurements.head : 360) * options.headSize
/* /*
* Construct the neck opening * Construct the neck opening
*/ */
const target = (head * options.neckRatio) / 4 const target = (measurements.head * options.neckRatio) / 4
let tweak = 1 let tweak = 1
let delta let delta
do { do {
points.right = new Point((tweak * head) / 10, 0) points.right = new Point((tweak * measurements.head) / 10, 0)
points.bottom = new Point(0, (tweak * head) / 12) points.bottom = new Point(0, (tweak * measurements.head) / 12)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2) points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2) points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
@ -65,8 +60,8 @@ export const bib = {
/* /*
* Construct the outline * Construct the outline
*/ */
let width = head * options.widthRatio let width = measurements.head * options.widthRatio
let length = head * options.lengthRatio let length = measurements.head * options.lengthRatio
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x)) points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
points.topRight = points.topLeft.shift(0, width) points.topRight = points.topLeft.shift(0, width)
@ -215,7 +210,7 @@ export const bib = {
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart) .curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft) .curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.close() .close()
.attr('class', 'fabric') .addClass('fabric')
/* /*
* *
@ -250,9 +245,8 @@ export const bib = {
if (complete) if (complete)
paths.bias = paths.seam paths.bias = paths.seam
.offset(-5) .offset(-5)
.attr('class', 'note dashed') .addClass('note dashed')
.attr('data-text', 'tutorial:finishWithBiasTape') .addText('tutorial:finishWithBiasTape', 'center fill-note')
.attr('data-text-class', 'center fill-note')
/* /*
* Add the title * Add the title

View file

@ -56,12 +56,16 @@ const getMdxConfig = ({ site, jargon }) => ({
plainText: ['dot', 'http', 'mermaid'], plainText: ['dot', 'http', 'mermaid'],
aliases: { aliases: {
javascript: [ javascript: [
'design/from-scratch/src/index.mjs',
'design/from-scratch/src/bib.mjs',
'design/src/index.mjs', 'design/src/index.mjs',
'design/src/part.mjs',
'design/src/bib.mjs', 'design/src/bib.mjs',
'design/src/part.mjs',
'index.mjs', 'index.mjs',
'part.mjs', 'part.mjs',
'bib.mjs', 'bib.mjs',
'src/index.mjs',
'src/bib.mjs',
], ],
json: [ json: [
'200.json', '200.json',