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

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

View file

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

View file

@ -1,397 +0,0 @@
---
title: Completing our pattern
order: 260
---
When we say we're going to *complete* our pattern, we mean we're going to add
things like a title and a scalebox and so on.
These things or not always wanted on a pattern. For example, if you're cutting
out patterns with a laser cutter, or tracing them with a projects, you
typically only want to have the pattern outline.
## The complete setting
Users can indicate their desire to have either a bare-bones pattern, or rather
when that's completed with all bells and whistles with [the
`complete` setting](/reference/settings/complete).
To make sure we respect the user's choice, we must wrap all of these
embellishments in a code block that only executes when the `complete` setting
is *truthy*.
To access the setting, we can destructure it:
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
measurements,
options,
macro,
// highlight-start
complete,
// highlight-end
part,
}) {
// Our earlier work is not shown for brevety
// highlight-start
if (complete) {
// Complete pattern here
}
// highlight-end
return part
}
```
## Adding snippets
Snippets are little re-useable things to embellish our pattern with.
Things like buttons or buttonholes, a logo, or snaps.
To use them, much like points and paths, we need to destructure both
the `Snippet` constructor as well as the `snippets` object to hold
our snippets:
```design/src/bib.mjs
function draftBib({
Path,
Point,
//highlight-start
Snippet,
//highlight-end
paths,
points,
// highlight-start
snippets,
// highlight-end
measurements,
options,
macro,
complete,
part,
}) {
// Our earlier work is not shown for brevety
if (complete) {
// Complete pattern here
}
return part
}
```
We'll be using them below to add a `snap-stud`, `snap-socket`, and a `logo`
snippet.
<Note>
You can find all possible snippets in [our documentation](/reference/api/snippet/).
</Note>
## The completed design
Let's put this and few other things together to complete our design:
<Example tutorial caption="Almost done. But there's one more thing the user can ask for: a **paperless** pattern">
```design/src/bib.mjs
function draftBib({
Path,
Point,
Snippet,
paths,
points,
snippets,
measurements,
options,
macro,
complete,
part,
}) {
// Construct the quarter neck opening
let tweak = 1
let target = (measurements.head * options.neckRatio) /4
let delta
do {
points.right = new Point(tweak * measurements.head / 10, 0)
points.bottom = new Point(0, tweak * measurements.head / 12)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right)/2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2)
paths.quarterNeck = new Path()
.move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
.hide() // Add this line
delta = paths.quarterNeck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
// Construct the complete neck opening
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
// Drawing the bib outline
const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio
points.topLeft = new Point(
width / -2,
points.top.y - (width / 2 - points.right.x)
)
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
// Shape the straps
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
points.topLeft,
0.5
)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
// Round the straps
const strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
macro("round", {
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
prefix: "tipRightTop",
})
macro("round", {
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
prefix: "tipRightBottom",
})
const rotateThese = [
"edgeTopLeftCp",
"edgeTop",
"tipRight",
"tipRightTop",
"tipRightTopStart",
"tipRightTopCp1",
"tipRightTopCp2",
"tipRightTopEnd",
"tipRightBottomStart",
"tipRightBottomCp1",
"tipRightBottomCp2",
"tipRightBottomEnd",
"tipRightBottom",
"top",
"topCp2"
]
while (points.tipRightBottomStart.x > -1) {
for (const p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
// Snap anchor
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
// Add points for second strap
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX()
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
points.snapRight = points.snapLeft.flipX()
// Round the bottom corners
macro("round", {
from: points.topLeft,
to: points.bottomRight,
via: points.bottomLeft,
radius: points.bottomRight.x / 4,
prefix: "bottomLeft"
})
macro("round", {
from: points.bottomLeft,
to: points.topRight,
via: points.bottomRight,
radius: points.bottomRight.x / 4,
prefix: "bottomRight"
})
// Create one path for the bib outline
paths.seam = new Path()
.move(points.edgeLeft)
.line(points.bottomLeftStart)
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
.line(points.bottomRightStart)
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
.line(points.edgeRight)
.curve(
points.edgeRightCp,
points.edgeTopRightCp,
points.tipLeftTopStart
)
.curve(
points.tipLeftTopCp1,
points.tipLeftTopCp2,
points.tipLeftTopEnd
)
.curve(
points.tipLeftBottomCp1,
points.tipLeftBottomCp2,
points.tipLeftBottomEnd
)
.curve(
points.topCp1,
points.rightCp2,
points.right
)
.curve(
points.rightCp1,
points.bottomCp2,
points.bottom
)
.curve(
points.bottomCp1,
points.leftCp2,
points.left
)
.curve(
points.leftCp1,
points.topCp2,
points.tipRightBottomEnd
)
.curve(
points.tipRightBottomCp2,
points.tipRightBottomCp1,
points.tipRightBottomStart
)
.curve(
points.tipRightTopCp2,
points.tipRightTopCp1,
points.tipRightTopStart
)
.curve(
points.edgeTopLeftCp,
points.edgeLeftCp,
points.edgeLeft
)
.close()
.addClass("fabric")
// highlight-start
if (complete) {
/*
* Let's add the points where
* the closure's snaps should go.
*/
// Add snaps
points.snapLeft = points.top
.shiftFractionTowards(points.edgeTop, 0.5)
points.snapRight = points.snapLeft
.flipX()
/*
* Now, let's use our newfound snippet powers
* to add the snaps to these points.
* First the left snap (the stud part)
*/
snippets.snapStud = new Snippet(
'snap-stud',
points.snapLeft
)
/*
* The right snap (the socket part)
* should go on the back of the fabric.
* To try to make that more obvious to the user
* let's set its `opacity` attribute to 0.5
* this way it will be semi-transparent.
*/
snippets.snapSocket = new Snippet(
'snap-socket',
points.snapRight
).attr('opacity', 0.5)
/*
* Another snippet we should add is our logo.
* A logo is not required, but we love skully.
*/
// Add a logo
points.logo = new Point(0, 0)
snippets.logo = new Snippet(
"logo",
points.logo
)
/*
* We already know how to use macros
* This one adds a title to our part
*/
// Add a title
points.title = points.bottom
.shift(-90, 45)
macro("title", {
at: points.title,
nr: 1,
title: "bib",
scale: 0.7
})
/*
* This macro adds a scalebox to our part
*/
// Add a scalbox
points.scalebox = points.title
.shift(-90, 55)
macro(
"scalebox",
{ at: points.scalebox }
)
/*
* Our bib should be finished with bias tape.
* To add it, we're using Path.offset()
* You will learn to LOVE this methid
* when designing pattern with seam allowance
* as it draws a path parallel to another one
*/
paths.bias = paths.seam
.offset(-5)
.addClass("various dashed")
.addText(
"finishWithBiasTape",
"center fill-various"
)
}
// highlight-end
return part
}
```
</Example>

View file

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

View file

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

View file

@ -0,0 +1,53 @@
---
title: Saving space (and trees) with exand
order: 40
---
There is one more way we like to save space (and trees): The `expand` setting.
The `expand` setting (which is true by default) indicates that user wants pattern
parts to be fully expanded.
It's common to reduce the amount of space required by reducing simple shapes like
rectangles to a description or cut things on the fold.
The `exand` setting gives the user control over this, at least insofar the designer
supports it. It's not relevant to our bib, but it's good to know it's there.
Here is an example snippets from [Aaron's arm binding](https://github.com/freesewing/freesewing/blob/develop/designs/aaron/src/arm-binding.mjs#L24):
```mjs
const w = store.get('bindingWidth')
const l = store.get('armBindingLength')
if (expand) {
store.flag.preset('expandIsOn')
} else {
// Expand is off, do not draw the part but flag this to the user
store.flag.note({
msg: `aaron:cutArmBinding`,
replace: {
width: units(w),
length: units(l),
},
suggest: {
text: 'flag:show',
icon: 'expand',
update: {
settings: ['expand', 1],
},
},
})
// Also hint about expand
store.flag.preset('expand')
return part.hide()
}
```
There's some stuff going on here that we'll learn next, but essentially if
`expand` is falsy, the part will hide itself with `part.hide()` and so it won't
be included on the pattern.
However, to avoid misunderstandings we want to inform the user about this.
That's what all those `store.flag` lines are about. We'll learn about then next.

View file

@ -0,0 +1,340 @@
---
title: How to communicate to the user
order: 50
---
As a designer, there are times you want to bring something to the attention of
the user. I am not talking about generic information that can go in the
documentation, but rather a message that is tailored specifically to this
pattern, much like this pattern is specifically tailored to the user.
Doing so is possible with the various `store.flag` methods, and below is
our updated bib making use of this. It's important to realize that things
will look the same here. But if you load this pattern in the development
environment (or on FreeSewing.org for that matter) the user will see this:
![A message for the user](./flag.png)
It's a simple example, but I hope it gets the point across.
Finally, keep in mind that we are now straddling the world of the core library
and frontend integration. These messages won't do anything unless you have a
frontend the shows them.
<Example previewFirst tutorial caption="We flagged something for the user">
```design/src/bib.mjs
function draftBib({
Point,
points,
Path,
paths,
utils,
store,
measurements,
options,
macro,
Snippet,
snippets,
complete,
// highlight-start
units,
// highlight-end
part,
}) {
/*
* Construct the neck opening
*/
const target = (measurements.head * options.neckRatio) / 4
let tweak = 1
let delta
do {
points.right = new Point((tweak * measurements.head) / 10, 0)
points.bottom = new Point(0, (tweak * measurements.head) / 12)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
paths.neck = new Path()
.move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
delta = paths.neck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
/*
* Construct the outline
*/
let width = measurements.head * options.widthRatio
let length = measurements.head * options.lengthRatio
points.topLeft = new Point(width / -2, points.top.y - (width / 2 - points.right.x))
points.topRight = points.topLeft.shift(0, width)
points.bottomLeft = points.topLeft.shift(-90, length)
points.bottomRight = points.topRight.shift(-90, length)
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
points.edgeRightCp = points.edgeLeftCp.flipX()
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
/*
* Round the end of the straps
*/
let strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
/*
* Macros will return the auto-generated IDs
*/
const ids1 = {
tipRightTop: macro('round', {
id: 'tipRightTop',
from: points.edgeTop,
to: points.tipRight,
via: points.tipRightTop,
}),
tipRightBottom: macro('round', {
id: 'tipRightBottom',
from: points.tipRight,
to: points.top,
via: points.tipRightBottom,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids1) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
}
}
/*
* Rotate straps so they don't overlap
*/
let rotateThese = [
'edgeTopLeftCp',
'edgeTop',
'tipRight',
'tipRightTop',
'tipRightTopStart',
'tipRightTopCp1',
'tipRightTopCp2',
'tipRightTopEnd',
'tipRightBottomStart',
'tipRightBottomCp1',
'tipRightBottomCp2',
'tipRightBottomEnd',
'tipRightBottom',
'top',
'topCp2',
]
while (points.tipRightBottomStart.x > -1) {
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
/*
* Add points to anchor snaps on
*/
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
/*
* Mirror points to the other side
*/
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX()
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
points.snapRight = points.snapLeft.flipX()
/*
* Round the bottom of the bib
* Radius is fixed, but you could use an option for it)
*
* Macros will return the auto-generated IDs
*/
const ids2 = {
bottomLeft: macro('round', {
id: 'bottomLeft',
from: points.topLeft,
to: points.bottomRight,
via: points.bottomLeft,
radius: points.bottomRight.x / 4,
}),
bottomRight: macro('round', {
id: 'bottomRight',
from: points.bottomLeft,
to: points.topRight,
via: points.bottomRight,
radius: points.bottomRight.x / 4,
}),
}
/*
* Create points from them with easy names
*/
for (const side in ids2) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
}
}
/*
* Construct the path
*/
paths.seam = new Path()
.move(points.edgeLeft)
.line(points.bottomLeftStart)
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
.line(points.bottomRightStart)
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
.line(points.edgeRight)
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
.curve(points.topCp1, points.rightCp2, points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
.curve(points.bottomCp1, points.leftCp2, points.left)
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
.close()
.attr('class', 'fabric')
/*
*
* Annotations
*
*/
// highlight-start
/*
* Let the user know about the bias tape and fabric requirements
*/
store.flag.note({
msg: 'tutorial:biasTapeLength',
replace: {
l: units(paths.seam.length()),
},
})
// highlight-end
/*
* Cut list
*/
store.cutlist.addCut({ cut: 1, from: 'fabric' })
/*
* Add the snaps
*/
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
/*
* Add the bias tape
*/
if (complete)
paths.bias = paths.seam
.offset(-5)
.addClass('note dashed')
.addText('finishWithBiasTape', 'center fill-note')
/*
* Add the title
*/
points.title = points.bottom.shift(-90, 45)
macro('title', {
at: points.title,
nr: 1,
title: 'bib',
align: 'center',
scale: 0.8,
})
/*
* Add the scalebox
*/
points.scalebox = points.title.shift(-90, 65)
macro('scalebox', { at: points.scalebox })
/*
* Add the logo
*/
points.logo = new Point(0, 0)
snippets.logo = new Snippet('logo', points.logo)
/*
* Add dimensions
*/
macro('hd', {
id: 'wFull',
from: points.bottomLeftStart,
to: points.bottomRightEnd,
y: points.bottomLeft.y + 15,
})
macro('vd', {
id: 'hBottomToOpeningBottom',
from: points.bottomRightStart,
to: points.bottom,
x: points.bottomRight.x + 15,
})
macro('vd', {
id: 'hBottomToOpeningCenter',
from: points.bottomRightStart,
to: points.right,
x: points.bottomRight.x + 30,
})
macro('vd', {
id: 'hTotal',
from: points.bottomRightStart,
to: points.tipLeftTopStart,
x: points.bottomRight.x + 45,
})
macro('hd', {
id: 'wOpening',
from: points.left,
to: points.right,
y: points.left.y + 25,
})
macro('ld', {
id: 'wStrap',
from: points.tipLeftBottomEnd,
to: points.tipLeftTopStart,
d: -15,
})
return part
}
```
</Example>
Now the first thing you should know is: **core does not care**. We are

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View file

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

View file

@ -0,0 +1,68 @@
---
title: Facilitating frontend integration
order: 60
---
Strictly speaking, this tutorial is about learning to use FreeSewing's core
library to do parametric design, and we made great strides in that regard.
But FreeSewing is a lot more than its core library, and you might be wondering
how your pattern options magically end up in the development environmnt under
**Design options**:
![Design options menu](./options.png)
To make this happen, we add extra information to the options configuration.
You can add anything you want, here is a made-up example:
```mjs
options: {
shipWith: {
dflt: 'pickUp',
list: ['pickUp', 'post', 'courier'],
menu: 'shipping',
extraNote: 'Pick-up Monday to Friday 10:00, to 19:00'
},
}
```
It's just a silly example, but there's two important take-aways here:
- You don't have to use options in your design. You can add options for things
that are not about the desing, but that you still want to capture the user's
input for (like shipping preferences in this case).
- You can add extra properties to an option. Each option type has it's required
properties. But you can add more and use them as you see fit.
Which is exactly what we do at FreeSewing, so I'd like to mention the `menu` one:
## Setting `menu` on your option
If you set a `menu` property on your option, the FreeSewing frontend will use
this to organize your various options in a menu structure.
### Sub menus
You can a nested menu structure with dot-notation. So `style.pockets` will
create a `pockets` submenu under the `style` menu and put your option there.
### Hiding options
If your `menu` property holds a falsy value, the option will be hidden from the
menu.
### Hiding options conditionally
If the `menu` property of your option holds a function, that function will be called with the following signature:
```mjs
function(
settings, // The settings provided by the user
mergedOptions, // The user-provided options merged with the defaults
) {
// return menu value here
}
```
This is typically used to hide options conditionally.
<Fixme compact>Include example</Fixme>

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View file

@ -1,28 +1,21 @@
---
title: Supporting paperless patterns
order: 270
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 before wrapping up, let's make the extra effort to make our bib design
support paperless pattern.
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.
Like other settings, we can destructure it to get access to it.
Then, much like with the `complete` setting, we will wrap our
paperless-specific code in a condition so it only runs when the user wants
that.
With paperless enabled, FreeSewing will automatically render a grid for each
pattern part with metric or imperial markings, depending on the units requested
by the user.
@ -38,129 +31,155 @@ Thankfully, there's macros that can help us with that, specifically:
- The `ld` macro adds a linear dimension
- The `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.
</Note>
<Example tutorial paperless caption="Making our pattern paperless is the icing on the cake. Time to wrap up, go over what we've learned, and give some pointers on where to go from here">
<Example previewFirst tutorial paperless caption="Suddenly, a printer is very much optional">
```design/src/bib.mjs
function draftBib({
Path,
Point,
paths,
points,
utils,
measurements,
options,
macro,
complete,
snippets,
Snippet,
store,
// highlight-start
paperless,
// highlight-end
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)
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()
paths.neck = new Path()
.move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
.hide() // Add this line
delta = paths.quarterNeck.length() - target
delta = paths.neck.length() - target
if (delta > 0) tweak = tweak * 0.99
else tweak = tweak * 1.02
} while (Math.abs(delta) > 1)
// Construct the complete neck opening
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
points.left = points.right.flipX()
points.leftCp1 = points.rightCp2.flipX()
points.leftCp2 = points.rightCp1.flipX()
points.top = points.bottom.flipY()
points.topCp1 = points.bottomCp2.flipY()
points.topCp2 = points.bottomCp1.flipY()
// Drawing the bib outline
const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio
/*
* 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.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.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(points.topLeft, 0.5)
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
// Round the straps
const strap = points.edgeTop.dy(points.top)
/*
* Round the end of the straps
*/
let strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
points.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)
/*
* 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,
}),
}
// Snap anchor
/*
* Create points from them with easy names
*/
for (const side in ids1) {
for (const id of ['start', 'cp1', 'cp2', 'end']) {
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
}
}
/*
* Rotate straps so they don't overlap
*/
let rotateThese = [
'edgeTopLeftCp',
'edgeTop',
'tipRight',
'tipRightTop',
'tipRightTopStart',
'tipRightTopCp1',
'tipRightTopCp2',
'tipRightTopEnd',
'tipRightBottomStart',
'tipRightBottomCp1',
'tipRightBottomCp2',
'tipRightBottomEnd',
'tipRightBottom',
'top',
'topCp2',
]
while (points.tipRightBottomStart.x > -1) {
for (let p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
}
/*
* Add points to anchor snaps on
*/
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
// Add points for second strap
/*
* Mirror points to the other side
*/
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX()
@ -173,23 +192,40 @@ function draftBib({
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"
})
/*
* 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()
}
}
// Create one path for the bib outline
/*
* Construct the path
*/
paths.seam = new Path()
.move(points.edgeLeft)
.line(points.bottomLeftStart)
@ -197,150 +233,111 @@ function draftBib({
.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
)
.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")
.attr('class', 'fabric')
if (complete) {
// Add 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)
/*
*
* Annotations
*
*/
// Add a logo
points.logo = new Point(0, 0)
snippets.logo = new Snippet("logo", points.logo)
/*
* Cut list
*/
store.cutlist.addCut({ cut: 1, from: 'fabric' })
// Add a title
points.title = points.bottom.shift(-90, 45)
macro("title", {
at: points.title,
nr: 1,
title: "bib",
scale: 0.7
})
// Add a scalbox
points.scalebox = points.title.shift(-90, 55)
macro("scalebox", { at: points.scalebox })
/*
* 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("various dashed")
.addText("finishWithBiasTape", "center fill-various")
.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
/*
* Note that we are putting our paperless block
* inside our `complete` block since we should
* only add this when the users wants a compete pattern
*/
if (paperless) {
// Add dimensions
/*
* The `hd` macro adds a Horizontal Dimension (hd)
* It takes a from and to point, and a y value
* at which to place the dimension
*/
macro("hd", {
from: points.bottomLeftStart,
to: points.bottomRightEnd,
y: points.bottomLeft.y + 15
})
/*
* The `vd` macro adds a Vertical Dimension (vd)
* It takes a from and to point, and an x value
* at which to place the dimension
*/
macro("vd", {
from: points.bottomRightStart,
to: points.bottom,
x: points.bottomRight.x + 15
})
/*
* Let's do a few more of these
*/
macro("vd", {
from: points.bottomRightStart,
to: points.right,
x: points.bottomRight.x + 30
})
macro("vd", {
from: points.bottomRightStart,
to: points.tipLeftTopStart,
x: points.bottomRight.x + 45
})
macro("hd", {
from: points.left,
to: points.right,
y: points.left.y + 25
})
/*
* The `ld` macro adds a Linear Dimension (ld)
* It takes a from and to point, and a d value
* that sets its offset from the line from -> to
*/
macro("ld", {
from: points.tipLeftBottomEnd,
to: points.tipLeftTopStart,
d: 15
})
}
/*
* 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
}
```

View file

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

View file

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