chore: Updated tutorial for v3
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Setting up the development environment
|
||||
order: 100
|
||||
order: 20
|
||||
---
|
||||
|
||||
FreeSewing provides a development environment that visualizes your design for you.
|
||||
|
@ -12,13 +12,15 @@ npx @freesewing/new-design@next
|
|||
```
|
||||
<Fixme compact>Remove `@next` suffix once v3 is in production</Fixme>
|
||||
|
||||
It will ask some questions.
|
||||
All the defaults will do, but here are the details:
|
||||
It will ask if it is ok to install the development environment in a new folder
|
||||
named `freesewing`. You can accept the default, or pick a different folder name
|
||||
if you prefer.
|
||||
|
||||
- *What template would you like to use?* — Pick the default: **Tutorial**
|
||||
- *What package manager should we use?* — Pick the default: **npm**, unless you are certain you have **yarn** installed
|
||||
It will also ask what package manager you would like to use.
|
||||
Here too the default (`npm`) is fine., unless you are certain you have **yarn** installed.
|
||||
|
||||
After answering these questions, files will be copied, dependencies installed, and requirements downloaded.
|
||||
After answering these questions, files will be downloaded, dependencies installed,
|
||||
and it will also initialize a git repository for you (if you have git on your system).
|
||||
|
||||
<Note>
|
||||
|
||||
|
@ -27,17 +29,17 @@ of dependencies that need to be downloaded.
|
|||
|
||||
</Note>
|
||||
|
||||
When it's ready, you can enter the `tutorial` directory that was just created and run `npm run dev`:
|
||||
When it's ready, you can enter the `freesewing` directory that was just created and run `npm run dev`:
|
||||
|
||||
```sh
|
||||
cd tutorial
|
||||
cd freesewing
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Or if you want to use yarn as package manager:
|
||||
|
||||
```sh
|
||||
cd tutorial
|
||||
cd freesewing
|
||||
yarn dev
|
||||
```
|
||||
|
||||
|
@ -47,10 +49,11 @@ If all goes well, we'll should see this landing page:
|
|||
|
||||

|
||||
|
||||
## Notes
|
||||
<Tip>
|
||||
|
||||
### Need help?
|
||||
##### Need help?
|
||||
|
||||
If you run into any issues, head over to [FreeSewing.org/support](https://next.freesewing.org/support)
|
||||
which lists the various ways in which you can get help.
|
||||
|
||||
</Tip>
|
||||
|
|
Before Width: | Height: | Size: 356 KiB After Width: | Height: | Size: 269 KiB |
47
markdown/dev/tutorials/pattern-design/part1/sde/en.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: The FreeSewing development environment
|
||||
order: 30
|
||||
---
|
||||
|
||||
If you have been to FreeSewing.org the FreeSewing development environment will look familiar.
|
||||
That's because under the hood, it re-uses the same building blocks.
|
||||
|
||||
At the top of the page is the header with a row of icons that lay out what is available to you.
|
||||
|
||||

|
||||
|
||||
From left to right you can see:
|
||||
|
||||
- **Home** will take you to the home page / welcome page
|
||||
- **Design** will offer you a list of templates to start a design from (more on this below)
|
||||
- **Documentation** will show a page with links to our documentation
|
||||
- **Code** will show a page with links to our source code
|
||||
- **Support** will show a page with the various ways you can get help
|
||||
- **Theme** allows you to change the theme (in other works the color scheme)
|
||||
- **Language** allows you to change the language
|
||||
- **Sign In** allows you to sign in to your FreeSewing account so you can use
|
||||
your (and our) measurements sets while designing
|
||||
|
||||
## Design templates
|
||||
|
||||
If you click he **Design** icon it will show this menu:
|
||||
|
||||

|
||||
|
||||
It allows you to choose a desing template to start from. The following templates are included:
|
||||
|
||||
- **From scratch**: Start with an (almost) empty design
|
||||
- **Tutorial**: Start with the end result of this very tutorial
|
||||
- **From Brian**: Start with a design that extends [Brian]('https://freesewing.org/designs/brian)
|
||||
- **From Bent**: Start with a design that extends [Bent]('https://freesewing.org/designs/bent)
|
||||
- **From Titan**: Start with a design that extends [Titan]('https://freesewing.org/designs/titan)
|
||||
- **From Bella**: Start with a design that extends [Bella]('https://freesewing.org/designs/bella)
|
||||
- **From Breanna**: Start with a design that extends [Breanna]('https://freesewing.org/designs/breanna)
|
||||
|
||||
For the following along this tutorial, you have two options:
|
||||
|
||||
- Pick **Tutorial** if you prefer to read along, make small changes, and see how they affect the design.
|
||||
- Pick **From scratch** if you prefer to actively participate be recreating the desing in this tutorial.
|
||||
|
||||
I recommend the latter. You will learn (and remember) a lot more if you are activele engaging.
|
||||
|
BIN
markdown/dev/tutorials/pattern-design/part1/sde/header.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
markdown/dev/tutorials/pattern-design/part1/sde/templates.png
Normal file
After Width: | Height: | Size: 217 KiB |
|
@ -1,45 +1,32 @@
|
|||
---
|
||||
title: Files and folder structure
|
||||
order: 110
|
||||
title: Folder structure
|
||||
order: 40
|
||||
---
|
||||
|
||||
Inside our `tutorial` folder, the `design/src` folder holds the source code for
|
||||
the new pattern we will create.
|
||||
Inside the `freesewing` folder -- which might have a different name if that is
|
||||
the choice you made -- you will find a bunch of files and folders.
|
||||
|
||||
If you want to support internationalization in your design, your translations
|
||||
go in the `design/i18n` folder.
|
||||
The one that matter is the `design` folder. In it, you will find the followin
|
||||
subfolders:
|
||||
|
||||
We can safely ignore all other files and folders, as they are part of the
|
||||
FreeSewing development environment.
|
||||
So feel free to skip ahead to [Our first part](/tutorials/pattern-design/our-first-part).
|
||||
- `from-bella`
|
||||
- `from-bent`
|
||||
- `from-breanna`
|
||||
- `from-brian`
|
||||
- `from-scratch`
|
||||
- `from-titan`
|
||||
- `tutorial`
|
||||
|
||||
## Notes
|
||||
Remember when you click the **Design** icon in the header it would bring up
|
||||
this menu:
|
||||
|
||||
If you'd like to learn about those other files and folders, here's what they do:
|
||||

|
||||
|
||||
### folders
|
||||
As you might have guessed by now, each of these options is contained in its
|
||||
own subfolder under `designs`.
|
||||
|
||||
- `design`: Holds the source code for our design
|
||||
- `lab`: Holds [React][react] hooks and components specific to the development environment
|
||||
- `node_modules`: Holds installed dependencies
|
||||
- `pages`: Holds [NextJS][next] client-side routes, aka pages
|
||||
- `public`: Holds pre-generated translation files
|
||||
- `shared`: Holds files from FreeSewing's shared codebase for frontend development
|
||||
You can edit the files under `designs/[template]/src/` and the changes you make
|
||||
will be reflected in the development environment.
|
||||
|
||||
### files
|
||||
|
||||
- `next.config.mjs`: The [NextJS][next] configuration file
|
||||
- `next-i18next.config.js`: The configuration file for [next-i18next][i18n] which handles translation within NextJS
|
||||
- `package.json`: Every Node.js project has a [package.json][pkg] file which holds important metadata and lists dependencies
|
||||
- `package-lock.json`: This *lockfile* will only exist if we use the npm package manager
|
||||
- `postcss.config.js`: Configuration file for [PostCSS][postcss], a tool to transform CSS with JavaScript
|
||||
- `tailwind.config.js`: Configuration file for the [TailwindCSS][tailwind] framework
|
||||
- `yarn.lock`: This *lockfile* will only exist if we use [the yarn package manager][yarn]
|
||||
|
||||
[next]: https://nextjs.org/
|
||||
[tailwind]: https://tailwindcss.com/
|
||||
[postcss]: https://postcss.org/
|
||||
[yarn]: https://yarnpkg.com/
|
||||
[pkg]: https://docs.npmjs.com/cli/v8/configuring-npm/package-json
|
||||
[react]: https://reactjs.org/
|
||||
[i18n]: https://next.i18next.com
|
||||
Don't take my word for it though. Let's start doing exactly that
|
||||
in [Part 2](/tutorials/pattern-design/part2).
|
||||
|
|
After Width: | Height: | Size: 217 KiB |
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Adding measurements
|
||||
order: 130
|
||||
order: 30
|
||||
---
|
||||
|
||||
FreeSewing is all about _made-to-measure_ sewing patterns -- or *parametric
|
||||
|
@ -18,19 +18,19 @@ So let's add it as a required measurement.
|
|||
|
||||
## Adding required measurements
|
||||
|
||||
In our `design/src/bib.mjs` file, we will add a `measurements` property to the `bib` object.
|
||||
In our `src/bib.mjs` file, we will add a `measurements` property to the `bib` object.
|
||||
This property will be an Array (a list) holding all required measurements for this part.
|
||||
|
||||
I am usign [*the official name* of the measurement](/reference/measurements) here. For head
|
||||
circumference, that name is `head`.
|
||||
|
||||
```design/src/bib.mjs
|
||||
```src/bib.mjs
|
||||
function draftBib({ part }) => {
|
||||
return part
|
||||
}
|
||||
|
||||
export const bib = {
|
||||
name: 'tutorial.bib',
|
||||
name: 'fromscratch.bib',
|
||||
draft: draftBib,
|
||||
// highlight-start
|
||||
measurements: [ 'head' ],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Adding options
|
||||
order: 140
|
||||
order: 40
|
||||
---
|
||||
|
||||
I have shown what our bib should look like, and added the _head_ measurement
|
||||
|
@ -23,13 +23,13 @@ and the head circumference. Let's call it `neckRatio`.
|
|||
|
||||
For this, I will add the `options` property to our `bib` object:
|
||||
|
||||
```design/src/bib.mjs
|
||||
```src/bib.mjs
|
||||
function draftBib({ part }) => {
|
||||
return part
|
||||
}
|
||||
|
||||
export const bib = {
|
||||
name: 'tutorial.bib',
|
||||
name: 'fromscratch.bib',
|
||||
draft: draftBib,
|
||||
measurements: [ 'head' ],
|
||||
// highlight-start
|
||||
|
@ -68,13 +68,7 @@ Instead, this `menu` property is there for the benefit FreeSewing's development
|
|||
environment which will use this to build a menu structure for the various
|
||||
options.
|
||||
|
||||
Each option type has a number of required properties. But in addition to that,
|
||||
you can add more to facilitate integrating with a front-end or other user
|
||||
interface.
|
||||
|
||||
You will see that after adding this option, the development environment will
|
||||
have a `fit` section under **Design Options**. This `menu` property is where
|
||||
that is based on.
|
||||
This is covered in more detail in [Part 3](/tutorials/pattern-design/part3) of this tutorial.
|
||||
|
||||
</Note>
|
||||
|
||||
|
@ -82,13 +76,13 @@ that is based on.
|
|||
|
||||
Let's do something similar for the width and length of our bib:
|
||||
|
||||
```design/src/bib.mjs
|
||||
```src/bib.mjs
|
||||
function draftBib({ part }) => {
|
||||
return part
|
||||
}
|
||||
|
||||
export const bib = {
|
||||
name: 'tutorial.bib',
|
||||
name: 'fromScratch.bib',
|
||||
draft: draftBib,
|
||||
measurements: [ 'head' ],
|
||||
options: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Avoiding overlap
|
||||
order: 220
|
||||
order: 92
|
||||
---
|
||||
|
||||
While we've only drawn the end of one strap, it's pretty obvious they overlap,
|
||||
|
@ -11,9 +11,48 @@ Specifically, we're going to rotate our strap out of the way until it no longer
|
|||
The rest of our bib should stay as it is, so let's start by making a list of points we need
|
||||
to rotate.
|
||||
|
||||
Once we have our list of points to rotate, we can rotate them. How far? Until the strap no longer overlaps.
|
||||
However, there is a catch.
|
||||
|
||||
<Example tutorial caption="It is looking pretty wonky now, but we'll deal with that next">
|
||||
## Macros and auto-gerenated IDs
|
||||
We have used the `round` macro to help us round the corners
|
||||
of our strap, and it added a bunch of auto-generated points to our pattern. We need to
|
||||
rotate these points too, but what are their names?
|
||||
|
||||
A macro will return the names of the things it created. So far, we have not captured
|
||||
that return value, but if we did, it would look like this:
|
||||
|
||||
```mjs
|
||||
{
|
||||
"tipRightTop": {
|
||||
"points": {
|
||||
"start": "__macro_@freesewing/plugin-round_tipRightTop_start",
|
||||
"cp1": "__macro_@freesewing/plugin-round_tipRightTop_cp1",
|
||||
"cp2": "__macro_@freesewing/plugin-round_tipRightTop_cp2",
|
||||
"end": "__macro_@freesewing/plugin-round_tipRightTop_end"
|
||||
},
|
||||
"paths": {
|
||||
"path": "__macro_@freesewing/plugin-round_tipRightTop_path"
|
||||
}
|
||||
},
|
||||
"tipRightBottom": {
|
||||
"points": {
|
||||
"start": "__macro_@freesewing/plugin-round_tipRightBottom_start",
|
||||
"cp1": "__macro_@freesewing/plugin-round_tipRightBottom_cp1",
|
||||
"cp2": "__macro_@freesewing/plugin-round_tipRightBottom_cp2",
|
||||
"end": "__macro_@freesewing/plugin-round_tipRightBottom_end"
|
||||
},
|
||||
"paths": {
|
||||
"path": "__macro_@freesewing/plugin-round_tipRightBottom_path"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Those names aren't very handy to remember. So I will rewrite this code a bit to
|
||||
we'll capture these return values from the `round` macros and create
|
||||
easy-to-remember points from them:
|
||||
|
||||
<Example tutorial caption="It looks the same as before, but not those macro points are accessible to us">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
|
@ -22,6 +61,9 @@ function draftBib({
|
|||
points,
|
||||
measurements,
|
||||
options,
|
||||
// highlight-start
|
||||
utils,
|
||||
// highlight-end
|
||||
macro,
|
||||
part,
|
||||
}) {
|
||||
|
@ -107,20 +149,176 @@ function draftBib({
|
|||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
macro("round", {
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
prefix: "tipRightTop",
|
||||
hide: false
|
||||
})
|
||||
macro("round", {
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
prefix: "tipRightBottom",
|
||||
hide: false
|
||||
})
|
||||
// highlight-start
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro("round", {
|
||||
id: "tipRightTop",
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
hide: false
|
||||
}),
|
||||
tipRightBottom: macro("round", {
|
||||
id: "tipRightBottom",
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
hide: false
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
paths.rect = new Path()
|
||||
.move(points.edgeTop)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.edgeTop)
|
||||
.close()
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
||||
Once we have our list of points to rotate, we can rotate them. How far? Until the strap no longer overlaps.
|
||||
|
||||
<Example tutorial caption="It is looking pretty wonky now, but we'll deal with that next">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
utils,
|
||||
macro,
|
||||
part,
|
||||
}) {
|
||||
|
||||
// Construct the quarter neck opening
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(tweak * measurements.head / 10, 0)
|
||||
points.bottom = new Point(0, tweak * measurements.head / 12)
|
||||
|
||||
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right)/2)
|
||||
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.hide() // Add this line
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
// Construct the complete neck opening
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
paths.neck = new Path()
|
||||
.move(points.top)
|
||||
.curve(points.topCp2, points.leftCp1, points.left)
|
||||
.curve(points.leftCp2, points.bottomCp1, points.bottom)
|
||||
.curve(points.bottomCp2, points.rightCp1, points.right)
|
||||
.curve(points.rightCp2, points.topCp1, points.top)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
|
||||
// Drawing the bib outline
|
||||
const width = measurements.head * options.widthRatio
|
||||
const length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(
|
||||
width / -2,
|
||||
points.top.y - (width / 2 - points.right.x)
|
||||
)
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
paths.rect = new Path()
|
||||
.move(points.topLeft)
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
.line(points.topRight)
|
||||
.line(points.topLeft)
|
||||
.close()
|
||||
.addClass('fabric')
|
||||
|
||||
// Shape the straps
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
|
||||
points.topLeft,
|
||||
0.5
|
||||
)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
// Round the straps
|
||||
const strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro("round", {
|
||||
id: "tipRightTop",
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
hide: false
|
||||
}),
|
||||
tipRightBottom: macro("round", {
|
||||
id: "tipRightBottom",
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
hide: false
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
// highlight-start
|
||||
/*
|
||||
|
@ -165,20 +363,20 @@ function draftBib({
|
|||
* what the rotated path looks like
|
||||
*/
|
||||
macro("round", {
|
||||
id: "showRightTop",
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
prefix: "tipRightTop",
|
||||
hide: false,
|
||||
class: 'contrast dotted',
|
||||
classes: 'contrast dotted',
|
||||
})
|
||||
macro("round", {
|
||||
id: "showRightBottom",
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
prefix: "tipRightBottom",
|
||||
hide: false,
|
||||
class: 'contrast dotted',
|
||||
classes: 'contrast dotted',
|
||||
})
|
||||
// highlight-end
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Completing the neck opening
|
||||
order: 180
|
||||
order: 80
|
||||
---
|
||||
|
||||
We've constructed the perfectly sized quarter neck, and we're going to use this
|
||||
|
@ -25,20 +25,38 @@ function draftBib({
|
|||
part,
|
||||
}) {
|
||||
|
||||
// Construct the quarter neck opening
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
const target = (measurements.head * options.neckRatio) /4
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(tweak * measurements.head / 10, 0)
|
||||
points.bottom = new Point(0, tweak * measurements.head / 12)
|
||||
points.right = new Point(
|
||||
tweak * measurements.head / 10,
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
tweak * measurements.head / 12
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right)/2)
|
||||
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2)
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
// highlight-start
|
||||
.hide()
|
||||
// highlight-end
|
||||
|
@ -79,16 +97,17 @@ function draftBib({
|
|||
part,
|
||||
}) {
|
||||
|
||||
// Construct the quarter neck opening
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
const target = (measurements.head * options.neckRatio) /4
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(tweak * measurements.head / 10, 0)
|
||||
points.bottom = new Point(0, tweak * measurements.head / 12)
|
||||
|
||||
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right)/2)
|
||||
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2)
|
||||
points.rightCp1 = points.right.shift( 90, points.bottom.dy(points.right) / 2)
|
||||
points.bottomCp2 = points.bottom.shift( 0, points.bottom.dx(points.right) / 2)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
|
@ -101,7 +120,9 @@ function draftBib({
|
|||
} while (Math.abs(delta) > 1)
|
||||
|
||||
// highlight-start
|
||||
// Construct the complete neck opening
|
||||
/*
|
||||
* Construct the complete neck opening
|
||||
*/
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
|
|
22
markdown/dev/tutorials/pattern-design/part2/conclusion/en.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
title: Conclusion (of part 2)
|
||||
order: 99
|
||||
---
|
||||
|
||||
You made it to the end of part 2, in which we got to do some real parametric design.
|
||||
|
||||
Wile our bib is a rather simple design, it did allow us to gain familiarity with FreeSewing's core API.
|
||||
|
||||
You have learned how to create points. And seen various way to manipulate them,
|
||||
including some more advanced things like rotating a bunch of them out of the
|
||||
way.
|
||||
|
||||
You've also learned how to draw paths, which are the lines and curves that make up our pattern.
|
||||
And we've used macros which can help us with repetetive tasks.
|
||||
|
||||
What we've gotten so far is a perfectly suitable sewing pattern. You can print this,
|
||||
and make a nice bib out of it.
|
||||
|
||||
But when we stick to these basics, FreeSewing doesn't really get a chance to shine.
|
||||
For that, I recommend [Part 3 of this tutorial](/tutorials/pattern-design/part3) where
|
||||
we'll go beyond the basics.
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Constructing the neck opening
|
||||
order: 160
|
||||
order: 60
|
||||
---
|
||||
|
||||
Our goal is to construct an oval neck opening that has a circumference
|
||||
|
@ -51,18 +51,34 @@ function draftBib({
|
|||
}) {
|
||||
|
||||
// highlight-start
|
||||
// Construct the quarter neck opening
|
||||
points.right = new Point(measurements.head / 10, 0)
|
||||
points.bottom = new Point(0, measurements.head / 12)
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
points.right = new Point(
|
||||
measurements.head / 10,
|
||||
0
|
||||
)
|
||||
points.bottom = new Point(
|
||||
0,
|
||||
measurements.head / 12
|
||||
)
|
||||
|
||||
points.rightCp1 = points.right
|
||||
.shift(90, points.bottom.dy(points.right)/2)
|
||||
points.bottomCp2 = points.bottom
|
||||
.shift(0, points.bottom.dx(points.right)/2)
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
points.bottomCp2 = points.bottom.shift(
|
||||
0,
|
||||
points.bottom.dx(points.right) / 2
|
||||
)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
// highlight-end
|
||||
|
||||
return part
|
||||
|
@ -76,21 +92,26 @@ Let's look at each line in detail.
|
|||
## Adding points
|
||||
|
||||
```js
|
||||
points.right = new Point(measurements.head / 10, 0)
|
||||
points.right = new Point(
|
||||
measurements.head / 10,
|
||||
0
|
||||
)
|
||||
```
|
||||
|
||||
- We're adding a point named `right` to the `points` object which holds our
|
||||
part's points
|
||||
- We're using the Point constructor, which takes two arguments: The point's X
|
||||
and Y values
|
||||
and Y coordinates in the 2-dimensional space
|
||||
- The X value is `measurements.head / 10`
|
||||
- The Y value is `0`
|
||||
|
||||
The creation of `points.bottom` is very similar, so let's skip to the next line:
|
||||
|
||||
```js
|
||||
points.rightCp1 = points.right
|
||||
.shift(90, points.bottom.dy(points.right)/2)
|
||||
points.rightCp1 = points.right.shift(
|
||||
90,
|
||||
points.bottom.dy(points.right) / 2
|
||||
)
|
||||
```
|
||||
|
||||
- We're adding a point named `rightCp1`, which will become the _control point_
|
||||
|
@ -123,7 +144,11 @@ introduced on the next line: Paths.
|
|||
```js
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.curve(
|
||||
points.rightCp1,
|
||||
points.bottomCp2,
|
||||
points.bottom
|
||||
)
|
||||
```
|
||||
|
||||
- We're adding a path named `quarterNeck` to the `paths` object which holds our
|
||||
|
@ -139,6 +164,12 @@ case, we moved to our `right` points.
|
|||
From there, we drew a cubic Bézier curve to our `bottom` point by using
|
||||
`rightCp1` and `bottomCp2` as control points.
|
||||
|
||||
<Tip>
|
||||
|
||||
Many of the methods in the FreeSewing API are *chainable* allowing you
|
||||
to string them together like in this example.
|
||||
</Tip>
|
||||
|
||||
When all is said and done, we now have a quarter of our neck opening.
|
||||
The only problem is, we have no guarantee whatsoever that this opening is the correct size.
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Creating the closure
|
||||
order: 210
|
||||
order: 91
|
||||
---
|
||||
|
||||
Things are starting to look good, but we can't fit the bib over the baby's head like this.
|
||||
|
@ -41,28 +41,31 @@ function draftBib({
|
|||
part,
|
||||
}) {
|
||||
|
||||
// Construct the quarter neck opening
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
const target = (measurements.head * options.neckRatio) /4
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(tweak * measurements.head / 10, 0)
|
||||
points.bottom = new Point(0, tweak * measurements.head / 12)
|
||||
|
||||
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right)/2)
|
||||
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2)
|
||||
points.rightCp1 = points.right.shift( 90, points.bottom.dy(points.right) / 2)
|
||||
points.bottomCp2 = points.bottom.shift( 0, points.bottom.dx(points.right) / 2)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.hide() // Add this line
|
||||
.hide()
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
// Construct the complete neck opening
|
||||
/*
|
||||
* Construct the complete neck opening
|
||||
*/
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
|
@ -81,7 +84,9 @@ function draftBib({
|
|||
.close()
|
||||
.addClass('fabric')
|
||||
|
||||
// Drawing the bib outline
|
||||
/*
|
||||
* Drawing the bib outline
|
||||
*/
|
||||
const width = measurements.head * options.widthRatio
|
||||
const length = measurements.head * options.lengthRatio
|
||||
|
||||
|
@ -102,7 +107,9 @@ function draftBib({
|
|||
.close()
|
||||
.addClass('fabric')
|
||||
|
||||
// Shape the straps
|
||||
/*
|
||||
* Shape the straps
|
||||
*/
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
@ -115,7 +122,9 @@ function draftBib({
|
|||
)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
// Now, adapt our `rect` path so it's no longer a rectangle:
|
||||
/*
|
||||
* Now, adapt our `rect` path so it's no longer a rectangle:
|
||||
*/
|
||||
paths.rect = new Path()
|
||||
.move(points.edgeTop)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
|
@ -134,17 +143,17 @@ function draftBib({
|
|||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
macro("round", {
|
||||
id: "tipRightTop",
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
prefix: "tipRightTop",
|
||||
hide: false
|
||||
})
|
||||
macro("round", {
|
||||
id: "tipRightBottom",
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
prefix: "tipRightBottom",
|
||||
hide: false
|
||||
})
|
||||
// highlight-end
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
---
|
||||
title: A part's draft method
|
||||
order: 150
|
||||
order: 50
|
||||
---
|
||||
|
||||
Time to turn our attention to the draft method of our part.
|
||||
Inside our `design/src/bib.mjs` file, this is what it currently looks like:
|
||||
Inside our `src/bib.mjs` file, this is what it currently looks like:
|
||||
|
||||
```design/src/bib.mjs
|
||||
```src/bib.mjs
|
||||
function draftBib({ part }) => {
|
||||
return part
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ equivalent:
|
|||
|
||||
<Tabs tabs="Without destructuring, With destructuring">
|
||||
<Tab>
|
||||
```design/src/bib.mjs
|
||||
```src/bib.mjs
|
||||
function draftBib(props) {
|
||||
|
||||
return props.part
|
||||
|
@ -38,7 +38,7 @@ function draftBib(props) {
|
|||
```
|
||||
</Tab>
|
||||
<Tab>
|
||||
```design/src/bib.mjs
|
||||
```src/bib.mjs
|
||||
function draftBib({ part }) {
|
||||
|
||||
return part
|
||||
|
@ -61,7 +61,7 @@ If you're new to JavaScript, and don't intuitively _get this_, stick with it. It
|
|||
|
||||
Change the function to look like this:
|
||||
|
||||
```design/src/bib.mjs
|
||||
```src/bib.mjs
|
||||
function draftBib({
|
||||
// highlight-start
|
||||
Path,
|
||||
|
@ -88,6 +88,8 @@ Here's a brief summary of the things we've added above:
|
|||
- `points`: A container object to hold the part's points
|
||||
- `paths`: A container object to hold the part's paths
|
||||
|
||||
<Tip compact>Remember: Constructures start with a **C**apital letter</Tip>
|
||||
|
||||
Long story short: These will make it possible for us to draw points and paths easily.
|
||||
|
||||
So let's go ahead and do that.
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
---
|
||||
title: Drawing the bib outline
|
||||
order: 190
|
||||
order: 88
|
||||
---
|
||||
|
||||
With our neck opening in place, let us draw the basic outline of our bib.
|
||||
|
||||
<Example tutorial caption="Note how the neck opening is the same distance from the left, right, and top edge">
|
||||
```design/src/bib.mjs
|
||||
```src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
|
|
|
@ -23,13 +23,16 @@ function draftBib({
|
|||
points,
|
||||
measurements,
|
||||
options,
|
||||
utils,
|
||||
macro,
|
||||
part,
|
||||
}) {
|
||||
|
||||
// Construct the quarter neck opening
|
||||
/*
|
||||
* Construct the neck opening
|
||||
*/
|
||||
const target = (measurements.head * options.neckRatio) / 4
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(tweak * measurements.head / 10, 0)
|
||||
|
@ -116,32 +119,47 @@ function draftBib({
|
|||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
macro("round", {
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
prefix: "tipRightTop",
|
||||
// strikeout-start
|
||||
/* Remove this to have the macro
|
||||
* only create the points we need
|
||||
* and not draw a path
|
||||
hide: false
|
||||
*/
|
||||
// strikeout-end
|
||||
})
|
||||
macro("round", {
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
prefix: "tipRightBottom",
|
||||
// strikeout-start
|
||||
/* Remove this to have the macro
|
||||
* only create the points we need
|
||||
* and not draw a path
|
||||
hide: false
|
||||
*/
|
||||
// strikeout-end
|
||||
})
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro("round", {
|
||||
id: "tipRightTop",
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
// strikeout-start
|
||||
/* Remove this to have the macro
|
||||
* only create the points we need
|
||||
* and not draw a path
|
||||
hide: false
|
||||
*/
|
||||
// strikeout-end
|
||||
}),
|
||||
tipRightBottom: macro("round", {
|
||||
id: "tipRightBottom",
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
// strikeout-start
|
||||
/* Remove this to have the macro
|
||||
* only create the points we need
|
||||
* and not draw a path
|
||||
hide: false
|
||||
*/
|
||||
// strikeout-end
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
const rotateThese = [
|
||||
"edgeTopLeftCp",
|
||||
"edgeTop",
|
||||
|
|
|
@ -2,267 +2,44 @@
|
|||
title: "Part 2: Parametric design"
|
||||
---
|
||||
|
||||
Hello there, and welcome to this FreeSewing pattern design tutorial.
|
||||
My name is Joost, and in this tutorial I will show you
|
||||
how to design a made-to-measure sewing pattern, start to finish.
|
||||
Welcome to part 2 of this FreeSewing pattern design tutorial.
|
||||
In this part I will show you how to design a made-to-measure
|
||||
sewing pattern, start to finish.
|
||||
|
||||
<Tip>
|
||||
##### Before you start
|
||||
|
||||
If you haven't done so yet, read the [Before you start
|
||||
guide](/guides/prerequisites). It's very short, but covers some basic
|
||||
This tutorial assumes you are familiar with the following:
|
||||
|
||||
- Scalable Vector Graphics
|
||||
- The coordinate system
|
||||
- Units in FreeSewing
|
||||
- Cubic Bézier curves
|
||||
|
||||
Which is a lot to assume. So if you'd like you can take a quick detour
|
||||
via our [Before you start guide](/guides/prerequisites). \
|
||||
It's very short, but covers some basic
|
||||
terminology and concepts that we'll use throughout this guide.
|
||||
</Tip>
|
||||
|
||||
I will be designing a pattern for a baby bib. It's a very simple pattern, but
|
||||
that's ok. It is a tutorial after all. This will give us plenty to work with.
|
||||
## Pick a template
|
||||
|
||||
At the end of this tutorial, I will have created this pattern, and if you
|
||||
follow along, so will you:
|
||||
The FreeSewing development environment ships with several templates you
|
||||
can start from. I recommend you start **From scratch** as you'll learn the most
|
||||
that way. But you can also start from the **Tutorial** template in which case
|
||||
you will already have the end result we are aiming for today:
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
|
||||
<Example tutorial="1" previewFirst="1" caption="Our end result">
|
||||
```design/src/bib.mjs
|
||||
function draftBib({
|
||||
Path,
|
||||
Point,
|
||||
paths,
|
||||
points,
|
||||
measurements,
|
||||
options,
|
||||
macro,
|
||||
store,
|
||||
complete,
|
||||
snippets,
|
||||
Snippet,
|
||||
part,
|
||||
}) {
|
||||

|
||||
|
||||
/*
|
||||
* 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)
|
||||
</div>
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.hide() // Add this line
|
||||
Depending on the choice you made, you will need to edit files in a different folder.
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
- Edit files in `design/from-scratch` if you are using the **From scratch** template
|
||||
- Edit files in `design/tutorial` if you are using the **Tutorial** template
|
||||
|
||||
/*
|
||||
* Construct the complete neck opening
|
||||
*/
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
points.leftCp1 = points.rightCp2.flipX()
|
||||
points.leftCp2 = points.rightCp1.flipX()
|
||||
points.top = points.bottom.flipY()
|
||||
points.topCp1 = points.bottomCp2.flipY()
|
||||
points.topCp2 = points.bottomCp1.flipY()
|
||||
|
||||
/*
|
||||
* Drawing the bib outline
|
||||
*/
|
||||
const width = measurements.head * options.widthRatio
|
||||
const length = measurements.head * options.lengthRatio
|
||||
|
||||
points.topLeft = new Point(
|
||||
width / -2,
|
||||
points.top.y - (width / 2 - points.right.x)
|
||||
)
|
||||
points.topRight = points.topLeft.shift(0, width)
|
||||
points.bottomLeft = points.topLeft.shift(-90, length)
|
||||
points.bottomRight = points.topRight.shift(-90, length)
|
||||
|
||||
// Shape the straps
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
||||
points.edgeLeftCp = points.edgeLeft.shiftFractionTowards(points.topLeft, 0.5)
|
||||
points.edgeRightCp = points.edgeLeftCp.flipX()
|
||||
points.edgeTopLeftCp = points.edgeTop.shiftFractionTowards(
|
||||
points.topLeft,
|
||||
0.5
|
||||
)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
/*
|
||||
* Round the straps
|
||||
*/
|
||||
const strap = points.edgeTop.dy(points.top)
|
||||
|
||||
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
|
||||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
macro("round", {
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
prefix: "tipRightTop",
|
||||
})
|
||||
macro("round", {
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
prefix: "tipRightBottom",
|
||||
})
|
||||
const rotateThese = [
|
||||
"edgeTopLeftCp",
|
||||
"edgeTop",
|
||||
"tipRight",
|
||||
"tipRightTop",
|
||||
"tipRightTopStart",
|
||||
"tipRightTopCp1",
|
||||
"tipRightTopCp2",
|
||||
"tipRightTopEnd",
|
||||
"tipRightBottomStart",
|
||||
"tipRightBottomCp1",
|
||||
"tipRightBottomCp2",
|
||||
"tipRightBottomEnd",
|
||||
"tipRightBottom",
|
||||
"top",
|
||||
"topCp2"
|
||||
]
|
||||
while (points.tipRightBottomStart.x > -1) {
|
||||
for (const p of rotateThese) points[p] = points[p].rotate(1, points.edgeLeft)
|
||||
}
|
||||
|
||||
/*
|
||||
* Snap anchor
|
||||
*/
|
||||
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
|
||||
|
||||
/*
|
||||
* Add points for second strap
|
||||
*/
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
points.topCp1 = points.topCp2.flipX()
|
||||
points.tipLeftTopStart = points.tipRightTopStart.flipX()
|
||||
points.tipLeftTopCp1 = points.tipRightTopCp1.flipX()
|
||||
points.tipLeftTopCp2 = points.tipRightTopCp2.flipX()
|
||||
points.tipLeftTopEnd = points.tipRightTopEnd.flipX()
|
||||
points.tipLeftBottomStart = points.tipRightBottomStart.flipX()
|
||||
points.tipLeftBottomCp1 = points.tipRightBottomCp1.flipX()
|
||||
points.tipLeftBottomCp2 = points.tipRightBottomCp2.flipX()
|
||||
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
|
||||
points.snapRight = points.snapLeft.flipX()
|
||||
|
||||
/*
|
||||
* Round the bottom corners
|
||||
*/
|
||||
macro("round", {
|
||||
from: points.topLeft,
|
||||
to: points.bottomRight,
|
||||
via: points.bottomLeft,
|
||||
radius: points.bottomRight.x / 4,
|
||||
prefix: "bottomLeft"
|
||||
})
|
||||
macro("round", {
|
||||
from: points.bottomLeft,
|
||||
to: points.topRight,
|
||||
via: points.bottomRight,
|
||||
radius: points.bottomRight.x / 4,
|
||||
prefix: "bottomRight"
|
||||
})
|
||||
|
||||
/*
|
||||
* Create one path for the bib outline
|
||||
*/
|
||||
paths.seam = new Path()
|
||||
.move(points.edgeLeft)
|
||||
.line(points.bottomLeftStart)
|
||||
.curve(points.bottomLeftCp1, points.bottomLeftCp2, points.bottomLeftEnd)
|
||||
.line(points.bottomRightStart)
|
||||
.curve(points.bottomRightCp1, points.bottomRightCp2, points.bottomRightEnd)
|
||||
.line(points.edgeRight)
|
||||
.curve(points.edgeRightCp, points.edgeTopRightCp, points.tipLeftTopStart)
|
||||
.curve(points.tipLeftTopCp1, points.tipLeftTopCp2, points.tipLeftTopEnd)
|
||||
.curve(points.tipLeftBottomCp1, points.tipLeftBottomCp2, points.tipLeftBottomEnd)
|
||||
.curve(points.topCp1, points.rightCp2, points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.curve(points.bottomCp1, points.leftCp2, points.left)
|
||||
.curve(points.leftCp1, points.topCp2, points.tipRightBottomEnd)
|
||||
.curve(points.tipRightBottomCp2, points.tipRightBottomCp1, points.tipRightBottomStart)
|
||||
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.close()
|
||||
.addClass("fabric")
|
||||
|
||||
/*
|
||||
* Mark the bias tape, but only if complete is set
|
||||
*/
|
||||
if (complete) paths.bias = paths.seam
|
||||
.offset(-5)
|
||||
.addClass("various dashed")
|
||||
.addText("finishWithBiasTape", "center fill-various")
|
||||
/*
|
||||
* Annotations
|
||||
*/
|
||||
|
||||
// Cutlist
|
||||
store.cutlist.setCut({ cut: 1, from: 'fabric' })
|
||||
|
||||
// Snaps
|
||||
points.snapLeft = points.top.shiftFractionTowards(points.edgeTop, 0.5)
|
||||
points.snapRight = points.snapLeft.flipX()
|
||||
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
|
||||
snippets.snapSocket = new Snippet('snap-socket', points.snapRight).attr('opacity', 0.5)
|
||||
|
||||
// Logo
|
||||
points.logo = new Point(0, 0)
|
||||
snippets.logo = new Snippet("logo", points.logo)
|
||||
|
||||
// Title
|
||||
points.title = points.bottom.shift(-90, 45)
|
||||
macro("title", {
|
||||
at: points.title,
|
||||
nr: 1,
|
||||
title: "bib",
|
||||
scale: 0.7
|
||||
})
|
||||
|
||||
// Scalbox
|
||||
points.scalebox = points.title.shift(-90, 55)
|
||||
macro("scalebox", { at: points.scalebox })
|
||||
|
||||
return part
|
||||
}
|
||||
```
|
||||
</Example>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before I can get started, I want to make sure I have the required software
|
||||
installed on my computer.
|
||||
|
||||
FreeSewing is a JavaScript library that can run in the browser, on
|
||||
[Node.js](https://nodejs.org/), or a variety of other runtimes such as Bun,
|
||||
Deno, AWS Lambda, and so on.
|
||||
|
||||
For development, I will use Node.js. If you don't have Node.js on our system,
|
||||
follow the link above and install it.
|
||||
|
||||
<Tip compact>You need Node.js 18 (lts/hydrogen) or higher to use FreeSewing</Tip>
|
||||
|
||||
To test whether NodeJS is installed, and see it's version, you can run this command:
|
||||
|
||||
```sh
|
||||
node -v
|
||||
```
|
||||
|
||||
If you get the Node.js version number, that means NodeJs is installed. Yay!
|
||||
You can choose either, or even switch back and forth between both.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Fitting the neck opening
|
||||
order: 170
|
||||
order: 70
|
||||
---
|
||||
|
||||
We are not going to create some opening that we _hope_ is the right size, we're
|
||||
|
|
BIN
markdown/dev/tutorials/pattern-design/part2/fromscratch.png
Normal file
After Width: | Height: | Size: 219 KiB |
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
title: Creating a new design
|
||||
order: 10
|
||||
---
|
||||
|
||||
The development environment has already setup various designs for us.
|
||||
Since I am using the **From scratch** template, the files I want to edit live
|
||||
in `design/from-scratch`.
|
||||
|
||||
The design's main file is `design/from-scratch/src/index.mjs`, and our bib part
|
||||
will live in `design/from-scratch/src/bib.mjs`.
|
||||
|
||||
This `bib.mjs` file is where will be doing most of the work here in part 2 of this
|
||||
tutorial. But let's start with the `index.mjs` file as an appetizer, because this
|
||||
is where a new FreeSewing design is brought to live. It looks like this:
|
||||
|
||||
```src/index.mjs
|
||||
import { Design } from '@freesewing/core'
|
||||
import { i18n } from '../i18n/index.mjs'
|
||||
import { bib } from './bib.mjs'
|
||||
|
||||
/*
|
||||
* Create the design
|
||||
*/
|
||||
const FromScratch = new Design({
|
||||
data: {
|
||||
name: 'fromScratch',
|
||||
version: '0.0.1',
|
||||
},
|
||||
parts: [ bib ],
|
||||
})
|
||||
|
||||
export { bib, FromScratch, i18n }
|
||||
```
|
||||
|
||||
Not too intimidating, is it?
|
||||
|
||||
## Imports
|
||||
|
||||
At the top of the file, we have a bunch of *imports*:
|
||||
|
||||
```src/index.mjs
|
||||
import { Design } from '@freesewing/core'
|
||||
import { i18n } from '../i18n/index.mjs'
|
||||
import { bib } from './bib.mjs'
|
||||
```
|
||||
|
||||
An `import` is how JavaScript loads code from a different file. \
|
||||
It essentially says: _import **something** from **somewhere**_:
|
||||
|
||||
| Something | Somewhere | Description |
|
||||
| ---------:|:--------- |:----------- |
|
||||
| `Design` | `@freesewing/core` | Loads the `Design` constructor from FreeSewing's core library |
|
||||
| `i18n` | `../i18n/index.mjs` | Loads `i18n` from the `index.mjs` file in the `i18n` one level higher (these are the translations) |
|
||||
| `bib` | `./bib.mjs` | Loads `bib` from the `bib.mjs` file in the same folder (this is our part) |
|
||||
|
||||
As you can see, the *somewhere* can be different things. A local file like in
|
||||
lines 2 and 3, or a package published on
|
||||
[NPM](https://www.npmjs.com/package/@freesewing/core), in line 1.
|
||||
|
||||
It's nothing for you to worry about too much at this point, but it does help us
|
||||
understand what's going on at the bottom of our file.
|
||||
|
||||
## Exports
|
||||
|
||||
The bottom holds a single `export` line:
|
||||
|
||||
```src/index.mjs
|
||||
export { bib, FromScratch, i18n }
|
||||
```
|
||||
|
||||
When we `export` things from our code, we allow others to `import` those things
|
||||
for re-use in their code. That's how it works.
|
||||
|
||||
These are named exports. We are exporting three things:
|
||||
|
||||
- `bib` is our part. We are exporting it so people can re-use just this part.
|
||||
- `FromScratch` is our complete design. Exporting it means people can use it.
|
||||
- `i18n` are the translations. We are exporting it so people can load them to use with their own translation solution.
|
||||
|
||||
If you are not familiar with this syntax, you'll get the hang of it soon enough.
|
||||
|
||||
## Design constructor
|
||||
|
||||
finally the most interesting part of this file is the middle part where we are
|
||||
creating a new design:
|
||||
|
||||
```src/index.mjs
|
||||
const FromScratch = new Design({
|
||||
data: {
|
||||
name: 'fromScratch',
|
||||
version: '0.0.1',
|
||||
},
|
||||
parts: [ bib ],
|
||||
})
|
||||
```
|
||||
|
||||
The `Design` that we imported on line 1 is a so-called **constructor**.
|
||||
A constructor is a function that can create things out of nothing. Or,
|
||||
to be more accurate, that you can use with the `new` keyword.
|
||||
|
||||
<Tip compact>
|
||||
|
||||
It's a convention that constructor names start with an **C**apital letter.
|
||||
</Tip>
|
||||
|
||||
We are passing some info to a this `Design` function, but the `data` we are
|
||||
passing is optional. If we strip that away for a moment, and don't bother
|
||||
assigning the result to a variable, we reveal the essence of what it takes to
|
||||
create a new FreeSewing design:
|
||||
|
||||
```src/index.mjs
|
||||
new Design({
|
||||
parts: [ bib ],
|
||||
})
|
||||
```
|
||||
|
||||
In several places in this documentation, I will mention that *a design is not
|
||||
much more than a thin wrapper around parts*. But I feel nothing drives
|
||||
that point home like seeing it in code like this.
|
||||
|
||||
To create a new design, we call the `Design` constructor and give it a list
|
||||
(an array) or parts.
|
||||
|
||||
<Related>
|
||||
|
||||
Refer to [the design reference documentation](/reference/api/design) for
|
||||
all details about what you can pass to the Design constructor.
|
||||
</Related>
|
||||
|
||||
That's it. So let's look at where the real action is next: Our first part.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Creating a part
|
||||
order: 120
|
||||
order: 20
|
||||
---
|
||||
|
||||
Much like garments themselves, patterns are made up of _parts_.
|
||||
|
@ -16,34 +16,23 @@ this, but it's a good habit to get into.
|
|||
|
||||
## bib.mjs
|
||||
|
||||
Since I chose the `tutorial` preset, the development environment is preconfigured for this tutorial.
|
||||
I am going to use the **From scratch** template. So the files I want to edit live
|
||||
in `design/from-scratch`.
|
||||
|
||||
The design's main file lives in `design/src/index.mjs`, and the bib part lives in `design/src/bib.mjs`.
|
||||
Our part lives in `design/from-scratch/src/bib.mjs`, and it currently looks like this:
|
||||
|
||||
This `bib.mjs` is where I will be doing most of the work.
|
||||
The file that was created includes a lot of comments to provide guidance to those not using this tutorial.
|
||||
I have removed those comments from the inline examples in this tutorial for clarity in our example.
|
||||
|
||||
The `bib.mjs` file currently looks like this:
|
||||
|
||||
```design/src/bib.mjs
|
||||
/*
|
||||
* This function drafts the part
|
||||
*/
|
||||
function draftBib ({ part }) => {
|
||||
```src/bib.mjs
|
||||
function draftBib({ part }) {
|
||||
return part
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the part object
|
||||
*/
|
||||
export const bib = {
|
||||
name: 'tutorial.bib',
|
||||
name: 'fromscratch.bib',
|
||||
draft: draftBib,
|
||||
}
|
||||
```
|
||||
|
||||
### The part object
|
||||
## The part object
|
||||
|
||||
Each part in FreeSewing is an object that describes the part, and has a `draft`
|
||||
method to do the actual work of drafting the part.
|
||||
|
@ -64,13 +53,13 @@ to create other designs.
|
|||
</Note>
|
||||
|
||||
|
||||
#### The part name
|
||||
### The part name
|
||||
|
||||
```design/src/bib.mjs
|
||||
name: 'tutorial.bib',
|
||||
```src/bib.mjs
|
||||
name: 'fromscratch.bib',
|
||||
```
|
||||
|
||||
A part's `name` should be unique in a design. I used `tutorial.bib` as the
|
||||
A part's `name` should be unique in a design. I used `fromscratch.bib` as the
|
||||
name, because that makes sense.
|
||||
|
||||
<Warning>
|
||||
|
@ -80,9 +69,9 @@ This avoids naming conflicts when mixing parts from various designs to create a
|
|||
|
||||
</Warning>
|
||||
|
||||
#### The part's draft method
|
||||
### The part's draft method
|
||||
|
||||
```design/src/bib.mjs
|
||||
```src/bib.mjs
|
||||
draft: draftBib,
|
||||
```
|
||||
|
||||
|
@ -97,86 +86,4 @@ This structure of putting the draft method at the top of the file and
|
|||
the part object at the bottom is a bit of a convention in FreeSewing.
|
||||
</Note>
|
||||
|
||||
## index.mjs
|
||||
|
||||
<Tip>
|
||||
Feel free to skip to [Adding
|
||||
measurements](/tutorials/pattern-design/adding-measurements) if you're itching
|
||||
to get started. Or, read on for an explanation of what's going on in the
|
||||
`index.mjs` file.
|
||||
</Tip>
|
||||
|
||||
The `index.mjs` file is already complete and we won't be making any changes to
|
||||
it. But if you are curious about what's going on inside `index.mjs`,
|
||||
this is all we need:
|
||||
|
||||
```design/src/index.mjs
|
||||
import { Design } from '@freesewing/core'
|
||||
import { bib } from './bib.mjs'
|
||||
import { i18n } from '../i18n/index.mjs'
|
||||
|
||||
const Tutorial = new Design({
|
||||
parts: [ bib ],
|
||||
})
|
||||
|
||||
export { bib, Tutorial, i18n }
|
||||
```
|
||||
|
||||
If you are familiar with Javascript, I hope you are happy to see that FreeSewing uses ESM modules, and named exports.
|
||||
|
||||
If you are not familiar with Javascript, these `import` statements are how we load code from other files.
|
||||
There's three of them:
|
||||
|
||||
```design/src/index.mjs
|
||||
import { Design } from '@freesewing/core'
|
||||
```
|
||||
|
||||
This loads the `Design` constructure from FreeSewing's core library.
|
||||
A constructor is a function that creates something. So the `Design` constructor creates a Design.
|
||||
|
||||
```design/src/index.mjs
|
||||
import { bib } from './bib.mjs'
|
||||
```
|
||||
|
||||
This loads the `bib` part from the `bib.mjs` file in the same folder.
|
||||
This is what we will be working on.
|
||||
|
||||
```design/src/index.mjs
|
||||
import { i18n } from '../i18n/index.mjs'
|
||||
```
|
||||
|
||||
And this loads something named `i18n` from the `index.mjs` file in the `i18n`
|
||||
folder that's one level higher. These are the translations.
|
||||
|
||||
I will show you how you can provide translations for your designs towards the
|
||||
end of this tutorial.
|
||||
|
||||
```design/src/index.mjs
|
||||
const Tutorial = new Design({
|
||||
parts: [ bib ],
|
||||
})
|
||||
```
|
||||
|
||||
This is where the magic happens. We create a new Design by passing the Design
|
||||
constructure a configuration object. All it holds is the `parts` key that is
|
||||
an array of our parts.
|
||||
|
||||
Which goes to show that a design isn't much more than a bunch of parts.
|
||||
|
||||
```design/src/index.mjs
|
||||
export { bib, Tutorial, i18n }
|
||||
```
|
||||
|
||||
And then all that's left to do is export things so that people can use them.
|
||||
These are named exports. We are exporting three things:
|
||||
|
||||
- `Tutorial` is our complete design. Exporting it means people can use it.
|
||||
- `bib` is our part. We are exporting it so people can re-use just this part.
|
||||
- `i18n` are the translations. We are exporting it so people can load them when using our Tutorial.
|
||||
|
||||
<Related>
|
||||
|
||||
Refer to [the design reference documentation](/reference/api/design) for
|
||||
all details about what you can pass to the Design constructor.
|
||||
</Related>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Rounding the corners
|
||||
order: 240
|
||||
order: 94
|
||||
---
|
||||
|
||||
We already know how to round corners, we'll have the `round` macro take care of that for us.
|
||||
|
@ -17,13 +17,16 @@ function draftBib({
|
|||
points,
|
||||
measurements,
|
||||
options,
|
||||
utils,
|
||||
macro,
|
||||
part,
|
||||
}) {
|
||||
|
||||
// Construct the quarter neck opening
|
||||
/*
|
||||
* Construct the neck opening
|
||||
*/
|
||||
const target = (measurements.head * options.neckRatio) / 4
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(tweak * measurements.head / 10, 0)
|
||||
|
@ -84,18 +87,33 @@ function draftBib({
|
|||
points.tipRightTop = new Point(points.tipRight.x, points.edgeTop.y)
|
||||
points.tipRightBottom = new Point(points.tipRight.x, points.top.y)
|
||||
|
||||
macro("round", {
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
prefix: "tipRightTop",
|
||||
})
|
||||
macro("round", {
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
prefix: "tipRightBottom",
|
||||
})
|
||||
/*
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids1 = {
|
||||
tipRightTop: macro("round", {
|
||||
id: "tipRightTop",
|
||||
from: points.edgeTop,
|
||||
to: points.tipRight,
|
||||
via: points.tipRightTop,
|
||||
}),
|
||||
tipRightBottom: macro("round", {
|
||||
id: "tipRightBottom",
|
||||
from: points.tipRight,
|
||||
to: points.top,
|
||||
via: points.tipRightBottom,
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids1) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids1[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
|
||||
const rotateThese = [
|
||||
"edgeTopLeftCp",
|
||||
"edgeTop",
|
||||
|
@ -130,21 +148,35 @@ function draftBib({
|
|||
points.tipLeftBottomEnd = points.tipRightBottomEnd.flipX()
|
||||
|
||||
// highlight-start
|
||||
// Round the bottom corners
|
||||
macro("round", {
|
||||
from: points.topLeft,
|
||||
to: points.bottomRight,
|
||||
via: points.bottomLeft,
|
||||
radius: points.bottomRight.x / 4,
|
||||
prefix: "bottomLeft"
|
||||
})
|
||||
macro("round", {
|
||||
from: points.bottomLeft,
|
||||
to: points.topRight,
|
||||
via: points.bottomRight,
|
||||
radius: points.bottomRight.x / 4,
|
||||
prefix: "bottomRight"
|
||||
})
|
||||
/*
|
||||
* Round the bottom corners
|
||||
* Macros will return the auto-generated IDs
|
||||
*/
|
||||
const ids2 = {
|
||||
bottomLeft: macro("round", {
|
||||
id: "bottomLeft",
|
||||
from: points.topLeft,
|
||||
to: points.bottomRight,
|
||||
via: points.bottomLeft,
|
||||
radius: points.bottomRight.x / 4,
|
||||
}),
|
||||
bottomRight: macro("round", {
|
||||
id: "bottomRight",
|
||||
from: points.bottomLeft,
|
||||
to: points.topRight,
|
||||
via: points.bottomRight,
|
||||
radius: points.bottomRight.x / 4,
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Create points from them with easy names
|
||||
*/
|
||||
for (const side in ids2) {
|
||||
for (const id of ['start', 'cp1', 'cp2', 'end']) {
|
||||
points[`${side}${utils.capitalize(id)}`] = points[ids2[side].points[id]].copy()
|
||||
}
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
// Create one path for the bib outline
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Shaping the straps
|
||||
order: 200
|
||||
order: 90
|
||||
---
|
||||
|
||||
Our straps should follow the neck opening, which isn't that hard to do.
|
||||
|
@ -28,28 +28,31 @@ function draftBib({
|
|||
part,
|
||||
}) {
|
||||
|
||||
// Construct the quarter neck opening
|
||||
/*
|
||||
* Construct the quarter neck opening
|
||||
*/
|
||||
const target = (measurements.head * options.neckRatio) /4
|
||||
let tweak = 1
|
||||
let target = (measurements.head * options.neckRatio) /4
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point(tweak * measurements.head / 10, 0)
|
||||
points.bottom = new Point(0, tweak * measurements.head / 12)
|
||||
|
||||
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right)/2)
|
||||
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2)
|
||||
points.rightCp1 = points.right.shift( 90, points.bottom.dy(points.right) / 2)
|
||||
points.bottomCp2 = points.bottom.shift( 0, points.bottom.dx(points.right) / 2)
|
||||
|
||||
paths.quarterNeck = new Path()
|
||||
.move(points.right)
|
||||
.curve(points.rightCp1, points.bottomCp2, points.bottom)
|
||||
.hide() // Add this line
|
||||
.hide()
|
||||
|
||||
delta = paths.quarterNeck.length() - target
|
||||
if (delta > 0) tweak = tweak * 0.99
|
||||
else tweak = tweak * 1.02
|
||||
} while (Math.abs(delta) > 1)
|
||||
|
||||
// Construct the complete neck opening
|
||||
/*
|
||||
* Construct the complete neck opening
|
||||
*/
|
||||
points.rightCp2 = points.rightCp1.flipY()
|
||||
points.bottomCp1 = points.bottomCp2.flipX()
|
||||
points.left = points.right.flipX()
|
||||
|
@ -68,7 +71,9 @@ function draftBib({
|
|||
.close()
|
||||
.addClass('fabric')
|
||||
|
||||
// Drawing the bib outline
|
||||
/*
|
||||
* Drawing the bib outline
|
||||
*/
|
||||
const width = measurements.head * options.widthRatio
|
||||
const length = measurements.head * options.lengthRatio
|
||||
|
||||
|
@ -90,7 +95,9 @@ function draftBib({
|
|||
.addClass('fabric')
|
||||
|
||||
// highlight-start
|
||||
// Shape the straps
|
||||
/*
|
||||
* Shape the straps
|
||||
*/
|
||||
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
|
||||
points.edgeRight = new Point(points.topRight.x, points.right.y)
|
||||
points.edgeTop = new Point(0, points.topLeft.y)
|
||||
|
@ -103,7 +110,9 @@ function draftBib({
|
|||
)
|
||||
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
|
||||
|
||||
// Now, adapt our `rect` path so it's no longer a rectangle:
|
||||
/*
|
||||
* Now, adapt our `rect` path so it's no longer a rectangle:
|
||||
*/
|
||||
paths.rect = new Path()
|
||||
.move(points.edgeTop)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
|
|
BIN
markdown/dev/tutorials/pattern-design/part2/tutorial.png
Normal file
After Width: | Height: | Size: 308 KiB |
527
markdown/dev/tutorials/pattern-design/part3/annotations/en.md
Normal 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>
|
||||
|
561
markdown/dev/tutorials/pattern-design/part3/complete/en.md
Normal 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>
|
||||
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
53
markdown/dev/tutorials/pattern-design/part3/expand/en.md
Normal 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.
|
340
markdown/dev/tutorials/pattern-design/part3/flag/en.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
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
|
BIN
markdown/dev/tutorials/pattern-design/part3/flag/flag.png
Normal file
After Width: | Height: | Size: 47 KiB |
7
markdown/dev/tutorials/pattern-design/part3/i18n/en.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: Supporting translation
|
||||
order: 80
|
||||
---
|
||||
|
||||
<Fixme compact>Write this section for v3</Fixme>
|
||||
|
68
markdown/dev/tutorials/pattern-design/part3/menu/en.md
Normal 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**:
|
||||
|
||||

|
||||
|
||||
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>
|
BIN
markdown/dev/tutorials/pattern-design/part3/menu/options.png
Normal file
After Width: | Height: | Size: 75 KiB |
|
@ -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
|
||||
}
|
||||
```
|
||||
|
|
77
markdown/dev/tutorials/pattern-design/part3/sa/en.md
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 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`.
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: "Testing a design"
|
||||
order: 1250
|
||||
title: Testing your designs
|
||||
order: 70
|
||||
---
|
||||
|
||||
<Fixme> Update this for v3 </Fixme>
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 208 KiB |
Before Width: | Height: | Size: 523 KiB After Width: | Height: | Size: 523 KiB |
Before Width: | Height: | Size: 649 KiB After Width: | Height: | Size: 649 KiB |
8
sites/sde/design/from-scratch/src/bib.mjs
Normal file
|
@ -0,0 +1,8 @@
|
|||
function draftBib({ part }) {
|
||||
return part
|
||||
}
|
||||
|
||||
export const bib = {
|
||||
name: 'fromscratch.bib',
|
||||
draft: draftBib,
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Design } from '@freesewing/core'
|
||||
import { i18n } from '../i18n/index.mjs'
|
||||
import { scratch } from './scratch.mjs'
|
||||
import { bib } from './bib.mjs'
|
||||
|
||||
/*
|
||||
* Create the design
|
||||
|
@ -10,7 +10,7 @@ const FromScratch = new Design({
|
|||
name: 'fromScratch',
|
||||
version: '0.0.1',
|
||||
},
|
||||
parts: [scratch],
|
||||
parts: [bib],
|
||||
})
|
||||
|
||||
export { scratch, FromScratch, i18n }
|
||||
export { bib, FromScratch, i18n }
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
function draftScratch({ part, points, Point, paths, Path }) {
|
||||
/*
|
||||
* Do your magic here
|
||||
*/
|
||||
|
||||
points.topLeft = new Point(0, 0)
|
||||
points.topRight = new Point(200, 0)
|
||||
points.bottomRight = new Point(200, 100)
|
||||
points.bottomLeft = new Point(0, 100)
|
||||
|
||||
points.mid = points.topLeft
|
||||
.shiftFractionTowards(points.bottomRight, 0.5)
|
||||
.addText('fromscratch:whatWillYouCreateToday', 'fill-note center')
|
||||
|
||||
paths.box = new Path()
|
||||
.move(points.topLeft)
|
||||
.line(points.bottomLeft)
|
||||
.line(points.bottomRight)
|
||||
.line(points.topRight)
|
||||
.line(points.topLeft)
|
||||
.close()
|
||||
.addClass('help note')
|
||||
|
||||
return part
|
||||
}
|
||||
|
||||
export const scratch = {
|
||||
name: 'fromscratch.front',
|
||||
draft: draftScratch,
|
||||
}
|
|
@ -24,20 +24,15 @@ export const bib = {
|
|||
units,
|
||||
part,
|
||||
}) => {
|
||||
/*
|
||||
* Head size
|
||||
*/
|
||||
const head = (`head` in measurements ? measurements.head : 360) * options.headSize
|
||||
|
||||
/*
|
||||
* Construct the neck opening
|
||||
*/
|
||||
const target = (head * options.neckRatio) / 4
|
||||
const target = (measurements.head * options.neckRatio) / 4
|
||||
let tweak = 1
|
||||
let delta
|
||||
do {
|
||||
points.right = new Point((tweak * head) / 10, 0)
|
||||
points.bottom = new Point(0, (tweak * 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)
|
||||
|
@ -65,8 +60,8 @@ export const bib = {
|
|||
/*
|
||||
* Construct the outline
|
||||
*/
|
||||
let width = head * options.widthRatio
|
||||
let length = head * options.lengthRatio
|
||||
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)
|
||||
|
@ -215,7 +210,7 @@ export const bib = {
|
|||
.curve(points.tipRightTopCp2, points.tipRightTopCp1, points.tipRightTopStart)
|
||||
.curve(points.edgeTopLeftCp, points.edgeLeftCp, points.edgeLeft)
|
||||
.close()
|
||||
.attr('class', 'fabric')
|
||||
.addClass('fabric')
|
||||
|
||||
/*
|
||||
*
|
||||
|
@ -250,9 +245,8 @@ export const bib = {
|
|||
if (complete)
|
||||
paths.bias = paths.seam
|
||||
.offset(-5)
|
||||
.attr('class', 'note dashed')
|
||||
.attr('data-text', 'tutorial:finishWithBiasTape')
|
||||
.attr('data-text-class', 'center fill-note')
|
||||
.addClass('note dashed')
|
||||
.addText('tutorial:finishWithBiasTape', 'center fill-note')
|
||||
|
||||
/*
|
||||
* Add the title
|
||||
|
|
|
@ -56,12 +56,16 @@ const getMdxConfig = ({ site, jargon }) => ({
|
|||
plainText: ['dot', 'http', 'mermaid'],
|
||||
aliases: {
|
||||
javascript: [
|
||||
'design/from-scratch/src/index.mjs',
|
||||
'design/from-scratch/src/bib.mjs',
|
||||
'design/src/index.mjs',
|
||||
'design/src/part.mjs',
|
||||
'design/src/bib.mjs',
|
||||
'design/src/part.mjs',
|
||||
'index.mjs',
|
||||
'part.mjs',
|
||||
'bib.mjs',
|
||||
'src/index.mjs',
|
||||
'src/bib.mjs',
|
||||
],
|
||||
json: [
|
||||
'200.json',
|
||||
|
|