1
0
Fork 0

chore: Updated tutorial for v3

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,12 @@
---
title: Drawing the bib outline
order: 190
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
```src/bib.mjs
function draftBib({
Path,
Point,

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB