1
0
Fork 0

chore: Port FreeSewing.dev to docusaurus

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB