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:
parent
497633d1d3
commit
ab3204f9f1
692 changed files with 11037 additions and 20674 deletions
|
@ -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 |
|
@ -0,0 +1,119 @@
|
|||
---
|
||||
title: Adding options
|
||||
order: 40
|
||||
---
|
||||
|
||||
I have shown what our bib should look like, and added the _head_ measurement
|
||||
to work with. But there's still a number of choices I have to make:
|
||||
|
||||
- How large should the neck opening be?
|
||||
- How wide should the bib be?
|
||||
- How long should the bib be?
|
||||
|
||||
I could make all of these choices for the user and set them in stone, so to speak.
|
||||
|
||||
But since the pattern I am designing is code, it is trivial (and _IMHO_ very satisfying)
|
||||
to make a pattern flexible and let the user choose.
|
||||
All I need to do to give control to the user is add _options_ to the part.
|
||||
|
||||
## Add the neckRatio option
|
||||
|
||||
The first option I will add controls the ratio between the neck opening
|
||||
and the head circumference. Let's call it `neckRatio`.
|
||||
|
||||
For this, I will add the `options` property to our `bib` object:
|
||||
|
||||
```design/src/bib.mjs
|
||||
function draftBib({ part }) {
|
||||
return part
|
||||
}
|
||||
|
||||
export const bib = {
|
||||
name: 'fromscratch.bib',
|
||||
draft: draftBib,
|
||||
measurements: [ 'head' ],
|
||||
// highlight-start
|
||||
options: {
|
||||
neckRatio: {
|
||||
pct: 80,
|
||||
min: 70,
|
||||
max: 90,
|
||||
menu: 'fit'
|
||||
},
|
||||
},
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
Can you guess what it means?
|
||||
|
||||
- We've added the `options` property to our `bib` object
|
||||
- On the `options` property, we have added `neckRatio` which holds the configuration for our option
|
||||
- It is a `pct` option -- which means it's a percentage
|
||||
- Its default value is 80%
|
||||
- Its minimum value is 70%
|
||||
- Its maximum value is 90%
|
||||
|
||||
There are different types of options, but percentages are by far the most common ones.
|
||||
They are all documented [in the part reference docs](/reference/api/part/config/options).
|
||||
|
||||
:::note
|
||||
|
||||
##### What is `menu` and why should you care?
|
||||
|
||||
The `menu` property on our option is *extra*.
|
||||
It will be ignored by FreeSewing's core library and if we leave it out, our design will produce the same result.
|
||||
|
||||
Instead, this `menu` property is there for the benefit of FreeSewing's development environment which will use this to build a menu structure for the various
|
||||
options.
|
||||
|
||||
This is covered in more detail in [Part 3](/tutorials/pattern-design/part3) of this tutorial.
|
||||
|
||||
:::
|
||||
|
||||
## Add the widthRatio and lengthRatio options
|
||||
|
||||
Let's do something similar for the width and length of our bib:
|
||||
|
||||
```design/src/bib.mjs
|
||||
function draftBib({ part }) => {
|
||||
return part
|
||||
}
|
||||
|
||||
export const bib = {
|
||||
name: 'fromScratch.bib',
|
||||
draft: draftBib,
|
||||
measurements: [ 'head' ],
|
||||
options: {
|
||||
neckRatio: {
|
||||
pct: 80,
|
||||
min: 70,
|
||||
max: 90,
|
||||
menu: 'fit'
|
||||
},
|
||||
|
||||
// highlight-start
|
||||
widthRatio: {
|
||||
pct: 45,
|
||||
min: 35,
|
||||
max: 55,
|
||||
menu: 'style'
|
||||
},
|
||||
lengthRatio: {
|
||||
pct: 75,
|
||||
min: 55,
|
||||
max: 85,
|
||||
menu: 'style'
|
||||
},
|
||||
// highlight-end
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This is pretty much the exact same thing, except that are placing these in the `style` menu.
|
||||
|
||||
Later, I will test-drive our pattern to see how it behaves when we adapt the options
|
||||
between their minimum and maximum values. At that time, I may need to tweak these values.
|
||||
|
||||
With that out of the way, I will start drawing the bib.
|
||||
|
|
@ -0,0 +1,426 @@
|
|||
---
|
||||
title: Avoiding overlap
|
||||
order: 92
|
||||
---
|
||||
|
||||
While we've only drawn the end of one strap, it's pretty obvious they overlap,
|
||||
which makes it impossible to cut out, so we're going to have to address
|
||||
that.
|
||||
|
||||
Specifically, we're going to rotate our strap out of the way until it no longer overlaps.
|
||||
The rest of our bib should stay as it is, so let's start by making a list of points we need
|
||||
to rotate.
|
||||
|
||||
However, there is a catch.
|
||||
|
||||
## Macros and auto-generated IDs
|
||||
We have used the `round` macro to help us round the corners
|
||||
of our strap, and it added a bunch of auto-generated points to our pattern. We need to
|
||||
rotate these points too, but what are their names?
|
||||
|
||||
A macro will return the names of the things it created. So far, we have not captured
|
||||
that return value, but if we did, it would look like this:
|
||||
|
||||
```mjs
|
||||
{
|
||||
"tipRightTop": {
|
||||
"points": {
|
||||
"start": "__macro_@freesewing/plugin-round_tipRightTop_start",
|
||||
"cp1": "__macro_@freesewing/plugin-round_tipRightTop_cp1",
|
||||
"cp2": "__macro_@freesewing/plugin-round_tipRightTop_cp2",
|
||||
"end": "__macro_@freesewing/plugin-round_tipRightTop_end"
|
||||
},
|
||||
"paths": {
|
||||
"path": "__macro_@freesewing/plugin-round_tipRightTop_path"
|
||||
}
|
||||
},
|
||||
"tipRightBottom": {
|
||||
"points": {
|
||||
"start": "__macro_@freesewing/plugin-round_tipRightBottom_start",
|
||||
"cp1": "__macro_@freesewing/plugin-round_tipRightBottom_cp1",
|
||||
"cp2": "__macro_@freesewing/plugin-round_tipRightBottom_cp2",
|
||||
"end": "__macro_@freesewing/plugin-round_tipRightBottom_end"
|
||||
},
|
||||
"paths": {
|
||||
"path": "__macro_@freesewing/plugin-round_tipRightBottom_path"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Those names aren't very handy to remember. So I will rewrite this code a bit to
|
||||
we'll capture these return values from the `round` macros and create
|
||||
easy-to-remember points from them:
|
||||
|
||||
<Example tutorial caption="It looks the same as before, but now those macro points are accessible to us">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
// highlight-start
|
||||
utils,
|
||||
// highlight-end
|
||||
macro,
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(
|
||||
tweak * measurements.head / 10,
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
tweak * measurements.head / 12
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
.hide()
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
/*
|
||||
* Construct the complete neck opening
|
||||
*/
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.top)
|
||||
.curve(points.topCp2, points.leftCp1, points.left)
|
||||
.curve(points.leftCp2, points.bottomCp1, points.bottom)
|
||||
.curve(points.bottomCp2, points.rightCp1, points.right)
|
||||
.curve(points.rightCp2, points.topCp1, points.top)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
|
||||
/*
|
||||
* Drawing the bib outline
|
||||
*/
|
||||
const width = measurements.head * options.widthRatio
|
||||
const length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(
|
||||
width / -2,
|
||||
points.top.y - (width / 2 - points.right.x)
|
||||
)
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
/*
|
||||
* Shape the straps
|
||||
*/
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
|
||||
points.topLeft,
|
||||
0.5
|
||||
)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
// Round the straps
|
||||
const strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro("round", {
|
||||
id: "tipRightTop",
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
hide: false
|
||||
}),
|
||||
tipRightBottom: macro("round", {
|
||||
id: "tipRightBottom",
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
hide: false
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
/*
|
||||
* Now, adapt our `rect` path so it's no longer a rectangle:
|
||||
*/
|
||||
paths.rect = new Path()
|
||||
.move(points.edgeTop)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.edgeTop)
|
||||
.close()
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
||||
Once we have our list of points to rotate, we can rotate them. How far? Until the strap no longer overlaps.
|
||||
|
||||
<Example tutorial caption="It is looking pretty wonky now, but we'll deal with that next">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
utils,
|
||||
macro,
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(
|
||||
tweak * measurements.head / 10,
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
tweak * measurements.head / 12
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
.hide()
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
/*
|
||||
* Construct the complete neck opening
|
||||
*/
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.top)
|
||||
.curve(points.topCp2, points.leftCp1, points.left)
|
||||
.curve(points.leftCp2, points.bottomCp1, points.bottom)
|
||||
.curve(points.bottomCp2, points.rightCp1, points.right)
|
||||
.curve(points.rightCp2, points.topCp1, points.top)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
|
||||
// Drawing the bib outline
|
||||
const width = measurements.head * options.widthRatio
|
||||
const length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(
|
||||
width / -2,
|
||||
points.top.y - (width / 2 - points.right.x)
|
||||
)
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
/*
|
||||
* Shape the straps
|
||||
*/
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
|
||||
points.topLeft,
|
||||
0.5
|
||||
)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
// Round the straps
|
||||
const strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro("round", {
|
||||
id: "tipRightTop",
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
hide: false
|
||||
}),
|
||||
tipRightBottom: macro("round", {
|
||||
id: "tipRightBottom",
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
hide: false
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* This is the list of points we need to rotate
|
||||
* to move our strap out of the way
|
||||
*/
|
||||
const rotateThese = [
|
||||
"edgeTopLeftCp",
|
||||
"edgeTop",
|
||||
"tipRight",
|
||||
"tipRightTop",
|
||||
"tipRightTopStart",
|
||||
"tipRightTopCp1",
|
||||
"tipRightTopCp2",
|
||||
"tipRightTopEnd",
|
||||
"tipRightBottomStart",
|
||||
"tipRightBottomCp1",
|
||||
"tipRightBottomCp2",
|
||||
"tipRightBottomEnd",
|
||||
"tipRightBottom",
|
||||
"top",
|
||||
"topCp2"
|
||||
]
|
||||
/*
|
||||
* We're rotating all the points in
|
||||
* the `rotateThese` array around
|
||||
* the `edgeLeft` point.
|
||||
*
|
||||
* We're using increments of 1 degree
|
||||
* until the `tipRightBottomStart` point
|
||||
* is 1 mm beyond the center of our bib.
|
||||
*/
|
||||
while (points.tipRightBottomStart.x > -1) {
|
||||
for (const p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
|
||||
}
|
||||
|
||||
/*
|
||||
* This is not needed
|
||||
* we are merely adding it to show
|
||||
* what the rotated path looks like
|
||||
*/
|
||||
macro("round", {
|
||||
id: "showRightTop",
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
hide: false,
|
||||
classes: 'contrast dotted',
|
||||
})
|
||||
macro("round", {
|
||||
id: "showRightBottom",
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
hide: false,
|
||||
classes: 'contrast dotted',
|
||||
})
|
||||
// highlight-end
|
||||
/*
|
||||
* Now, adapt our `rect` path so it's no longer a rectangle:
|
||||
*/
|
||||
paths.rect = new Path()
|
||||
.move(points.edgeTop)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.edgeTop)
|
||||
.close()
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
|
@ -0,0 +1,172 @@
|
|||
---
|
||||
title: Completing the neck opening
|
||||
order: 80
|
||||
---
|
||||
|
||||
We've constructed the perfectly sized quarter neck, and we're going to use this
|
||||
to create our complete neck path by flipping and mirroring it.
|
||||
|
||||
## Hiding our quarter neck opening
|
||||
|
||||
To make our code easier to understand, we're going to leave the `quarterNeck` path
|
||||
as it is, and simply chose to not show it.
|
||||
|
||||
To accomplish this, we'll call the `hide()` method on our path:
|
||||
|
||||
<Example tutorial caption="A hidden path is not shown">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(
|
||||
tweak * measurements.head / 10,
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
tweak * measurements.head / 12
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
// highlight-start
|
||||
.hide()
|
||||
// highlight-end
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
||||
We're saying: _hide this path_. In other words, don't show it.
|
||||
The path is still known, and we can still use it to calculate the length of the neck opening.
|
||||
But it won't show up on screen or on the page.
|
||||
|
||||
## Create the complete neck opening
|
||||
|
||||
Now that we've hidden our homework, let's create the complete neck path.
|
||||
As the neck opening is symmetrical, there's no need to re-calculate the points
|
||||
on the other side. We can just flip them over, so to speak. And that's exactly
|
||||
what we'll do.
|
||||
|
||||
Let's add some more points, and then construct the complete path for the neck
|
||||
opening.
|
||||
|
||||
<Example tutorial caption="Our completed neck opening">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(
|
||||
tweak * measurements.head / 10,
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
tweak * measurements.head / 12
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
.hide()
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* Construct the complete neck opening
|
||||
*/
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.top)
|
||||
.curve(points.topCp2, points.leftCp1, points.left)
|
||||
.curve(points.leftCp2, points.bottomCp1, points.bottom)
|
||||
.curve(points.bottomCp2, points.rightCp1, points.right)
|
||||
.curve(points.rightCp2, points.topCp1, points.top)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
// highlight-end
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
||||
To add the points, we're using the `Point.flipX()` and `Point.flipY()` methods
|
||||
here. There's a few new Path methods too, like `close()` and `addClass()`.
|
||||
|
||||
Perhaps you can figure out what they do? If not, both [the Point
|
||||
documentation](/reference/api/point/) and [the Path
|
||||
documentation](/reference/api/path) have detailed info on all the methods
|
||||
available, including these.
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
title: Conclusion (of part 2)
|
||||
order: 99
|
||||
---
|
||||
|
||||
You made it to the end of part 2, in which we got to do some real parametric design.
|
||||
|
||||
Wile our bib is a rather simple design, it did allow us to gain familiarity with FreeSewing's core API.
|
||||
|
||||
You have learned how to create points. And seen various way to manipulate them,
|
||||
including some more advanced things like rotating a bunch of them out of the
|
||||
way.
|
||||
|
||||
You've also learned how to draw paths, which are the lines and curves that make up our pattern.
|
||||
And we've used macros which can help us with repetitive tasks.
|
||||
|
||||
What we've gotten so far is a perfectly suitable sewing pattern. You can print this,
|
||||
and make a nice bib out of it.
|
||||
|
||||
But when we stick to these basics, FreeSewing doesn't really get a chance to shine.
|
||||
For that, I recommend [Part 3 of this tutorial](/tutorials/pattern-design/part3) where
|
||||
we'll go beyond the basics.
|
|
@ -0,0 +1,176 @@
|
|||
---
|
||||
title: Constructing the neck opening
|
||||
order: 60
|
||||
---
|
||||
|
||||
Our goal is to construct an oval neck opening that has a circumference
|
||||
that is the `head` measurements multiplied by the `neckRatio` option.
|
||||
|
||||
That might involve some trial and error. But since the neck opening will be symmetric
|
||||
both horizontal and vertical, we only need to construct one quadrant.
|
||||
|
||||
## Destructuring measurements and options
|
||||
|
||||
We'll be adding some points to our pattern to do just that. But we want to have
|
||||
access to our measurements and options to do so. For this, we first destructure
|
||||
`measurements` and `options` so we can access them:
|
||||
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
// highlight-start
|
||||
measurements,
|
||||
options,
|
||||
// highlight-end
|
||||
part,
|
||||
}) {
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
|
||||
Great. Now let's get to work.
|
||||
|
||||
## Drawing our first path
|
||||
|
||||
Let's add some points, and use them to draw our first curve:
|
||||
|
||||
<Example tutorial caption="Our very first path forms a quarter of our neck opening">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
part,
|
||||
}) {
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
points.right = new Point(
|
||||
measurements.head / 10,
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
measurements.head / 12
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
// highlight-end
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
||||
We've added some points to our part, and drawn our first path.
|
||||
Let's look at each line in detail.
|
||||
|
||||
## Adding points
|
||||
|
||||
```js
|
||||
points.right = new Point(
|
||||
measurements.head / 10,
|
||||
0
|
||||
)
|
||||
```
|
||||
|
||||
- We're adding a point named `right` to the `points` object which holds our
|
||||
part's points
|
||||
- We're using the Point constructor, which takes two arguments: The point's X
|
||||
and Y coordinates in the 2-dimensional space
|
||||
- The X value is `measurements.head / 10`
|
||||
- The Y value is `0`
|
||||
|
||||
The creation of `points.bottom` is very similar, so let's skip to the next line:
|
||||
|
||||
```js
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
```
|
||||
|
||||
- We're adding a point named `rightCp1`, which will become the _control point_
|
||||
of the right part
|
||||
- Instead of using the Point constructor, we're calling the `Point.shift()`
|
||||
method on an existing point
|
||||
- It takes two arguments: The angle to shift towards, and the distance
|
||||
- We can see that we're shifting 90 degrees (that means up) but the distance
|
||||
uses another method
|
||||
- The `Point.dy()` method returns the delta along the Y axis between the point
|
||||
we call it on and the point we pass it
|
||||
- We shift half of the Y-delta
|
||||
|
||||
The next point is very similar again, except that this time we're shifting to
|
||||
the right (0 degrees) for half of the X-delta between points `bottom` and
|
||||
`right`.
|
||||
|
||||
:::tip
|
||||
##### Further reading
|
||||
The `Point.shift()` and `Point.dy()` are just the tip of the iceberg.
|
||||
Points come with a bunch of these methods.
|
||||
You can find them all in [the Point API docs](/reference/api/point/).
|
||||
:::
|
||||
|
||||
## Adding paths
|
||||
|
||||
Adding points is typically merely a means to an end. And that end gets
|
||||
introduced on the next line: Paths.
|
||||
|
||||
```js
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
```
|
||||
|
||||
- We're adding a path named `quarterNeck` to the `paths` object which holds our
|
||||
part's paths
|
||||
- We're using the Path constructor, which takes no arguments
|
||||
- We're following up with a `Path.move()` call that takes one Point as argument
|
||||
- Then, there's a `Path.curve()` call that takes 3 points as arguments
|
||||
|
||||
If you've read through the high-level [Design guide](/guides/designs) you
|
||||
will have learned that paths always start with a `move()` operation. In this
|
||||
case, we moved to our `right` points.
|
||||
|
||||
From there, we drew a cubic Bézier curve to our `bottom` point by using
|
||||
`rightCp1` and `bottomCp2` as control points.
|
||||
|
||||
:::tip
|
||||
|
||||
Many of the methods in the FreeSewing API are *chainable* allowing you
|
||||
to string them together like in this example.
|
||||
:::
|
||||
|
||||
When all is said and done, we now have a quarter of our neck opening.
|
||||
The only problem is, we have no guarantee whatsoever that this opening is the correct size.
|
||||
|
||||
Rather than hope it is the correct size, we'll make sure it is next.
|
|
@ -0,0 +1,174 @@
|
|||
---
|
||||
title: Creating the closure
|
||||
order: 91
|
||||
---
|
||||
|
||||
Things are starting to look good, but we can't fit the bib over the baby's head like this.
|
||||
So we must create a closure. We'll let the straps overlap at the end, and put in a snap
|
||||
later.
|
||||
|
||||
## Using macros
|
||||
|
||||
To round the straps, we'll use something new: **a macro**. To use macros, we
|
||||
need the `macro` method, which we can destructure to get access to it.
|
||||
|
||||
Macros are little helpers that automate things that would otherwise get rather
|
||||
tedious. There are macros to add titles to our pattern, or grainline
|
||||
indicators, a scalebox, and there's a macro to round corners. The `round`
|
||||
macro.
|
||||
|
||||
:::note You can find more information on the `round` macro in [the macros docs](/reference/macros/round/).:::
|
||||
|
||||
We need a half circle here, but the `round` macro works on 90° angles, so
|
||||
we'll use it twice. As such, we'll add some points to guide the macro, and
|
||||
then put it to work.
|
||||
|
||||
Like our neck opening, we've only drawn half since we can simply copy the
|
||||
points to the other side.
|
||||
|
||||
<Example tutorial caption="Now the straps overlap. Which doesn't work for a pattern as it would make it impossible to cut it out of a single piece of fabric. So let's deal with the overlap next.">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
// highlight-start
|
||||
macro,
|
||||
// highlight-end
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(
|
||||
tweak * measurements.head / 10,
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
tweak * measurements.head / 12
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
.hide()
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
/*
|
||||
* Construct the complete neck opening
|
||||
*/
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.top)
|
||||
.curve(points.topCp2, points.leftCp1, points.left)
|
||||
.curve(points.leftCp2, points.bottomCp1, points.bottom)
|
||||
.curve(points.bottomCp2, points.rightCp1, points.right)
|
||||
.curve(points.rightCp2, points.topCp1, points.top)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
|
||||
/*
|
||||
* Drawing the bib outline
|
||||
*/
|
||||
const width = measurements.head * options.widthRatio
|
||||
const length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(
|
||||
width / -2,
|
||||
points.top.y - (width / 2 - points.right.x)
|
||||
)
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
/*
|
||||
* Shape the straps
|
||||
*/
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
|
||||
points.topLeft,
|
||||
0.5
|
||||
)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
// highlight-start
|
||||
// Round the straps
|
||||
const strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
macro("round", {
|
||||
id: "tipRightTop",
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
hide: false
|
||||
})
|
||||
macro("round", {
|
||||
id: "tipRightBottom",
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
hide: false
|
||||
})
|
||||
// highlight-end
|
||||
|
||||
/*
|
||||
* Now, adapt our `rect` path so it's no longer a rectangle:
|
||||
*/
|
||||
paths.rect = new Path()
|
||||
.move(points.edgeTop)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.edgeTop)
|
||||
.close()
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
||||
Notice that we always draw our path at the end after we've manipulated our points.
|
|
@ -0,0 +1,99 @@
|
|||
---
|
||||
title: A part's draft method
|
||||
order: 50
|
||||
---
|
||||
|
||||
Time to turn our attention to the draft method of our part.
|
||||
Inside our `src/bib.mjs` file, this is what it currently looks like:
|
||||
|
||||
```design/src/bib.mjs
|
||||
function draftBib({ part }) {
|
||||
return part
|
||||
}
|
||||
```
|
||||
|
||||
This is an empty skeleton for a draft method. A draft method should always
|
||||
return the part object, and that's effectively the only thing it currently
|
||||
does.
|
||||
|
||||
## Destructuring the function parameter
|
||||
|
||||
If you're not familiar with the `({ part })` syntax you see above, this is a
|
||||
technique called *parameter destructuring* or more generally, [object
|
||||
destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment).
|
||||
|
||||
The draft method receives only 1 parameter: An object that holds everything we
|
||||
need to draft our method. Destructuring is a way to *pull things out of the
|
||||
object into their own variable*. It saves us a bunch of typing as these two are
|
||||
equivalent:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value='without' label="Without descructuring">
|
||||
|
||||
```design/src/bib.mjs
|
||||
function draftBib(props) {
|
||||
|
||||
return props.part
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value='with' label="With descructuring">
|
||||
|
||||
```design/src/bib.mjs
|
||||
function draftBib({ part }) {
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
As we'll make our way through this tutorial, we'll need more and more stuff, so
|
||||
we'll be pulling it out of the object passed to the draft method via
|
||||
*destructuring*.
|
||||
|
||||
:::note
|
||||
|
||||
If you're new to JavaScript, and don't intuitively _get this_, stick with it. It will become second nature soon enough.
|
||||
|
||||
:::
|
||||
|
||||
## Destructuring what we need to start drawing our bib
|
||||
|
||||
Change the function to look like this:
|
||||
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
// highlight-start
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
// highlight-end
|
||||
part,
|
||||
}) {
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
|
||||
That's a bunch of new lines, but each of one gives us something we'll use in this
|
||||
tutorial.
|
||||
|
||||
For a complete list of what you can access via destructuring like this, refer
|
||||
to [the draft method reference documentation](/reference/api/part/draft).
|
||||
Here's a brief summary of the things we've added above:
|
||||
|
||||
- `Path`: The Path constructor, allows us to create new Paths
|
||||
- `Point`: The Point constructor, allows us to create new Points
|
||||
- `points`: A container object to hold the part's points
|
||||
- `paths`: A container object to hold the part's paths
|
||||
|
||||
:::tipRemember: Constructors start with a **C**apital letter:::
|
||||
|
||||
Long story short: These will make it possible for us to draw points and paths easily.
|
||||
|
||||
So let's go ahead and do that.
|
|
@ -0,0 +1,145 @@
|
|||
---
|
||||
title: Drawing the bib outline
|
||||
order: 88
|
||||
---
|
||||
|
||||
With our neck opening in place, let us draw the basic outline of our bib.
|
||||
|
||||
<Example tutorial caption="Note how the neck opening is the same distance from the left, right, and top edge">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
part,
|
||||
}) {
|
||||
|
||||
// Construct the quarter neck opening
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(
|
||||
tweak * measurements.head / 10,
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
tweak * measurements.head / 12
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
.hide()
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
/*
|
||||
* Construct the complete neck opening
|
||||
*/
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.top)
|
||||
.curve(points.topCp2, points.leftCp1, points.left)
|
||||
.curve(points.leftCp2, points.bottomCp1, points.bottom)
|
||||
.curve(points.bottomCp2, points.rightCp1, points.right)
|
||||
.curve(points.rightCp2, points.topCp1, points.top)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* Drawing the bib outline
|
||||
*/
|
||||
const width = measurements.head * options.widthRatio
|
||||
const length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(
|
||||
width / -2,
|
||||
points.top.y - (width / 2 - points.right.x)
|
||||
)
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
paths.rect = new Path()
|
||||
.move(points.topLeft)
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
.line(points.topRight)
|
||||
.line(points.topLeft)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
// highlight-end
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
||||
First thing we did was create the `width` and `length` variables to
|
||||
save ourselves some typing:
|
||||
|
||||
```js
|
||||
const width = measurements.head * options.widthRatio
|
||||
const length = measurements.head * options.lengthRatio
|
||||
```
|
||||
|
||||
Both the length and width of our bib are a factor of the head circumference.
|
||||
This way, our bib size will adapt to the size of the baby, and the user can tweak
|
||||
the length and width by playing with the options we added to the pattern.
|
||||
|
||||
Once we have our variables, we're adding some new points, and a second path called `rect`.
|
||||
|
||||
```js
|
||||
points.topLeft = new Point(
|
||||
width / -2,
|
||||
points.top.y - (width / 2 - points.right.x)
|
||||
)
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
paths.rect = new Path()
|
||||
.move(points.topLeft)
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
.line(points.topRight)
|
||||
.line(points.topLeft)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
```
|
||||
|
||||
We're calculating the `topLeft` point so that the top edge of our bib
|
||||
and the sides are equidistant from the neck opening.
|
||||
|
||||
We didn't have to do that. But it looks nicely balanced this way.
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
---
|
||||
title: Drawing the straps
|
||||
order: 93
|
||||
---
|
||||
|
||||
All we have to do now is flip a bunch of points on the other side,
|
||||
and create one single path that follows our bib outline.
|
||||
|
||||
And as we now have one path to draw the bib, we can (and should)
|
||||
remove the earlier paths we drew to see what we are doing.
|
||||
|
||||
The `round` macro we added earlier is still required to calculate the points we
|
||||
need to construct the half-circle. But we don't want it to draw the half-circle
|
||||
path. As it happens, that is the default behaviour, so we merely have to remove
|
||||
its `hidden: false` property.
|
||||
|
||||
<Example tutorial caption="It is starting to look good. But this sharp corners at the bottom don't exactly say baby, do they?">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
utils,
|
||||
macro,
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the neck opening
|
||||
*/
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(
|
||||
tweak * measurements.head / 10,
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
tweak * measurements.head / 12
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
.hide()
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
/*
|
||||
* Construct the complete neck opening
|
||||
*/
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
// strikeout-start
|
||||
/* Remove this path
|
||||
paths.neck = new Path()
|
||||
.move(points.top)
|
||||
.curve(points.topCp2, points.leftCp1, points.left)
|
||||
.curve(points.leftCp2, points.bottomCp1, points.bottom)
|
||||
.curve(points.bottomCp2, points.rightCp1, points.right)
|
||||
.curve(points.rightCp2, points.topCp1, points.top)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
*/
|
||||
// strikeout-end
|
||||
|
||||
// Drawing the bib outline
|
||||
const width = measurements.head * options.widthRatio
|
||||
const length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(
|
||||
width / -2,
|
||||
points.top.y - (width / 2 - points.right.x)
|
||||
)
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
/*
|
||||
* Shape the straps
|
||||
*/
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
|
||||
points.topLeft,
|
||||
0.5
|
||||
)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
// Round the straps
|
||||
const strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro("round", {
|
||||
id: "tipRightTop",
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
// strikeout-start
|
||||
/* Remove this to have the macro
|
||||
* only create the points we need
|
||||
* and not draw a path
|
||||
hide: false
|
||||
*/
|
||||
// strikeout-end
|
||||
}),
|
||||
tipRightBottom: macro("round", {
|
||||
id: "tipRightBottom",
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
// strikeout-start
|
||||
/* Remove this to have the macro
|
||||
* only create the points we need
|
||||
* and not draw a path
|
||||
hide: false
|
||||
*/
|
||||
// strikeout-end
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the list of points we need to rotate
|
||||
* to move our strap out of the way
|
||||
*/
|
||||
|
||||
const rotateThese = [
|
||||
"edgeTopLeftCp",
|
||||
"edgeTop",
|
||||
"tipRight",
|
||||
"tipRightTop",
|
||||
"tipRightTopStart",
|
||||
"tipRightTopCp1",
|
||||
"tipRightTopCp2",
|
||||
"tipRightTopEnd",
|
||||
"tipRightBottomStart",
|
||||
"tipRightBottomCp1",
|
||||
"tipRightBottomCp2",
|
||||
"tipRightBottomEnd",
|
||||
"tipRightBottom",
|
||||
"top",
|
||||
"topCp2"
|
||||
]
|
||||
/*
|
||||
* We're rotating all the points in
|
||||
* the `rotateThese` array around
|
||||
* the `edgeLeft` point.
|
||||
*
|
||||
* We're using increments of 1 degree
|
||||
* until the `tipRightBottomStart` point
|
||||
* is 1 mm beyond the center of our bib.
|
||||
*/
|
||||
while (points.tipRightBottomStart.x > -1) {
|
||||
for (const p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
|
||||
}
|
||||
|
||||
// strikeout-start
|
||||
/* Remove this repetition
|
||||
macro("round", {
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
prefix: "tipRightTop",
|
||||
hide: false,
|
||||
class: 'contrast dotted',
|
||||
})
|
||||
macro("round", {
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
prefix: "tipRightBottom",
|
||||
hide: false,
|
||||
class: 'contrast dotted',
|
||||
})
|
||||
|
||||
paths.rect = new Path()
|
||||
.move(points.edgeTop)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.edgeTop)
|
||||
.close()
|
||||
*/
|
||||
// strikeout-end
|
||||
|
||||
// highlight-start
|
||||
// Add points for second strap
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
points.topCp1 = points.topCp2.flipX()
|
||||
points.tipLeftTopStart = points.tipRightTopStart.flipX()
|
||||
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
|
||||
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
|
||||
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
|
||||
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
|
||||
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
|
||||
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
|
||||
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
|
||||
|
||||
// Create one path for the bib outline
|
||||
paths.seam = new Path()
|
||||
.move(points.edgeLeft)
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
.line(points.edgeRight)
|
||||
.curve(
|
||||
points.edgeRightCp,
|
||||
points.edgeTopRightCp,
|
||||
points.tipLeftTopStart
|
||||
)
|
||||
.curve(
|
||||
points.tipLeftTopCp1,
|
||||
points.tipLeftTopCp2,
|
||||
points.tipLeftTopEnd
|
||||
)
|
||||
.curve(
|
||||
points.tipLeftBottomCp1,
|
||||
points.tipLeftBottomCp2,
|
||||
points.tipLeftBottomEnd
|
||||
)
|
||||
.curve(
|
||||
points.topCp1,
|
||||
points.rightCp2,
|
||||
points.right
|
||||
)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
.curve(
|
||||
points.bottomCp1,
|
||||
points.leftCp2,
|
||||
points.left
|
||||
)
|
||||
.curve(
|
||||
points.leftCp1,
|
||||
points.topCp2,
|
||||
points.tipRightBottomEnd
|
||||
)
|
||||
.curve(
|
||||
points.tipRightBottomCp2,
|
||||
points.tipRightBottomCp1,
|
||||
points.tipRightBottomStart
|
||||
)
|
||||
.curve(
|
||||
points.tipRightTopCp2,
|
||||
points.tipRightTopCp1,
|
||||
points.tipRightTopStart
|
||||
)
|
||||
.curve(
|
||||
points.edgeTopLeftCp,
|
||||
points.edgeLeftCp,
|
||||
points.edgeLeft
|
||||
)
|
||||
.close()
|
||||
.addClass("fabric")
|
||||
// highlight-end
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
title: Fitting the neck opening
|
||||
order: 70
|
||||
---
|
||||
|
||||
We are not going to create some opening that we _hope_ is the right size, we're
|
||||
going to make sure it is. Here's how we'll make sure the neck opening is _just
|
||||
right_:
|
||||
|
||||
<Example tutorial caption="It might look the same as before, but now it's just right">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
// highlight-start
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
// highlight-end
|
||||
points.right = new Point(
|
||||
// highlight-start
|
||||
tweak * measurements.head / 10,
|
||||
// highlight-end
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
// highlight-start
|
||||
tweak * measurements.head / 12
|
||||
// highlight-end
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
|
||||
|
||||
// highlight-start
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
// highlight-end
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
||||
We've added a few new variables:
|
||||
|
||||
- `tweak`: A _tweak factor_ that we'll use to increase or decrease the neck
|
||||
opening by making it more or less than 1
|
||||
- `target`: How long our (quarter) neck opening should be
|
||||
- `delta`: How far we're off. Positive numbers mean it's too long, negative
|
||||
means too short
|
||||
|
||||
Now that we know what `target` is, we construct our path as we did before. But
|
||||
this time around, we multiply our point coordinates with our `tweak` variable
|
||||
(1 at the start).
|
||||
|
||||
Then, we compare our `target` to the result of `paths.neck.length()` which —
|
||||
you guessed it — returns the length of our neck path.
|
||||
|
||||
If the delta is positive, our path is too long and we reduce the tweak factor.
|
||||
If the delta is negative, our path is too short and we increase the tweak
|
||||
factor.
|
||||
|
||||
We keep on doing this until `Math.abs(delta)` is less than 1. Meaning that we
|
||||
are within 1 mm of our target value.
|
||||
|
||||
Now that we're happy with the length of our quarter neck opening, let's
|
||||
complete the entire neck opening.
|
BIN
sites/dev/docs/tutorials/pattern-design/part2/fromscratch.png
Normal file
BIN
sites/dev/docs/tutorials/pattern-design/part2/fromscratch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 219 KiB |
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
title: Creating a new design
|
||||
order: 10
|
||||
---
|
||||
|
||||
The development environment has already setup various designs for us.
|
||||
Since I am using the **From scratch** template, the files I want to edit live
|
||||
in `design/from-scratch`.
|
||||
|
||||
The design's main file is `design/from-scratch/src/index.mjs`, and our bib part
|
||||
will live in `design/from-scratch/src/bib.mjs`.
|
||||
|
||||
This `bib.mjs` file is where will be doing most of the work here in part 2 of this
|
||||
tutorial. But let's start with the `index.mjs` file as an appetizer, because this
|
||||
is where a new FreeSewing design is brought to life. It looks like this:
|
||||
|
||||
```src/index.mjs
|
||||
import { Design } from '@freesewing/core'
|
||||
import { i18n } from '../i18n/index.mjs'
|
||||
import { bib } from './bib.mjs'
|
||||
|
||||
/*
|
||||
* Create the design
|
||||
*/
|
||||
const FromScratch = new Design({
|
||||
data: {
|
||||
name: 'fromScratch',
|
||||
version: '0.0.1',
|
||||
},
|
||||
parts: [bib],
|
||||
})
|
||||
|
||||
export { bib, FromScratch, i18n }
|
||||
```
|
||||
|
||||
Not too intimidating, is it?
|
||||
|
||||
## Imports
|
||||
|
||||
At the top of the file, we have a bunch of *imports*:
|
||||
|
||||
```src/index.mjs
|
||||
import { Design } from '@freesewing/core'
|
||||
import { i18n } from '../i18n/index.mjs'
|
||||
import { bib } from './bib.mjs'
|
||||
```
|
||||
|
||||
An `import` is how JavaScript loads code from a different file. \
|
||||
It essentially says: _import **something** from **somewhere**_:
|
||||
|
||||
| Something | Somewhere | Description |
|
||||
| ---------:|:--------- |:----------- |
|
||||
| `Design` | `@freesewing/core` | Loads the `Design` constructor from FreeSewing's core library |
|
||||
| `i18n` | `../i18n/index.mjs` | Loads `i18n` from the `index.mjs` file in the `i18n` one level higher (these are the translations) |
|
||||
| `bib` | `./bib.mjs` | Loads `bib` from the `bib.mjs` file in the same folder (this is our part) |
|
||||
|
||||
As you can see, the *somewhere* can be different things. A local file like in
|
||||
lines 2 and 3, or a package published on
|
||||
[NPM](https://www.npmjs.com/package/@freesewing/core), in line 1.
|
||||
|
||||
It's nothing for you to worry about too much at this point, but it does help us
|
||||
understand what's going on at the bottom of our file.
|
||||
|
||||
## Exports
|
||||
|
||||
The bottom holds a single `export` line:
|
||||
|
||||
```src/index.mjs
|
||||
export { bib, FromScratch, i18n }
|
||||
```
|
||||
|
||||
When we `export` things from our code, we allow others to `import` those things
|
||||
for re-use in their code. That's how it works.
|
||||
|
||||
These are named exports. We are exporting three things:
|
||||
|
||||
- `bib` is our part. We are exporting it so people can re-use just this part.
|
||||
- `FromScratch` is our complete design. Exporting it means people can use it.
|
||||
- `i18n` are the translations. We are exporting it so people can load them to use with their own translation solution.
|
||||
|
||||
If you are not familiar with this syntax, you'll get the hang of it soon enough.
|
||||
|
||||
## Design constructor
|
||||
|
||||
Finally, the most interesting part of this file is the middle part where we are
|
||||
creating a new design:
|
||||
|
||||
```src/index.mjs
|
||||
const FromScratch = new Design({
|
||||
data: {
|
||||
name: 'fromScratch',
|
||||
version: '0.0.1',
|
||||
},
|
||||
parts: [bib],
|
||||
})
|
||||
```
|
||||
|
||||
The `Design` that we imported on line 1 is a so-called **constructor**.
|
||||
A constructor is a function that can create things out of nothing. Or,
|
||||
to be more accurate, that you can use with the `new` keyword.
|
||||
|
||||
:::tip
|
||||
|
||||
It's a convention that constructor names start with an **C**apital letter.
|
||||
:::
|
||||
|
||||
We are passing some info to this `Design` function, but the `data` we are
|
||||
passing is optional. If we strip that away for a moment, and don't bother
|
||||
assigning the result to a variable, we reveal the essence of what it takes to
|
||||
create a new FreeSewing design:
|
||||
|
||||
```src/index.mjs
|
||||
new Design({
|
||||
parts: [bib],
|
||||
})
|
||||
```
|
||||
|
||||
In several places in this documentation, I will mention that *a design is not
|
||||
much more than a thin wrapper around parts*. But I feel nothing drives
|
||||
that point home like seeing it in code like this.
|
||||
|
||||
To create a new design, we call the `Design` constructor and give it a list
|
||||
(an array) or parts.
|
||||
|
||||
:::note RELATED
|
||||
|
||||
Refer to [the design reference documentation](/reference/api/design) for
|
||||
all details about what you can pass to the Design constructor.
|
||||
:::
|
||||
|
||||
That's it. So let's look at where the real action is next: Our first part.
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
title: Creating a part
|
||||
order: 20
|
||||
---
|
||||
|
||||
Much like garments themselves, patterns are made up of _parts_.
|
||||
Most patterns will have multiple parts. A sleeve, a back part, the collar, and
|
||||
so on. The pattern you create today is very simple, and only has one part: the bib.
|
||||
|
||||
:::tip
|
||||
|
||||
It's a good idea to keep each part in its own file. You don't *have to* do
|
||||
this, but it's a good habit to get into.
|
||||
|
||||
:::
|
||||
|
||||
## bib.mjs
|
||||
|
||||
I am going to use the **From scratch** template. So the files I want to edit live
|
||||
in `design/from-scratch`.
|
||||
|
||||
Our part lives in `design/from-scratch/src/bib.mjs`, and it currently looks like this:
|
||||
|
||||
```src/bib.mjs
|
||||
function draftBib({ part }) {
|
||||
return part
|
||||
}
|
||||
|
||||
export const bib = {
|
||||
name: 'fromscratch.bib',
|
||||
draft: draftBib,
|
||||
}
|
||||
```
|
||||
|
||||
## The part object
|
||||
|
||||
Each part in FreeSewing is an object that describes the part, and has a `draft`
|
||||
method to do the actual work of drafting the part.
|
||||
|
||||
The only mandatory keys on a part object are `name` and `draft`.
|
||||
|
||||
:::note RELATED
|
||||
|
||||
Refer to [the part reference documentation](/reference/api/part) for
|
||||
all details about configuring the part object
|
||||
:::
|
||||
|
||||
:::note
|
||||
|
||||
A design in FreeSewing is a thin wrapper around a collection of parts.
|
||||
Each parts stands on its own, and parts from various designs can be combined
|
||||
to create other designs.
|
||||
:::
|
||||
|
||||
|
||||
### The part name
|
||||
|
||||
```src/bib.mjs
|
||||
name: 'fromscratch.bib',
|
||||
```
|
||||
|
||||
A part's `name` should be unique in a design. I used `fromscratch.bib` as the
|
||||
name, because that makes sense.
|
||||
|
||||
:::warning
|
||||
|
||||
I **strongly** recommend that you apply the same `designName.partName` naming scheme in all your parts.
|
||||
This avoids naming conflicts when mixing parts from various designs to create a new design.
|
||||
|
||||
:::
|
||||
|
||||
### The part's draft method
|
||||
|
||||
```src/bib.mjs
|
||||
draft: draftBib,
|
||||
```
|
||||
|
||||
The second mandatory key on the part object is `draft` which should hold the method that drafts the part.
|
||||
|
||||
In the example above, it refers to the `draftBib` function that was defined at the top of the same `bib.mjs` file.
|
||||
|
||||
For now this function doesn't do much, but that will change soon enough.
|
||||
|
||||
:::note
|
||||
This structure of putting the draft method at the top of the file and
|
||||
the part object at the bottom is a bit of a convention in FreeSewing.
|
||||
:::
|
||||
|
||||
We'll take a deeper look at our part's draft method soon. For now, let's look at adding measurements to our part.
|
Binary file not shown.
After Width: | Height: | Size: 277 KiB |
47
sites/dev/docs/tutorials/pattern-design/part2/readme.mdx
Normal file
47
sites/dev/docs/tutorials/pattern-design/part2/readme.mdx
Normal file
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: "Part 2: Parametric design"
|
||||
---
|
||||
|
||||
Welcome to part 2 of this FreeSewing pattern design tutorial.
|
||||
In this part I will show you how to design a bespoke
|
||||
sewing pattern, start to finish.
|
||||
|
||||
:::tip
|
||||
##### Before you start
|
||||
|
||||
This tutorial assumes you are familiar with the following:
|
||||
|
||||
- Scalable Vector Graphics
|
||||
- The coordinate system
|
||||
- Units in FreeSewing
|
||||
- Cubic Bézier curves
|
||||
|
||||
Which is a lot to assume. So if you'd like you can take a quick detour
|
||||
via our [Before you start guide](/guides/prerequisites). \
|
||||
It's very short, but covers some basic
|
||||
terminology and concepts that we'll use throughout this guide.
|
||||
:::
|
||||
|
||||
## Pick a template
|
||||
|
||||
The FreeSewing development environment ships with several templates you
|
||||
can start from. I recommend you start **From scratch** as you'll learn the most
|
||||
that way. But you can also start from the **Tutorial** template in which case
|
||||
you will already have the end result we are aiming for today:
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
Depending on the choice you made, you will need to edit files in a different folder.
|
||||
|
||||
- Edit files in `design/from-scratch` if you are using the **From scratch** template
|
||||
- Edit files in `design/tutorial` if you are using the **Tutorial** template
|
||||
|
||||
You can choose either, or even switch back and forth between both.
|
||||
|
||||
To follow along step-by-step with the tutorial, go to the `design/from-scratch` folder.
|
|
@ -0,0 +1,288 @@
|
|||
---
|
||||
title: Rounding the corners
|
||||
order: 94
|
||||
---
|
||||
|
||||
We already know how to round corners, we'll have the `round` macro take care of that for us.
|
||||
|
||||
With our corners rounded, we should also update our path.
|
||||
Fortunately, we merely have to update the start of it.
|
||||
|
||||
<Example tutorial caption="The shape of our bib is now completed">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
utils,
|
||||
macro,
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(
|
||||
tweak * measurements.head / 10,
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
tweak * measurements.head / 12
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
.hide()
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
/*
|
||||
* Construct the complete neck opening
|
||||
*/
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
// Drawing the bib outline
|
||||
const width = measurements.head * options.widthRatio
|
||||
const length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(
|
||||
width / -2,
|
||||
points.top.y - (width / 2 - points.right.x)
|
||||
)
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
/*
|
||||
* Shape the straps
|
||||
*/
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
|
||||
points.topLeft,
|
||||
0.5
|
||||
)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
// Round the straps
|
||||
const strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro("round", {
|
||||
id: "tipRightTop",
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
}),
|
||||
tipRightBottom: macro("round", {
|
||||
id: "tipRightBottom",
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the list of points we need to rotate
|
||||
* to move our strap out of the way
|
||||
*/
|
||||
const rotateThese = [
|
||||
"edgeTopLeftCp",
|
||||
"edgeTop",
|
||||
"tipRight",
|
||||
"tipRightTop",
|
||||
"tipRightTopStart",
|
||||
"tipRightTopCp1",
|
||||
"tipRightTopCp2",
|
||||
"tipRightTopEnd",
|
||||
"tipRightBottomStart",
|
||||
"tipRightBottomCp1",
|
||||
"tipRightBottomCp2",
|
||||
"tipRightBottomEnd",
|
||||
"tipRightBottom",
|
||||
"top",
|
||||
"topCp2"
|
||||
]
|
||||
/*
|
||||
* We're rotating all the points in
|
||||
* the `rotateThese` array around
|
||||
* the `edgeLeft` point.
|
||||
*
|
||||
* We're using increments of 1 degree
|
||||
* until the `tipRightBottomStart` point
|
||||
* is 1 mm beyond the center of our bib.
|
||||
*/
|
||||
while (points.tipRightBottomStart.x > -1) {
|
||||
for (const p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
|
||||
}
|
||||
|
||||
// Add points for second strap
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
points.topCp1 = points.topCp2.flipX()
|
||||
points.tipLeftTopStart = points.tipRightTopStart.flipX()
|
||||
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
|
||||
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
|
||||
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
|
||||
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
|
||||
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
|
||||
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
|
||||
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* Round the bottom corners
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids2 = {
|
||||
bottomLeft: macro("round", {
|
||||
id: "bottomLeft",
|
||||
from: points.topLeft,
|
||||
to: points.bottomRight,
|
||||
via: points.bottomLeft,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
bottomRight: macro("round", {
|
||||
id: "bottomRight",
|
||||
from: points.bottomLeft,
|
||||
to: points.topRight,
|
||||
via: points.bottomRight,
|
||||
radius: points.bottomRight.x / 4,
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids2) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
// Create one path for the bib outline
|
||||
paths.seam = new Path()
|
||||
.move(points.edgeLeft)
|
||||
// strikeout-start
|
||||
/* We only need to replace the start
|
||||
* with the new lines below
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
*/
|
||||
// strikeout-end
|
||||
// highlight-start
|
||||
.line(points.bottomLeftStart)
|
||||
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
|
||||
.line(points.bottomRightStart)
|
||||
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
|
||||
// highlight-end
|
||||
.line(points.edgeRight)
|
||||
.curve(
|
||||
points.edgeRightCp,
|
||||
points.edgeTopRightCp,
|
||||
points.tipLeftTopStart
|
||||
)
|
||||
.curve(
|
||||
points.tipLeftTopCp1,
|
||||
points.tipLeftTopCp2,
|
||||
points.tipLeftTopEnd
|
||||
)
|
||||
.curve(
|
||||
points.tipLeftBottomCp1,
|
||||
points.tipLeftBottomCp2,
|
||||
points.tipLeftBottomEnd
|
||||
)
|
||||
.curve(
|
||||
points.topCp1,
|
||||
points.rightCp2,
|
||||
points.right
|
||||
)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
.curve(
|
||||
points.bottomCp1,
|
||||
points.leftCp2,
|
||||
points.left
|
||||
)
|
||||
.curve(
|
||||
points.leftCp1,
|
||||
points.topCp2,
|
||||
points.tipRightBottomEnd
|
||||
)
|
||||
.curve(
|
||||
points.tipRightBottomCp2,
|
||||
points.tipRightBottomCp1,
|
||||
points.tipRightBottomStart
|
||||
)
|
||||
.curve(
|
||||
points.tipRightTopCp2,
|
||||
points.tipRightTopCp1,
|
||||
points.tipRightTopStart
|
||||
)
|
||||
.curve(
|
||||
points.edgeTopLeftCp,
|
||||
points.edgeLeftCp,
|
||||
points.edgeLeft
|
||||
)
|
||||
.close()
|
||||
.addClass("fabric")
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
|
@ -0,0 +1,153 @@
|
|||
---
|
||||
title: Shaping the straps
|
||||
order: 90
|
||||
---
|
||||
|
||||
Our straps should follow the neck opening, which isn't that hard to do.
|
||||
We just need to keep the control points of our curves at similar proportions.
|
||||
Which means, halfway between the start of the curve, and the corner of our rectangle.
|
||||
|
||||
:::note
|
||||
|
||||
For this, we'll be using a new method: `Point.shiftFractionTowards()`. We've already
|
||||
used `Point.shift()` and there's also `Point.shiftTowards()` and `Point.shiftOutwards()`.
|
||||
As always, [the API docs](/reference/api/point/) have all the details.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
<Example tutorial caption="All of a sudden, things are starting to look like a bib">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(
|
||||
tweak * measurements.head / 10,
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
tweak * measurements.head / 12
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
.hide()
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
/*
|
||||
* Construct the complete neck opening
|
||||
*/
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.top)
|
||||
.curve(points.topCp2, points.leftCp1, points.left)
|
||||
.curve(points.leftCp2, points.bottomCp1, points.bottom)
|
||||
.curve(points.bottomCp2, points.rightCp1, points.right)
|
||||
.curve(points.rightCp2, points.topCp1, points.top)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
|
||||
/*
|
||||
* Drawing the bib outline
|
||||
*/
|
||||
const width = measurements.head * options.widthRatio
|
||||
const length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(
|
||||
width / -2,
|
||||
points.top.y - (width / 2 - points.right.x)
|
||||
)
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
// strikeout-start
|
||||
/*
|
||||
* Remove this path
|
||||
|
||||
paths.rect = new Path()
|
||||
.move(points.topLeft)
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
.line(points.topRight)
|
||||
.line(points.topLeft)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
*/
|
||||
// strikeout-end
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* Shape the straps
|
||||
*/
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
|
||||
points.topLeft,
|
||||
0.5
|
||||
)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
/*
|
||||
* Now, adapt our `rect` path so it's no longer a rectangle:
|
||||
*/
|
||||
paths.rect = new Path()
|
||||
.move(points.edgeTop)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.edgeTop)
|
||||
.close()
|
||||
|
||||
// highlight-end
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
BIN
sites/dev/docs/tutorials/pattern-design/part2/tutorial.png
Normal file
BIN
sites/dev/docs/tutorials/pattern-design/part2/tutorial.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 308 KiB |
Loading…
Add table
Add a link
Reference in a new issue