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,527 @@
|
|||
---
|
||||
title: Adding annotations
|
||||
order: 20
|
||||
---
|
||||
|
||||
Our pattern is still a little bit *bare*. It would be nice to add some *annotations* to it.
|
||||
|
||||
When I say *annotations* it's an umbrella term for things like text or other
|
||||
bits of information that help the user understand the pattern.
|
||||
|
||||
## Adding snippets
|
||||
|
||||
Snippets are little re-useable things to embellish our pattern with.
|
||||
Things like buttons or buttonholes, a logo, or snaps.
|
||||
|
||||
To use them, much like points and paths, we need to destructure both
|
||||
the `Snippet` constructor as well as the `snippets` object to hold
|
||||
our snippets.
|
||||
|
||||
We'll be using them below to add a `snap-stud`, `snap-socket`, and a `logo`
|
||||
snippet.
|
||||
|
||||
:::note
|
||||
You can find all possible snippets in [our documentation](/reference/api/snippet/).
|
||||
:::
|
||||
|
||||
<Example previewFirst tutorial caption="This looks better">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Point,
|
||||
points,
|
||||
Path,
|
||||
paths,
|
||||
utils,
|
||||
measurements,
|
||||
options,
|
||||
macro,
|
||||
// highlight-start
|
||||
Snippet,
|
||||
snippets,
|
||||
// highlight-end
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the neck opening
|
||||
*/
|
||||
const target = (measurements.head * options.neckRatio) / 4
|
||||
let tweak = 1
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point((tweak * measurements.head) / 10, 0)
|
||||
points.bottom = new Point(0, (tweak * measurements.head) / 12)
|
||||
|
||||
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
|
||||
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
|
||||
delta = paths.neck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
/*
|
||||
* Construct the outline
|
||||
*/
|
||||
let width = measurements.head * options.widthRatio
|
||||
let length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
/*
|
||||
* Round the end of the straps
|
||||
*/
|
||||
let strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro('round', {
|
||||
id: 'tipRightTop',
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
}),
|
||||
tipRightBottom: macro('round', {
|
||||
id: 'tipRightBottom',
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
}),
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotate straps so they don't overlap
|
||||
*/
|
||||
let rotateThese = [
|
||||
'edgeTopLeftCp',
|
||||
'edgeTop',
|
||||
'tipRight',
|
||||
'tipRightTop',
|
||||
'tipRightTopStart',
|
||||
'tipRightTopCp1',
|
||||
'tipRightTopCp2',
|
||||
'tipRightTopEnd',
|
||||
'tipRightBottomStart',
|
||||
'tipRightBottomCp1',
|
||||
'tipRightBottomCp2',
|
||||
'tipRightBottomEnd',
|
||||
'tipRightBottom',
|
||||
'top',
|
||||
'topCp2',
|
||||
]
|
||||
|
||||
while (points.tipRightBottomStart.x > -1) {
|
||||
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
|
||||
}
|
||||
|
||||
/*
|
||||
* Add points to anchor snaps on
|
||||
*/
|
||||
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
|
||||
|
||||
/*
|
||||
* Mirror points to the other side
|
||||
*/
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
points.topCp1 = points.topCp2.flipX()
|
||||
points.tipLeftTopStart = points.tipRightTopStart.flipX()
|
||||
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
|
||||
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
|
||||
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
|
||||
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
|
||||
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
|
||||
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
|
||||
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
|
||||
points.snapRight = points.snapLeft.flipX()
|
||||
|
||||
/*
|
||||
* Round the bottom of the bib
|
||||
* Radius is fixed, but you could use an option for it)
|
||||
*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids2 = {
|
||||
bottomLeft: macro('round', {
|
||||
id: 'bottomLeft',
|
||||
from: points.topLeft,
|
||||
to: points.bottomRight,
|
||||
via: points.bottomLeft,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
bottomRight: macro('round', {
|
||||
id: 'bottomRight',
|
||||
from: points.bottomLeft,
|
||||
to: points.topRight,
|
||||
via: points.bottomRight,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
}
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids2) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct the path
|
||||
*/
|
||||
paths.seam = new Path()
|
||||
.move(points.edgeLeft)
|
||||
.line(points.bottomLeftStart)
|
||||
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
|
||||
.line(points.bottomRightStart)
|
||||
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
|
||||
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
|
||||
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
|
||||
.curve(points.topCp1, points.rightCp2, points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.curve(points.bottomCp1, points.leftCp2, points.left)
|
||||
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
|
||||
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
|
||||
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.close()
|
||||
.attr('class', 'fabric')
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
*
|
||||
* Annotations
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Add the snaps
|
||||
*/
|
||||
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
|
||||
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
|
||||
|
||||
/*
|
||||
* Add the logo
|
||||
*/
|
||||
points.logo = new Point(0, 0)
|
||||
snippets.logo = new Snippet('logo', points.logo)
|
||||
// highlight-end
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
||||
## Setting the cutlist, adding a title and scalebox
|
||||
|
||||
We are also going to add a title and scalebox. For both of these we will use a macro.
|
||||
The `title` and `scalebox` macros to be precise.
|
||||
|
||||
Before we add the title, we will also set the cutlist via a method that was added to the
|
||||
store by one of the core plugins.
|
||||
|
||||
As a matter of fact, all of these snippets, macros, and store methods are provided by plugins.
|
||||
For more details, [refer to the plugin guide](/guides/plugins).
|
||||
|
||||
<Example previewFirst tutorial caption="This looks way better">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Point,
|
||||
points,
|
||||
Path,
|
||||
paths,
|
||||
utils,
|
||||
measurements,
|
||||
options,
|
||||
macro,
|
||||
complete,
|
||||
snippets,
|
||||
Snippet,
|
||||
// highlight-start
|
||||
store,
|
||||
// highlight-end
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the neck opening
|
||||
*/
|
||||
const target = (measurements.head * options.neckRatio) / 4
|
||||
let tweak = 1
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point((tweak * measurements.head) / 10, 0)
|
||||
points.bottom = new Point(0, (tweak * measurements.head) / 12)
|
||||
|
||||
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
|
||||
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
|
||||
delta = paths.neck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
/*
|
||||
* Construct the outline
|
||||
*/
|
||||
let width = measurements.head * options.widthRatio
|
||||
let length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
/*
|
||||
* Round the end of the straps
|
||||
*/
|
||||
let strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro('round', {
|
||||
id: 'tipRightTop',
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
}),
|
||||
tipRightBottom: macro('round', {
|
||||
id: 'tipRightBottom',
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
}),
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotate straps so they don't overlap
|
||||
*/
|
||||
let rotateThese = [
|
||||
'edgeTopLeftCp',
|
||||
'edgeTop',
|
||||
'tipRight',
|
||||
'tipRightTop',
|
||||
'tipRightTopStart',
|
||||
'tipRightTopCp1',
|
||||
'tipRightTopCp2',
|
||||
'tipRightTopEnd',
|
||||
'tipRightBottomStart',
|
||||
'tipRightBottomCp1',
|
||||
'tipRightBottomCp2',
|
||||
'tipRightBottomEnd',
|
||||
'tipRightBottom',
|
||||
'top',
|
||||
'topCp2',
|
||||
]
|
||||
|
||||
while (points.tipRightBottomStart.x > -1) {
|
||||
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
|
||||
}
|
||||
|
||||
/*
|
||||
* Add points to anchor snaps on
|
||||
*/
|
||||
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
|
||||
|
||||
/*
|
||||
* Mirror points to the other side
|
||||
*/
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
points.topCp1 = points.topCp2.flipX()
|
||||
points.tipLeftTopStart = points.tipRightTopStart.flipX()
|
||||
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
|
||||
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
|
||||
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
|
||||
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
|
||||
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
|
||||
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
|
||||
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
|
||||
points.snapRight = points.snapLeft.flipX()
|
||||
|
||||
/*
|
||||
* Round the bottom of the bib
|
||||
* Radius is fixed, but you could use an option for it)
|
||||
*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids2 = {
|
||||
bottomLeft: macro('round', {
|
||||
id: 'bottomLeft',
|
||||
from: points.topLeft,
|
||||
to: points.bottomRight,
|
||||
via: points.bottomLeft,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
bottomRight: macro('round', {
|
||||
id: 'bottomRight',
|
||||
from: points.bottomLeft,
|
||||
to: points.topRight,
|
||||
via: points.bottomRight,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
}
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids2) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct the path
|
||||
*/
|
||||
paths.seam = new Path()
|
||||
.move(points.edgeLeft)
|
||||
.line(points.bottomLeftStart)
|
||||
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
|
||||
.line(points.bottomRightStart)
|
||||
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
|
||||
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
|
||||
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
|
||||
.curve(points.topCp1, points.rightCp2, points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.curve(points.bottomCp1, points.leftCp2, points.left)
|
||||
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
|
||||
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
|
||||
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.close()
|
||||
.attr('class', 'fabric')
|
||||
|
||||
/*
|
||||
*
|
||||
* Annotations
|
||||
*
|
||||
*/
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* Cut list
|
||||
*/
|
||||
store.cutlist.addCut({ cut: 1, from: 'fabric' })
|
||||
// highlight-end
|
||||
|
||||
/*
|
||||
* Add the snaps
|
||||
*/
|
||||
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
|
||||
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* Add the title
|
||||
*/
|
||||
points.title = points.bottom.shift(-90, 45)
|
||||
macro('title', {
|
||||
at: points.title,
|
||||
nr: 1,
|
||||
title: 'bib',
|
||||
align: 'center',
|
||||
scale: 0.8,
|
||||
})
|
||||
|
||||
/*
|
||||
* Add the scalebox
|
||||
*/
|
||||
points.scalebox = points.title.shift(-90, 65)
|
||||
macro('scalebox', { at: points.scalebox })
|
||||
// highlight-end
|
||||
|
||||
/*
|
||||
* Add the logo
|
||||
*/
|
||||
points.logo = new Point(0, 0)
|
||||
snippets.logo = new Snippet('logo', points.logo)
|
||||
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
|
@ -0,0 +1,561 @@
|
|||
---
|
||||
title: Dealing with laser cutters
|
||||
order: 25
|
||||
---
|
||||
|
||||
Laser cutters is merely an example of a situation where your user wants not the
|
||||
complete detailed pattern with all annotations, but just the outlines.
|
||||
Essentially what we had at the end of part 2 of this tutorial.
|
||||
|
||||
Since then, we've added a bunch of embellishments, and perhaps the user does
|
||||
not want those.
|
||||
|
||||
Well, good news: there is a setting for that too. That setting is `complete`,
|
||||
and more annotations will automatically take it into account.
|
||||
|
||||
For example, if you put a logo or title on the pattern, it will check the
|
||||
`complete` setting and if it is `false` it will do nothing.
|
||||
When we say we're going to *complete* our pattern, we mean we're going to add
|
||||
things like a title and a scalebox and so on.
|
||||
|
||||
So while in most scenarios you don't have to worry about `complete`, you
|
||||
should keep it in mind when you are adding text or paths to the design
|
||||
that should not be shown on a non-complete pattern.
|
||||
|
||||
An example will make this more clear:
|
||||
|
||||
## Adding an indicator for bias tape
|
||||
|
||||
<Example tutorial previewFirst caption="A nice note about bias tape">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Point,
|
||||
points,
|
||||
Path,
|
||||
paths,
|
||||
utils,
|
||||
measurements,
|
||||
options,
|
||||
macro,
|
||||
Snippet,
|
||||
snippets,
|
||||
store,
|
||||
// highlight-start
|
||||
complete,
|
||||
// highlight-end
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the neck opening
|
||||
*/
|
||||
const target = (measurements.head * options.neckRatio) / 4
|
||||
let tweak = 1
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point((tweak * measurements.head) / 10, 0)
|
||||
points.bottom = new Point(0, (tweak * measurements.head) / 12)
|
||||
|
||||
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
|
||||
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
|
||||
delta = paths.neck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
/*
|
||||
* Construct the outline
|
||||
*/
|
||||
let width = measurements.head * options.widthRatio
|
||||
let length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
/*
|
||||
* Round the end of the straps
|
||||
*/
|
||||
let strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro('round', {
|
||||
id: 'tipRightTop',
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
}),
|
||||
tipRightBottom: macro('round', {
|
||||
id: 'tipRightBottom',
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
}),
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotate straps so they don't overlap
|
||||
*/
|
||||
let rotateThese = [
|
||||
'edgeTopLeftCp',
|
||||
'edgeTop',
|
||||
'tipRight',
|
||||
'tipRightTop',
|
||||
'tipRightTopStart',
|
||||
'tipRightTopCp1',
|
||||
'tipRightTopCp2',
|
||||
'tipRightTopEnd',
|
||||
'tipRightBottomStart',
|
||||
'tipRightBottomCp1',
|
||||
'tipRightBottomCp2',
|
||||
'tipRightBottomEnd',
|
||||
'tipRightBottom',
|
||||
'top',
|
||||
'topCp2',
|
||||
]
|
||||
|
||||
while (points.tipRightBottomStart.x > -1) {
|
||||
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
|
||||
}
|
||||
|
||||
/*
|
||||
* Add points to anchor snaps on
|
||||
*/
|
||||
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
|
||||
|
||||
/*
|
||||
* Mirror points to the other side
|
||||
*/
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
points.topCp1 = points.topCp2.flipX()
|
||||
points.tipLeftTopStart = points.tipRightTopStart.flipX()
|
||||
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
|
||||
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
|
||||
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
|
||||
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
|
||||
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
|
||||
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
|
||||
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
|
||||
points.snapRight = points.snapLeft.flipX()
|
||||
|
||||
/*
|
||||
* Round the bottom of the bib
|
||||
* Radius is fixed, but you could use an option for it)
|
||||
*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids2 = {
|
||||
bottomLeft: macro('round', {
|
||||
id: 'bottomLeft',
|
||||
from: points.topLeft,
|
||||
to: points.bottomRight,
|
||||
via: points.bottomLeft,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
bottomRight: macro('round', {
|
||||
id: 'bottomRight',
|
||||
from: points.bottomLeft,
|
||||
to: points.topRight,
|
||||
via: points.bottomRight,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
}
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids2) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct the path
|
||||
*/
|
||||
paths.seam = new Path()
|
||||
.move(points.edgeLeft)
|
||||
.line(points.bottomLeftStart)
|
||||
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
|
||||
.line(points.bottomRightStart)
|
||||
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
|
||||
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
|
||||
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
|
||||
.curve(points.topCp1, points.rightCp2, points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.curve(points.bottomCp1, points.leftCp2, points.left)
|
||||
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
|
||||
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
|
||||
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.close()
|
||||
.attr('class', 'fabric')
|
||||
|
||||
/*
|
||||
*
|
||||
* Annotations
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Cut list
|
||||
*/
|
||||
store.cutlist.addCut({ cut: 1, from: 'fabric' })
|
||||
|
||||
/*
|
||||
* Add the snaps
|
||||
*/
|
||||
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
|
||||
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* Add the bias tape
|
||||
*/
|
||||
if (complete)
|
||||
paths.bias = paths.seam
|
||||
.offset(-5)
|
||||
.addClass('note dashed')
|
||||
.addText('tutorial:finishWithBiasTape', 'center fill-note')
|
||||
// highlight-end
|
||||
|
||||
/*
|
||||
* Add the title
|
||||
*/
|
||||
points.title = points.bottom.shift(-90, 45)
|
||||
macro('title', {
|
||||
at: points.title,
|
||||
nr: 1,
|
||||
title: 'bib',
|
||||
align: 'center',
|
||||
scale: 0.8,
|
||||
})
|
||||
|
||||
/*
|
||||
* Add the scalebox
|
||||
*/
|
||||
points.scalebox = points.title.shift(-90, 65)
|
||||
macro('scalebox', { at: points.scalebox })
|
||||
|
||||
/*
|
||||
* Add the logo
|
||||
*/
|
||||
points.logo = new Point(0, 0)
|
||||
snippets.logo = new Snippet('logo', points.logo)
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
||||
|
||||
And here is the exact same thing, but withe `complete` setting set to `false`:
|
||||
|
||||
<Example tutorial previewFirst settings="complete: false" caption="Same code, but with complete disabled">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Point,
|
||||
points,
|
||||
Path,
|
||||
paths,
|
||||
utils,
|
||||
measurements,
|
||||
options,
|
||||
macro,
|
||||
Snippet,
|
||||
snippets,
|
||||
store,
|
||||
// highlight-start
|
||||
complete,
|
||||
// highlight-end
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the neck opening
|
||||
*/
|
||||
const target = (measurements.head * options.neckRatio) / 4
|
||||
let tweak = 1
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point((tweak * measurements.head) / 10, 0)
|
||||
points.bottom = new Point(0, (tweak * measurements.head) / 12)
|
||||
|
||||
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
|
||||
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
|
||||
delta = paths.neck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
/*
|
||||
* Construct the outline
|
||||
*/
|
||||
let width = measurements.head * options.widthRatio
|
||||
let length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
/*
|
||||
* Round the end of the straps
|
||||
*/
|
||||
let strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro('round', {
|
||||
id: 'tipRightTop',
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
}),
|
||||
tipRightBottom: macro('round', {
|
||||
id: 'tipRightBottom',
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
}),
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotate straps so they don't overlap
|
||||
*/
|
||||
let rotateThese = [
|
||||
'edgeTopLeftCp',
|
||||
'edgeTop',
|
||||
'tipRight',
|
||||
'tipRightTop',
|
||||
'tipRightTopStart',
|
||||
'tipRightTopCp1',
|
||||
'tipRightTopCp2',
|
||||
'tipRightTopEnd',
|
||||
'tipRightBottomStart',
|
||||
'tipRightBottomCp1',
|
||||
'tipRightBottomCp2',
|
||||
'tipRightBottomEnd',
|
||||
'tipRightBottom',
|
||||
'top',
|
||||
'topCp2',
|
||||
]
|
||||
|
||||
while (points.tipRightBottomStart.x > -1) {
|
||||
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
|
||||
}
|
||||
|
||||
/*
|
||||
* Add points to anchor snaps on
|
||||
*/
|
||||
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
|
||||
|
||||
/*
|
||||
* Mirror points to the other side
|
||||
*/
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
points.topCp1 = points.topCp2.flipX()
|
||||
points.tipLeftTopStart = points.tipRightTopStart.flipX()
|
||||
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
|
||||
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
|
||||
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
|
||||
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
|
||||
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
|
||||
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
|
||||
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
|
||||
points.snapRight = points.snapLeft.flipX()
|
||||
|
||||
/*
|
||||
* Round the bottom of the bib
|
||||
* Radius is fixed, but you could use an option for it)
|
||||
*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids2 = {
|
||||
bottomLeft: macro('round', {
|
||||
id: 'bottomLeft',
|
||||
from: points.topLeft,
|
||||
to: points.bottomRight,
|
||||
via: points.bottomLeft,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
bottomRight: macro('round', {
|
||||
id: 'bottomRight',
|
||||
from: points.bottomLeft,
|
||||
to: points.topRight,
|
||||
via: points.bottomRight,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
}
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids2) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct the path
|
||||
*/
|
||||
paths.seam = new Path()
|
||||
.move(points.edgeLeft)
|
||||
.line(points.bottomLeftStart)
|
||||
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
|
||||
.line(points.bottomRightStart)
|
||||
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
|
||||
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
|
||||
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
|
||||
.curve(points.topCp1, points.rightCp2, points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.curve(points.bottomCp1, points.leftCp2, points.left)
|
||||
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
|
||||
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
|
||||
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.close()
|
||||
.attr('class', 'fabric')
|
||||
|
||||
/*
|
||||
*
|
||||
* Annotations
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Cut list
|
||||
*/
|
||||
store.cutlist.addCut({ cut: 1, from: 'fabric' })
|
||||
|
||||
/*
|
||||
* Add the snaps
|
||||
*/
|
||||
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
|
||||
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* Add the bias tape
|
||||
*/
|
||||
if (complete)
|
||||
paths.bias = paths.seam
|
||||
.offset(-5)
|
||||
.addClass('note dashed')
|
||||
.addText('tutorial:finishWithBiasTape', 'center fill-note')
|
||||
// highlight-end
|
||||
|
||||
/*
|
||||
* Add the title
|
||||
*/
|
||||
points.title = points.bottom.shift(-90, 45)
|
||||
macro('title', {
|
||||
at: points.title,
|
||||
nr: 1,
|
||||
title: 'bib',
|
||||
align: 'center',
|
||||
scale: 0.8,
|
||||
})
|
||||
|
||||
/*
|
||||
* Add the scalebox
|
||||
*/
|
||||
points.scalebox = points.title.shift(-90, 65)
|
||||
macro('scalebox', { at: points.scalebox })
|
||||
|
||||
/*
|
||||
* Add the logo
|
||||
*/
|
||||
points.logo = new Point(0, 0)
|
||||
snippets.logo = new Snippet('logo', points.logo)
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: Conclusion
|
||||
order: 90
|
||||
---
|
||||
|
||||
Congratulations, we have created our first pattern. And while it's arguably
|
||||
rather simple, we have learned a bunch of things along the way.
|
||||
|
||||
## More reading material
|
||||
|
||||
- If you haven't done so already, read through [the design
|
||||
guide](/guides/designs/) which provides a good overview of how designs work
|
||||
under the hood
|
||||
- Bookmark [the FreeSewing API docs](/reference/api/), they are your reference
|
||||
every time you're not entirely certain how something works
|
||||
- Have a look at [the pattern design best practices](/guides/best-practices/) for best practices
|
||||
that will help you make the best possible patterns
|
||||
|
||||
## What to do next
|
||||
|
||||
Now that you have learned how to create a pattern, why don't you make one?
|
||||
|
||||
Think about what it is you would like to create, and just do it. If you get
|
||||
stuck at any moment, or need some help or advice, you can [join our chat
|
||||
room](https://discord.freesewing.org/) and we'll help you out.
|
||||
|
||||
:::note COMMENT (by joost)
|
||||
##### How did I do?
|
||||
|
||||
You could do me a real favor by letting me know what you loved or hated about
|
||||
this tutorial.
|
||||
|
||||
Were there areas that were not clear? Did I dwell too long on one topic, or
|
||||
rushed through another one too quickly? Your feedback helps improve things,
|
||||
so don't be shy and tell me what you think.
|
||||
|
||||
You can reach me at joost@freesewing.org
|
||||
|
||||
:::
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
title: Saving space (and trees) with exand
|
||||
order: 40
|
||||
---
|
||||
|
||||
There is one more way we like to save space (and trees): The `expand` setting.
|
||||
|
||||
The `expand` setting (which is true by default) indicates that user wants pattern
|
||||
parts to be fully expanded.
|
||||
|
||||
It's common to reduce the amount of space required by reducing simple shapes like
|
||||
rectangles to a description or cut things on the fold.
|
||||
|
||||
The `exand` setting gives the user control over this, at least insofar the designer
|
||||
supports it. It's not relevant to our bib, but it's good to know it's there.
|
||||
|
||||
Here is an example snippets from [Aaron's arm binding](https://github.com/freesewing/freesewing/blob/develop/designs/aaron/src/arm-binding.mjs#L24):
|
||||
|
||||
```mjs
|
||||
const w = store.get('bindingWidth')
|
||||
const l = store.get('armBindingLength')
|
||||
|
||||
if (expand) {
|
||||
store.flag.preset('expandIsOn')
|
||||
} else {
|
||||
// Expand is off, do not draw the part but flag this to the user
|
||||
store.flag.note({
|
||||
msg: `aaron:cutArmBinding`,
|
||||
replace: {
|
||||
width: units(w),
|
||||
length: units(l),
|
||||
},
|
||||
suggest: {
|
||||
text: 'flag:show',
|
||||
icon: 'expand',
|
||||
update: {
|
||||
settings: ['expand', 1],
|
||||
},
|
||||
},
|
||||
})
|
||||
// Also hint about expand
|
||||
store.flag.preset('expand')
|
||||
|
||||
return part.hide()
|
||||
}
|
||||
```
|
||||
|
||||
There's some stuff going on here that we'll learn next, but essentially if
|
||||
`expand` is falsy, the part will hide itself with `part.hide()` and so it won't
|
||||
be included on the pattern.
|
||||
|
||||
However, to avoid misunderstandings we want to inform the user about this.
|
||||
That's what all those `store.flag` lines are about. We'll learn about then next.
|
BIN
sites/dev/docs/tutorials/pattern-design/part3/flag/flag.png
Normal file
BIN
sites/dev/docs/tutorials/pattern-design/part3/flag/flag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
343
sites/dev/docs/tutorials/pattern-design/part3/flag/readme.mdx
Normal file
343
sites/dev/docs/tutorials/pattern-design/part3/flag/readme.mdx
Normal file
|
@ -0,0 +1,343 @@
|
|||
---
|
||||
title: How to communicate to the user
|
||||
order: 50
|
||||
---
|
||||
|
||||
As a designer, there are times you want to bring something to the attention of
|
||||
the user. I am not talking about generic information that can go in the
|
||||
documentation, but rather a message that is tailored specifically to this
|
||||
pattern, much like this pattern is specifically tailored to the user.
|
||||
|
||||
Doing so is possible with the various `store.flag` methods, and below is
|
||||
our updated bib making use of this. It's important to realize that things
|
||||
will look the same here. But if you load this pattern in the development
|
||||
environment (or on FreeSewing.org for that matter) the user will see this:
|
||||
|
||||

|
||||
|
||||
It's a simple example, but I hope it gets the point across.
|
||||
|
||||
Finally, keep in mind that we are now straddling the world of the core library
|
||||
and frontend integration. These messages won't do anything unless you have a
|
||||
frontend the shows them.
|
||||
|
||||
In other words **core does not care**. We are merely storing data in the store
|
||||
and relying on the frontend to show this data to the user. We merely offer
|
||||
standard methods to do so, but you can choose to ignore this info, or show it
|
||||
in a different way in your own frontend implementation.
|
||||
|
||||
<Example previewFirst tutorial caption="We flagged something for the user">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Point,
|
||||
points,
|
||||
Path,
|
||||
paths,
|
||||
utils,
|
||||
store,
|
||||
measurements,
|
||||
options,
|
||||
macro,
|
||||
Snippet,
|
||||
snippets,
|
||||
complete,
|
||||
// highlight-start
|
||||
units,
|
||||
// highlight-end
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the neck opening
|
||||
*/
|
||||
const target = (measurements.head * options.neckRatio) / 4
|
||||
let tweak = 1
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point((tweak * measurements.head) / 10, 0)
|
||||
points.bottom = new Point(0, (tweak * measurements.head) / 12)
|
||||
|
||||
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
|
||||
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
|
||||
delta = paths.neck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
/*
|
||||
* Construct the outline
|
||||
*/
|
||||
let width = measurements.head * options.widthRatio
|
||||
let length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
/*
|
||||
* Round the end of the straps
|
||||
*/
|
||||
let strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro('round', {
|
||||
id: 'tipRightTop',
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
}),
|
||||
tipRightBottom: macro('round', {
|
||||
id: 'tipRightBottom',
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
}),
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotate straps so they don't overlap
|
||||
*/
|
||||
let rotateThese = [
|
||||
'edgeTopLeftCp',
|
||||
'edgeTop',
|
||||
'tipRight',
|
||||
'tipRightTop',
|
||||
'tipRightTopStart',
|
||||
'tipRightTopCp1',
|
||||
'tipRightTopCp2',
|
||||
'tipRightTopEnd',
|
||||
'tipRightBottomStart',
|
||||
'tipRightBottomCp1',
|
||||
'tipRightBottomCp2',
|
||||
'tipRightBottomEnd',
|
||||
'tipRightBottom',
|
||||
'top',
|
||||
'topCp2',
|
||||
]
|
||||
|
||||
while (points.tipRightBottomStart.x > -1) {
|
||||
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
|
||||
}
|
||||
|
||||
/*
|
||||
* Add points to anchor snaps on
|
||||
*/
|
||||
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
|
||||
|
||||
/*
|
||||
* Mirror points to the other side
|
||||
*/
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
points.topCp1 = points.topCp2.flipX()
|
||||
points.tipLeftTopStart = points.tipRightTopStart.flipX()
|
||||
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
|
||||
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
|
||||
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
|
||||
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
|
||||
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
|
||||
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
|
||||
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
|
||||
points.snapRight = points.snapLeft.flipX()
|
||||
|
||||
/*
|
||||
* Round the bottom of the bib
|
||||
* Radius is fixed, but you could use an option for it)
|
||||
*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids2 = {
|
||||
bottomLeft: macro('round', {
|
||||
id: 'bottomLeft',
|
||||
from: points.topLeft,
|
||||
to: points.bottomRight,
|
||||
via: points.bottomLeft,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
bottomRight: macro('round', {
|
||||
id: 'bottomRight',
|
||||
from: points.bottomLeft,
|
||||
to: points.topRight,
|
||||
via: points.bottomRight,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
}
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids2) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct the path
|
||||
*/
|
||||
paths.seam = new Path()
|
||||
.move(points.edgeLeft)
|
||||
.line(points.bottomLeftStart)
|
||||
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
|
||||
.line(points.bottomRightStart)
|
||||
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
|
||||
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
|
||||
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
|
||||
.curve(points.topCp1, points.rightCp2, points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.curve(points.bottomCp1, points.leftCp2, points.left)
|
||||
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
|
||||
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
|
||||
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.close()
|
||||
.attr('class', 'fabric')
|
||||
|
||||
/*
|
||||
*
|
||||
* Annotations
|
||||
*
|
||||
*/
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* Let the user know about the bias tape and fabric requirements
|
||||
*/
|
||||
store.flag.note({
|
||||
msg: 'tutorial:biasTapeLength',
|
||||
replace: {
|
||||
l: units(paths.seam.length()),
|
||||
},
|
||||
})
|
||||
// highlight-end
|
||||
|
||||
/*
|
||||
* Cut list
|
||||
*/
|
||||
store.cutlist.addCut({ cut: 1, from: 'fabric' })
|
||||
|
||||
/*
|
||||
* Add the snaps
|
||||
*/
|
||||
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
|
||||
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
|
||||
|
||||
/*
|
||||
* Add the bias tape
|
||||
*/
|
||||
if (complete)
|
||||
paths.bias = paths.seam
|
||||
.offset(-5)
|
||||
.addClass('note dashed')
|
||||
.addText('finishWithBiasTape', 'center fill-note')
|
||||
|
||||
/*
|
||||
* Add the title
|
||||
*/
|
||||
points.title = points.bottom.shift(-90, 45)
|
||||
macro('title', {
|
||||
at: points.title,
|
||||
nr: 1,
|
||||
title: 'bib',
|
||||
align: 'center',
|
||||
scale: 0.8,
|
||||
})
|
||||
|
||||
/*
|
||||
* Add the scalebox
|
||||
*/
|
||||
points.scalebox = points.title.shift(-90, 65)
|
||||
macro('scalebox', { at: points.scalebox })
|
||||
|
||||
/*
|
||||
* Add the logo
|
||||
*/
|
||||
points.logo = new Point(0, 0)
|
||||
snippets.logo = new Snippet('logo', points.logo)
|
||||
|
||||
/*
|
||||
* Add dimensions
|
||||
*/
|
||||
macro('hd', {
|
||||
id: 'wFull',
|
||||
from: points.bottomLeftStart,
|
||||
to: points.bottomRightEnd,
|
||||
y: points.bottomLeft.y + 15,
|
||||
})
|
||||
macro('vd', {
|
||||
id: 'hBottomToOpeningBottom',
|
||||
from: points.bottomRightStart,
|
||||
to: points.bottom,
|
||||
x: points.bottomRight.x + 15,
|
||||
})
|
||||
macro('vd', {
|
||||
id: 'hBottomToOpeningCenter',
|
||||
from: points.bottomRightStart,
|
||||
to: points.right,
|
||||
x: points.bottomRight.x + 30,
|
||||
})
|
||||
macro('vd', {
|
||||
id: 'hTotal',
|
||||
from: points.bottomRightStart,
|
||||
to: points.tipLeftTopStart,
|
||||
x: points.bottomRight.x + 45,
|
||||
})
|
||||
macro('hd', {
|
||||
id: 'wOpening',
|
||||
from: points.left,
|
||||
to: points.right,
|
||||
y: points.left.y + 25,
|
||||
})
|
||||
macro('ld', {
|
||||
id: 'wStrap',
|
||||
from: points.tipLeftBottomEnd,
|
||||
to: points.tipLeftTopStart,
|
||||
d: -15,
|
||||
})
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: Supporting translation
|
||||
order: 80
|
||||
---
|
||||
|
||||
:::note [FIXME]Write this section for v3:::
|
||||
|
BIN
sites/dev/docs/tutorials/pattern-design/part3/menu/options.png
Normal file
BIN
sites/dev/docs/tutorials/pattern-design/part3/menu/options.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 75 KiB |
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
title: Facilitating frontend integration
|
||||
order: 60
|
||||
---
|
||||
|
||||
Strictly speaking, this tutorial is about learning to use FreeSewing's core
|
||||
library to do parametric design, and we made great strides in that regard.
|
||||
|
||||
But FreeSewing is a lot more than its core library, and you might be wondering
|
||||
how your pattern options magically end up in the development environment under
|
||||
**Design options**:
|
||||
|
||||

|
||||
|
||||
To make this happen, we add extra information to the options configuration.
|
||||
You can add anything you want, here is a made-up example:
|
||||
|
||||
```mjs
|
||||
options: {
|
||||
shipWith: {
|
||||
dflt: 'pickUp',
|
||||
list: ['pickUp', 'post', 'courier'],
|
||||
menu: 'shipping',
|
||||
extraNote: 'Pick-up Monday to Friday 10:00, to 19:00'
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
It's just a silly example, but there's two important take-aways here:
|
||||
|
||||
- You don't have to use options in your design. You can add options for things
|
||||
that are not about the design, but that you still want to capture the user's
|
||||
input for (like shipping preferences in this case).
|
||||
- You can add extra properties to an option. Each option type has it's required
|
||||
properties. But you can add more and use them as you see fit.
|
||||
|
||||
Which is exactly what we do at FreeSewing, so I'd like to mention the `menu` one:
|
||||
|
||||
## Setting `menu` on your option
|
||||
|
||||
If you set a `menu` property on your option, the FreeSewing frontend will use
|
||||
this to organize your various options in a menu structure.
|
||||
|
||||
### Sub menus
|
||||
You can a nested menu structure with dot-notation. So `style.pockets` will
|
||||
create a `pockets` submenu under the `style` menu and put your option there.
|
||||
|
||||
### Hiding options
|
||||
|
||||
If your `menu` property holds a falsy value, the option will be hidden from the
|
||||
menu.
|
||||
|
||||
### Hiding options conditionally
|
||||
|
||||
If the `menu` property of your option holds a function, that function will be called with the following signature:
|
||||
|
||||
```mjs
|
||||
function(
|
||||
settings, // The settings provided by the user
|
||||
mergedOptions, // The user-provided options merged with the defaults
|
||||
) {
|
||||
// return menu value here
|
||||
}
|
||||
```
|
||||
|
||||
This is typically used to hide options conditionally.
|
||||
|
||||
:::note [FIXME]Include example:::
|
|
@ -0,0 +1,344 @@
|
|||
---
|
||||
title: Supporting paperless patterns
|
||||
order: 30
|
||||
---
|
||||
|
||||
The goal of paperless patterns is to create a pattern that we don't need to
|
||||
print to be able to use it. Saving paper is always a good thing, but it's
|
||||
also a way to democratize access to patterns.
|
||||
While more and more of humanity is on the internet, access to printers and
|
||||
printing paper is often harder to come by, especially in developing countries.
|
||||
|
||||
So let's make the extra effort to make our bib design support paperless.
|
||||
|
||||
## The paperless setting
|
||||
|
||||
Users can request paperless patterns by setting [the `paperless`
|
||||
setting](/reference/settings/paperless) to a *truthy* value.
|
||||
|
||||
With paperless enabled, FreeSewing will automatically render a grid for each
|
||||
pattern part with metric or imperial markings, depending on the units requested
|
||||
by the user.
|
||||
|
||||
Such a grid is already a good starting point. In addition, we'll be using
|
||||
different macros to add *dimensions* to the pattern.
|
||||
|
||||
While the grid gets added automatically, the dimensions we have to add ourselves.
|
||||
Thankfully, there's macros that can help us with that, specifically:
|
||||
|
||||
- The `hd` macro adds a horizontal dimension
|
||||
- The `vd` macro adds a vertical dimension
|
||||
- The `ld` macro adds a linear dimension
|
||||
- The `pd` macro adds a path dimension that follows a given path
|
||||
|
||||
These macros will also adapt to the units chosen by the user (metric or imperial).
|
||||
|
||||
:::note
|
||||
Refer to [the list of macros](/reference/macros/) for more details.
|
||||
:::
|
||||
|
||||
<Example previewFirst tutorial paperless caption="Suddenly, a printer is very much optional">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
utils,
|
||||
measurements,
|
||||
options,
|
||||
macro,
|
||||
complete,
|
||||
snippets,
|
||||
Snippet,
|
||||
store,
|
||||
// highlight-start
|
||||
paperless,
|
||||
// highlight-end
|
||||
part,
|
||||
}) {
|
||||
|
||||
/*
|
||||
* Construct the neck opening
|
||||
*/
|
||||
const target = (measurements.head * options.neckRatio) / 4
|
||||
let tweak = 1
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point((tweak * measurements.head) / 10, 0)
|
||||
points.bottom = new Point(0, (tweak * measurements.head) / 12)
|
||||
|
||||
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
|
||||
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
|
||||
delta = paths.neck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
/*
|
||||
* Construct the outline
|
||||
*/
|
||||
let width = measurements.head * options.widthRatio
|
||||
let length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
/*
|
||||
* Round the end of the straps
|
||||
*/
|
||||
let strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro('round', {
|
||||
id: 'tipRightTop',
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
}),
|
||||
tipRightBottom: macro('round', {
|
||||
id: 'tipRightBottom',
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
}),
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotate straps so they don't overlap
|
||||
*/
|
||||
let rotateThese = [
|
||||
'edgeTopLeftCp',
|
||||
'edgeTop',
|
||||
'tipRight',
|
||||
'tipRightTop',
|
||||
'tipRightTopStart',
|
||||
'tipRightTopCp1',
|
||||
'tipRightTopCp2',
|
||||
'tipRightTopEnd',
|
||||
'tipRightBottomStart',
|
||||
'tipRightBottomCp1',
|
||||
'tipRightBottomCp2',
|
||||
'tipRightBottomEnd',
|
||||
'tipRightBottom',
|
||||
'top',
|
||||
'topCp2',
|
||||
]
|
||||
|
||||
while (points.tipRightBottomStart.x > -1) {
|
||||
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
|
||||
}
|
||||
|
||||
/*
|
||||
* Add points to anchor snaps on
|
||||
*/
|
||||
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
|
||||
|
||||
/*
|
||||
* Mirror points to the other side
|
||||
*/
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
points.topCp1 = points.topCp2.flipX()
|
||||
points.tipLeftTopStart = points.tipRightTopStart.flipX()
|
||||
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
|
||||
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
|
||||
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
|
||||
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
|
||||
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
|
||||
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
|
||||
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
|
||||
points.snapRight = points.snapLeft.flipX()
|
||||
|
||||
/*
|
||||
* Round the bottom of the bib
|
||||
* Radius is fixed, but you could use an option for it)
|
||||
*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids2 = {
|
||||
bottomLeft: macro('round', {
|
||||
id: 'bottomLeft',
|
||||
from: points.topLeft,
|
||||
to: points.bottomRight,
|
||||
via: points.bottomLeft,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
bottomRight: macro('round', {
|
||||
id: 'bottomRight',
|
||||
from: points.bottomLeft,
|
||||
to: points.topRight,
|
||||
via: points.bottomRight,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
}
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids2) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct the path
|
||||
*/
|
||||
paths.seam = new Path()
|
||||
.move(points.edgeLeft)
|
||||
.line(points.bottomLeftStart)
|
||||
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
|
||||
.line(points.bottomRightStart)
|
||||
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
|
||||
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
|
||||
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
|
||||
.curve(points.topCp1, points.rightCp2, points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.curve(points.bottomCp1, points.leftCp2, points.left)
|
||||
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
|
||||
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
|
||||
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.close()
|
||||
.attr('class', 'fabric')
|
||||
|
||||
/*
|
||||
*
|
||||
* Annotations
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Cut list
|
||||
*/
|
||||
store.cutlist.addCut({ cut: 1, from: 'fabric' })
|
||||
|
||||
/*
|
||||
* Add the snaps
|
||||
*/
|
||||
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
|
||||
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
|
||||
|
||||
/*
|
||||
* Add the bias tape
|
||||
*/
|
||||
if (complete)
|
||||
paths.bias = paths.seam
|
||||
.offset(-5)
|
||||
.addClass('note dashed')
|
||||
.addText('fronscratch:finishWithBiasTape', 'center fill-note')
|
||||
|
||||
/*
|
||||
* Add the title
|
||||
*/
|
||||
points.title = points.bottom.shift(-90, 45)
|
||||
macro('title', {
|
||||
at: points.title,
|
||||
nr: 1,
|
||||
title: 'bib',
|
||||
align: 'center',
|
||||
scale: 0.8,
|
||||
})
|
||||
|
||||
/*
|
||||
* Add the scalebox
|
||||
*/
|
||||
points.scalebox = points.title.shift(-90, 65)
|
||||
macro('scalebox', { at: points.scalebox })
|
||||
|
||||
/*
|
||||
* Add the logo
|
||||
*/
|
||||
points.logo = new Point(0, 0)
|
||||
snippets.logo = new Snippet('logo', points.logo)
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
* Add dimensions
|
||||
*/
|
||||
macro('hd', {
|
||||
id: 'wFull',
|
||||
from: points.bottomLeftStart,
|
||||
to: points.bottomRightEnd,
|
||||
y: points.bottomLeft.y + 15,
|
||||
})
|
||||
macro('vd', {
|
||||
id: 'hBottomToOpeningBottom',
|
||||
from: points.bottomRightStart,
|
||||
to: points.bottom,
|
||||
x: points.bottomRight.x + 15,
|
||||
})
|
||||
macro('vd', {
|
||||
id: 'hBottomToOpeningCenter',
|
||||
from: points.bottomRightStart,
|
||||
to: points.right,
|
||||
x: points.bottomRight.x + 30,
|
||||
})
|
||||
macro('vd', {
|
||||
id: 'hTotal',
|
||||
from: points.bottomRightStart,
|
||||
to: points.tipLeftTopStart,
|
||||
x: points.bottomRight.x + 45,
|
||||
})
|
||||
macro('hd', {
|
||||
id: 'wOpening',
|
||||
from: points.left,
|
||||
to: points.right,
|
||||
y: points.left.y + 25,
|
||||
})
|
||||
macro('ld', {
|
||||
id: 'wStrap',
|
||||
from: points.tipLeftBottomEnd,
|
||||
to: points.tipLeftTopStart,
|
||||
d: -15,
|
||||
})
|
||||
// highlight-end
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
18
sites/dev/docs/tutorials/pattern-design/part3/readme.mdx
Normal file
18
sites/dev/docs/tutorials/pattern-design/part3/readme.mdx
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
title: "Part 3: Beyond the basics"
|
||||
---
|
||||
|
||||
I'm excited about diving into part 3 with you, and I hope that a brief
|
||||
overview of what we'll cover will get you excited too:
|
||||
|
||||
- Adding seam allowance
|
||||
- Dealing with laser cutters
|
||||
- Providing paperless patterns
|
||||
- Limiting output through expand
|
||||
- Facilitating frontend integration
|
||||
- How to communicate to the user
|
||||
- Testing your designs
|
||||
- Supporting translation
|
||||
|
||||
If that sounds like something you'd like to learn more about, then let's dive
|
||||
right in.
|
77
sites/dev/docs/tutorials/pattern-design/part3/sa/readme.mdx
Normal file
77
sites/dev/docs/tutorials/pattern-design/part3/sa/readme.mdx
Normal file
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
title: Adding seam allowance
|
||||
order: 10
|
||||
---
|
||||
|
||||
When adding seam allowance to a pattern, there are 3 things that come into play:
|
||||
|
||||
- Does the user want seam allowance?
|
||||
- How much seam allowance does the user want?
|
||||
- How can we add seam allowance?
|
||||
|
||||
Let's whip up a quick example for a moment to clarify how we will handle this:
|
||||
|
||||
<Example settings="sa: 10"caption="Some straight lines and some curves">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
// highlight-start
|
||||
sa,
|
||||
// highlight-end
|
||||
part,
|
||||
}) {
|
||||
|
||||
points.topLeft = new Point(0,0)
|
||||
points.bottomRight = new Point(100,40)
|
||||
points.topRight = new Point(points.bottomRight.x, points.topLeft.y)
|
||||
points.bottomLeft = new Point(points.topLeft.x, points.bottomRight.y)
|
||||
points.cp1 = new Point(50, 20)
|
||||
points.cp2 = new Point(70, 60)
|
||||
|
||||
paths.shape = new Path()
|
||||
.move(points.topLeft)
|
||||
.line(points.bottomLeft)
|
||||
.curve(points.cp1, points.cp2, points.bottomRight)
|
||||
.line(points.topRight)
|
||||
.line(points.topLeft)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
|
||||
// highlight-start
|
||||
if (sa) paths.sa = paths.shape.offset(sa).addClass('fabric sa')
|
||||
// highlight-end
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
||||
As you can see from the source, we can destructure an `sa` variable (short for
|
||||
seam allowance) that will hold either:
|
||||
|
||||
- `false` if the user does not want seam allowance
|
||||
- A value in `mm` indicating how much seam allowance the user wants
|
||||
|
||||
To add seam allowance to our path, we just `offset` it by `sa` and add
|
||||
some classes to it to style it. But, crucially, only if the user wants
|
||||
seam allowance:
|
||||
|
||||
```mjs
|
||||
if (sa) paths.sa = paths.shape
|
||||
.offset(sa)
|
||||
.addClass('fabric sa')
|
||||
```
|
||||
|
||||
To refer back to our three question: Whether the user wants seam allowance, and
|
||||
if so how much seam allowance is answered by the `sa` value passed to our draft
|
||||
method.
|
||||
|
||||
And we add it with the `path.offset()` method which will offset the path.
|
||||
|
||||
Our bib pattern does not require seam allowance as it will be finished with
|
||||
bias tape. But we can use this same technique to indicate that.
|
||||
|
||||
Before we do so though, we should talk about `complete`.
|
147
sites/dev/docs/tutorials/pattern-design/part3/tests/readme.mdx
Normal file
147
sites/dev/docs/tutorials/pattern-design/part3/tests/readme.mdx
Normal file
|
@ -0,0 +1,147 @@
|
|||
---
|
||||
title: Testing your designs
|
||||
order: 70
|
||||
---
|
||||
|
||||
:::note [FIXME] Update this for v3 :::
|
||||
|
||||
With the basic outline of our pattern ready, now would be a good time
|
||||
to test it to see how well it adapts to different measurements,
|
||||
and the range of options we provided.
|
||||
|
||||
:::note [FIXME]
|
||||
This page needs to be updated with screenshots from the v3 development
|
||||
environment
|
||||
:::
|
||||
|
||||
:::tip
|
||||
|
||||
###### No more grading
|
||||
|
||||
FreeSewing patterns are _bespoke_, which means that we don't need to
|
||||
grade our pattern to provide a range of sizes. We should test our pattern
|
||||
for different measurements and options to see how well it adapts.
|
||||
|
||||
:::
|
||||
|
||||
If testing our pattern sounds like a lot of work, we're in luck. FreeSewing can do it
|
||||
for us. Click the **Test Design** link in the sidebar under the **View** title.
|
||||
|
||||
We have a number of ways to test our pattern:
|
||||
|
||||
- Test design options
|
||||
- Test measurements
|
||||
- Test measurements sets
|
||||
|
||||
The [API docs on sampling](/reference/api/pattern/sample) have all the details
|
||||
on how this works, but for now we'll just look at the end result of each of
|
||||
these.
|
||||
|
||||
## Testing pattern options
|
||||
|
||||
We used percentage options, which can vary between their minimum and maximum
|
||||
value. For these tests, FreeSewing will divide that range into 10 steps and
|
||||
draft our pattern for each step.
|
||||
|
||||
Click on any of the options we've added to our pattern, and our bib will be
|
||||
drawn with that option sampled.
|
||||
|
||||
### lengthRatio
|
||||
|
||||
The `lengthRatio` option controls the length of our bib. Testing it confirms
|
||||
that it only influences the length:
|
||||
|
||||

|
||||
|
||||
:::note [FIXME]Update screenshot for v3:::
|
||||
|
||||
### neckRatio
|
||||
|
||||
The `neckRatio` option will determine the size of the neck opening. For the
|
||||
same `head` measurement, varying this option should result in bibs with
|
||||
increasingly larger neck opening.
|
||||
|
||||
Testing it confirms this. We can also see that as the neck opening gets
|
||||
smaller, we will rotate the straps further out of the way to avoid overlap:
|
||||
|
||||

|
||||
|
||||
:::note [FIXME]Update screenshot for v3:::
|
||||
|
||||
### widthRatio
|
||||
|
||||
The `widthRatio` option will determine the width of our bib. For the same
|
||||
`head` measurement, varying this option should result in increasingly wider
|
||||
bibs.
|
||||
|
||||
If we test it, we can see that it works as intended. But there's one thing that
|
||||
perhaps requires our attention. Making the bib wider shortens the length from
|
||||
the bottom of the neck opening to the bottom of the bib. Thereby making the
|
||||
bib shorter when it's worn.
|
||||
|
||||

|
||||
|
||||
:::note [FIXME]Update screenshot for v3:::
|
||||
|
||||
:::note
|
||||
|
||||
Even if the _total length_ of the bib stays the same, the _useable length_
|
||||
shortens when the bib is made wider. Users will not expect this, so it's
|
||||
something that we should fix in our pattern.
|
||||
|
||||
Adjusting the pattern to make the `widthRatio` not influence the _useable
|
||||
length_ of the bib is not covered in this tutorial. It is left _as an exercise
|
||||
to the reader_.
|
||||
|
||||
:::
|
||||
|
||||
## Testing measurements
|
||||
|
||||
Testing a measurement will vary that measurement 10% up or down while leaving
|
||||
everything else the same. This gives us the option to determine how any given
|
||||
measurement is influencing the pattern.
|
||||
|
||||
For our bib, we only use one measurement, so it influences the entire pattern.
|
||||
|
||||
:::note [FIXME]Add screenshot:::
|
||||
|
||||
## Testing measurments sets
|
||||
|
||||
Whereas testing a measurement will only vary one individual measurement,
|
||||
testing measurements sets will draft our pattern for different sets of
|
||||
measurements.
|
||||
|
||||
On the surface, the result below is the same as our measurement test. But that
|
||||
is because our bib only uses one measurement. So testing that one measurement
|
||||
ends up being the same as testing a complete set of measurements.
|
||||
|
||||
But most patterns use multiple measurements, and we'll find this test gives
|
||||
us insight into how our pattern will adapt to differently sized bodies.
|
||||
|
||||
:::note [FIXME]Add screenshot:::
|
||||
|
||||
## The antperson test
|
||||
|
||||
A special case of model testing is the so-called _antperson test_. It drafts
|
||||
our pattern with a set of _typical_ measurements , and then drafts it again
|
||||
with measurements that are 1/10th of those _typical_ measurements.
|
||||
|
||||
It is named after [the cartoon
|
||||
character](https://en.wikipedia.org/wiki/Ant-Man_\(film\)) who can shrink, yet
|
||||
somehow his suit still fits.
|
||||
|
||||
The purpose of the antperson test is to bring out areas in our pattern where
|
||||
we made assumptions that will not properly scale. Many drafting books will
|
||||
tell us to _add 3 cm there_ or _measure 2 inch to the right_. Those
|
||||
instructions don't scale, and we should avoid them.
|
||||
|
||||
The best patterns will pass the antperson test with 2 patterns exactly the
|
||||
same, where one will simply be 1/10th the scale of the other.
|
||||
|
||||
:::note [FIXME]Add screenshot:::
|
||||
|
||||
When we're happy with how our pattern passes these tests, it's time to
|
||||
complete our design.
|
Binary file not shown.
After Width: | Height: | Size: 208 KiB |
Binary file not shown.
After Width: | Height: | Size: 523 KiB |
Binary file not shown.
After Width: | Height: | Size: 649 KiB |
Loading…
Add table
Add a link
Reference in a new issue