diff --git a/markdown/dev/tutorials/pattern-design/part1/new-design/en.md b/markdown/dev/tutorials/pattern-design/part1/new-design/en.md index 5f23f75e702..14328c075b3 100644 --- a/markdown/dev/tutorials/pattern-design/part1/new-design/en.md +++ b/markdown/dev/tutorials/pattern-design/part1/new-design/en.md @@ -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 ``` Remove `@next` suffix once v3 is in production -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). @@ -27,17 +29,17 @@ of dependencies that need to be downloaded. -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: ![The FreeSewing development environment](./nd.png) -## Notes + -### 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. + diff --git a/markdown/dev/tutorials/pattern-design/part1/new-design/nd.png b/markdown/dev/tutorials/pattern-design/part1/new-design/nd.png index 0fc70cc753b..1ab95fd64d9 100644 Binary files a/markdown/dev/tutorials/pattern-design/part1/new-design/nd.png and b/markdown/dev/tutorials/pattern-design/part1/new-design/nd.png differ diff --git a/markdown/dev/tutorials/pattern-design/part1/sde/en.md b/markdown/dev/tutorials/pattern-design/part1/sde/en.md new file mode 100644 index 00000000000..d3ae2a77983 --- /dev/null +++ b/markdown/dev/tutorials/pattern-design/part1/sde/en.md @@ -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. + +![The icons in the header of the FreeSewing development environment](./header.png) + +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: + +![Design templates provided by the FreeSewing development environment](./templates.png) + +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. + diff --git a/markdown/dev/tutorials/pattern-design/part1/sde/header.png b/markdown/dev/tutorials/pattern-design/part1/sde/header.png new file mode 100644 index 00000000000..da00179dbe3 Binary files /dev/null and b/markdown/dev/tutorials/pattern-design/part1/sde/header.png differ diff --git a/markdown/dev/tutorials/pattern-design/part1/sde/templates.png b/markdown/dev/tutorials/pattern-design/part1/sde/templates.png new file mode 100644 index 00000000000..1c7fca2b98b Binary files /dev/null and b/markdown/dev/tutorials/pattern-design/part1/sde/templates.png differ diff --git a/markdown/dev/tutorials/pattern-design/part1/structure/en.md b/markdown/dev/tutorials/pattern-design/part1/structure/en.md index 6ae2e34aa14..75b0bab3436 100644 --- a/markdown/dev/tutorials/pattern-design/part1/structure/en.md +++ b/markdown/dev/tutorials/pattern-design/part1/structure/en.md @@ -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: +![Design templates provided by the FreeSewing development environment](./templates.png) -### 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). diff --git a/markdown/dev/tutorials/pattern-design/part1/structure/templates.png b/markdown/dev/tutorials/pattern-design/part1/structure/templates.png new file mode 100644 index 00000000000..1c7fca2b98b Binary files /dev/null and b/markdown/dev/tutorials/pattern-design/part1/structure/templates.png differ diff --git a/markdown/dev/tutorials/pattern-design/part2/adding-measurements/en.md b/markdown/dev/tutorials/pattern-design/part2/adding-measurements/en.md index fde9d8a7a60..7abd178a731 100644 --- a/markdown/dev/tutorials/pattern-design/part2/adding-measurements/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/adding-measurements/en.md @@ -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' ], diff --git a/markdown/dev/tutorials/pattern-design/part2/adding-options/en.md b/markdown/dev/tutorials/pattern-design/part2/adding-options/en.md index 8f86d692e5e..2b7ea3dbab8 100644 --- a/markdown/dev/tutorials/pattern-design/part2/adding-options/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/adding-options/en.md @@ -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. @@ -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: { diff --git a/markdown/dev/tutorials/pattern-design/part2/avoiding-overlap/en.md b/markdown/dev/tutorials/pattern-design/part2/avoiding-overlap/en.md index 4942b78726c..4302fd921c8 100644 --- a/markdown/dev/tutorials/pattern-design/part2/avoiding-overlap/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/avoiding-overlap/en.md @@ -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. - +## 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: + + ```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 +} +``` + + +Once we have our list of points to rotate, we can rotate them. How far? Until the strap no longer overlaps. + + +```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 diff --git a/markdown/dev/tutorials/pattern-design/part2/completing-the-neck-opening/en.md b/markdown/dev/tutorials/pattern-design/part2/completing-the-neck-opening/en.md index 48f03a7856d..0d7e8503b81 100644 --- a/markdown/dev/tutorials/pattern-design/part2/completing-the-neck-opening/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/completing-the-neck-opening/en.md @@ -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() diff --git a/markdown/dev/tutorials/pattern-design/part2/conclusion/en.md b/markdown/dev/tutorials/pattern-design/part2/conclusion/en.md new file mode 100644 index 00000000000..20a73039f32 --- /dev/null +++ b/markdown/dev/tutorials/pattern-design/part2/conclusion/en.md @@ -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. diff --git a/markdown/dev/tutorials/pattern-design/part2/constructing-the-neck-opening/en.md b/markdown/dev/tutorials/pattern-design/part2/constructing-the-neck-opening/en.md index 88d2e46121d..ff71f0c80ad 100644 --- a/markdown/dev/tutorials/pattern-design/part2/constructing-the-neck-opening/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/constructing-the-neck-opening/en.md @@ -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. + + +Many of the methods in the FreeSewing API are *chainable* allowing you +to string them together like in this example. + + When all is said and done, we now have a quarter of our neck opening. The only problem is, we have no guarantee whatsoever that this opening is the correct size. diff --git a/markdown/dev/tutorials/pattern-design/part2/creating-the-closure/en.md b/markdown/dev/tutorials/pattern-design/part2/creating-the-closure/en.md index 704327e0f11..feb31709e2d 100644 --- a/markdown/dev/tutorials/pattern-design/part2/creating-the-closure/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/creating-the-closure/en.md @@ -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 diff --git a/markdown/dev/tutorials/pattern-design/part2/draft-method/en.md b/markdown/dev/tutorials/pattern-design/part2/draft-method/en.md index ec538a9c164..f3f153c3493 100644 --- a/markdown/dev/tutorials/pattern-design/part2/draft-method/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/draft-method/en.md @@ -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: -```design/src/bib.mjs +```src/bib.mjs function draftBib(props) { return props.part @@ -38,7 +38,7 @@ function draftBib(props) { ``` -```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 +Remember: Constructures start with a **C**apital letter + Long story short: These will make it possible for us to draw points and paths easily. So let's go ahead and do that. diff --git a/markdown/dev/tutorials/pattern-design/part2/drawing-the-bib-outline/en.md b/markdown/dev/tutorials/pattern-design/part2/drawing-the-bib-outline/en.md index c8c78fa2185..3c9420e2114 100644 --- a/markdown/dev/tutorials/pattern-design/part2/drawing-the-bib-outline/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/drawing-the-bib-outline/en.md @@ -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. -```design/src/bib.mjs +```src/bib.mjs function draftBib({ Path, Point, diff --git a/markdown/dev/tutorials/pattern-design/part2/drawing-the-straps/en.md b/markdown/dev/tutorials/pattern-design/part2/drawing-the-straps/en.md index 35a7682f973..5a03fa78692 100644 --- a/markdown/dev/tutorials/pattern-design/part2/drawing-the-straps/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/drawing-the-straps/en.md @@ -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", diff --git a/markdown/dev/tutorials/pattern-design/part2/en.md b/markdown/dev/tutorials/pattern-design/part2/en.md index aa77d6d9236..1b895041497 100644 --- a/markdown/dev/tutorials/pattern-design/part2/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/en.md @@ -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. ##### 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. -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: +
- -```design/src/bib.mjs -function draftBib({ - Path, - Point, - paths, - points, - measurements, - options, - macro, - store, - complete, - snippets, - Snippet, - part, -}) { +!['From scratch' template](./fromscratch.png) - /* - * Construct the quarter neck opening - */ - let tweak = 1 - let target = (measurements.head * options.neckRatio) /4 - let delta - do { - points.right = new Point(tweak * measurements.head / 10, 0) - points.bottom = new Point(0, tweak * measurements.head / 12) +!['Tutorial' template](./tutorial.png) - points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right)/2) - points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right)/2) +
- 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 -} -``` -
- -## 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. - -You need Node.js 18 (lts/hydrogen) or higher to use FreeSewing - -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. diff --git a/markdown/dev/tutorials/pattern-design/part2/fitting-the-neck-opening/en.md b/markdown/dev/tutorials/pattern-design/part2/fitting-the-neck-opening/en.md index 26eafa5e768..b606584b8f8 100644 --- a/markdown/dev/tutorials/pattern-design/part2/fitting-the-neck-opening/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/fitting-the-neck-opening/en.md @@ -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 diff --git a/markdown/dev/tutorials/pattern-design/part2/fromscratch.png b/markdown/dev/tutorials/pattern-design/part2/fromscratch.png new file mode 100644 index 00000000000..190f0c1d9e6 Binary files /dev/null and b/markdown/dev/tutorials/pattern-design/part2/fromscratch.png differ diff --git a/markdown/dev/tutorials/pattern-design/part2/our-first-design/en.md b/markdown/dev/tutorials/pattern-design/part2/our-first-design/en.md new file mode 100644 index 00000000000..1a7d1f4faab --- /dev/null +++ b/markdown/dev/tutorials/pattern-design/part2/our-first-design/en.md @@ -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. + + + +It's a convention that constructor names start with an **C**apital letter. + + +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. + + + +Refer to [the design reference documentation](/reference/api/design) for +all details about what you can pass to the Design constructor. + + +That's it. So let's look at where the real action is next: Our first part. + diff --git a/markdown/dev/tutorials/pattern-design/part2/our-first-part/en.md b/markdown/dev/tutorials/pattern-design/part2/our-first-part/en.md index 7f1ced7e4cb..820d3d9a1b3 100644 --- a/markdown/dev/tutorials/pattern-design/part2/our-first-part/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/our-first-part/en.md @@ -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. -#### 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. @@ -80,9 +69,9 @@ This avoids naming conflicts when mixing parts from various designs to create a -#### 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. -## index.mjs - - -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. - - -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. - - - -Refer to [the design reference documentation](/reference/api/design) for -all details about what you can pass to the Design constructor. - diff --git a/markdown/dev/tutorials/pattern-design/part2/rounding-the-corners/en.md b/markdown/dev/tutorials/pattern-design/part2/rounding-the-corners/en.md index 24dabefcf2f..3c85ce94784 100644 --- a/markdown/dev/tutorials/pattern-design/part2/rounding-the-corners/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/rounding-the-corners/en.md @@ -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 diff --git a/markdown/dev/tutorials/pattern-design/part2/shaping-the-straps/en.md b/markdown/dev/tutorials/pattern-design/part2/shaping-the-straps/en.md index 5a53920f769..0467cc28c4b 100644 --- a/markdown/dev/tutorials/pattern-design/part2/shaping-the-straps/en.md +++ b/markdown/dev/tutorials/pattern-design/part2/shaping-the-straps/en.md @@ -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) diff --git a/markdown/dev/tutorials/pattern-design/part2/tutorial.png b/markdown/dev/tutorials/pattern-design/part2/tutorial.png new file mode 100644 index 00000000000..97558cc0068 Binary files /dev/null and b/markdown/dev/tutorials/pattern-design/part2/tutorial.png differ diff --git a/markdown/dev/tutorials/pattern-design/part3/annotations/en.md b/markdown/dev/tutorials/pattern-design/part3/annotations/en.md new file mode 100644 index 00000000000..6212bfb24c3 --- /dev/null +++ b/markdown/dev/tutorials/pattern-design/part3/annotations/en.md @@ -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. + + +You can find all possible snippets in [our documentation](/reference/api/snippet/). + + + +```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 +} +``` + + +## 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). + + +```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 +} +``` + + diff --git a/markdown/dev/tutorials/pattern-design/part3/complete/en.md b/markdown/dev/tutorials/pattern-design/part3/complete/en.md new file mode 100644 index 00000000000..7ab91abf478 --- /dev/null +++ b/markdown/dev/tutorials/pattern-design/part3/complete/en.md @@ -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 + + +```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 +} +``` + + + +And here is the exact same thing, but withe `complete` setting set to `false`: + + +```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 +} +``` + + diff --git a/markdown/dev/tutorials/pattern-design/part3/completing-our-pattern/en.md b/markdown/dev/tutorials/pattern-design/part3/completing-our-pattern/en.md deleted file mode 100644 index 215ecb6cecf..00000000000 --- a/markdown/dev/tutorials/pattern-design/part3/completing-our-pattern/en.md +++ /dev/null @@ -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. - - -You can find all possible snippets in [our documentation](/reference/api/snippet/). - - -## The completed design - -Let's put this and few other things together to complete our design: - - -```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 -} -``` - diff --git a/markdown/dev/tutorials/pattern-design/part3/conclusion/en.md b/markdown/dev/tutorials/pattern-design/part3/conclusion/en.md index f996377551d..6fa98f72658 100644 --- a/markdown/dev/tutorials/pattern-design/part3/conclusion/en.md +++ b/markdown/dev/tutorials/pattern-design/part3/conclusion/en.md @@ -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 diff --git a/markdown/dev/tutorials/pattern-design/part3/en.md b/markdown/dev/tutorials/pattern-design/part3/en.md index f1f82b2017c..d86ca2d878c 100644 --- a/markdown/dev/tutorials/pattern-design/part3/en.md +++ b/markdown/dev/tutorials/pattern-design/part3/en.md @@ -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: - -##### 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. - - -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: - - - -```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 -} -``` - - -## 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. - -You need Node.js 18 (lts/hydrogen) or higher to use FreeSewing - -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. diff --git a/markdown/dev/tutorials/pattern-design/part3/expand/en.md b/markdown/dev/tutorials/pattern-design/part3/expand/en.md new file mode 100644 index 00000000000..c007e0bdab4 --- /dev/null +++ b/markdown/dev/tutorials/pattern-design/part3/expand/en.md @@ -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. diff --git a/markdown/dev/tutorials/pattern-design/part3/flag/en.md b/markdown/dev/tutorials/pattern-design/part3/flag/en.md new file mode 100644 index 00000000000..f8eea72473d --- /dev/null +++ b/markdown/dev/tutorials/pattern-design/part3/flag/en.md @@ -0,0 +1,340 @@ +--- +title: How to communicate to the user +order: 50 +--- + +As a designer, there are times you want to bring something to the attention of +the user. I am not talking about generic information that can go in the +documentation, but rather a message that is tailored specifically to this +pattern, much like this pattern is specifically tailored to the user. + +Doing so is possible with the various `store.flag` methods, and below is +our updated bib making use of this. It's important to realize that things +will look the same here. But if you load this pattern in the development +environment (or on FreeSewing.org for that matter) the user will see this: + +![A message for the user](./flag.png) + +It's a simple example, but I hope it gets the point across. + +Finally, keep in mind that we are now straddling the world of the core library +and frontend integration. These messages won't do anything unless you have a +frontend the shows them. + + +```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 +} +``` + + +Now the first thing you should know is: **core does not care**. We are diff --git a/markdown/dev/tutorials/pattern-design/part3/flag/flag.png b/markdown/dev/tutorials/pattern-design/part3/flag/flag.png new file mode 100644 index 00000000000..fe9719b5bcc Binary files /dev/null and b/markdown/dev/tutorials/pattern-design/part3/flag/flag.png differ diff --git a/markdown/dev/tutorials/pattern-design/part3/i18n/en.md b/markdown/dev/tutorials/pattern-design/part3/i18n/en.md new file mode 100644 index 00000000000..cd97a3adcad --- /dev/null +++ b/markdown/dev/tutorials/pattern-design/part3/i18n/en.md @@ -0,0 +1,7 @@ +--- +title: Supporting translation +order: 80 +--- + +Write this section for v3 + diff --git a/markdown/dev/tutorials/pattern-design/part3/menu/en.md b/markdown/dev/tutorials/pattern-design/part3/menu/en.md new file mode 100644 index 00000000000..a561e784708 --- /dev/null +++ b/markdown/dev/tutorials/pattern-design/part3/menu/en.md @@ -0,0 +1,68 @@ +--- +title: Facilitating frontend integration +order: 60 +--- + +Strictly speaking, this tutorial is about learning to use FreeSewing's core +library to do parametric design, and we made great strides in that regard. + +But FreeSewing is a lot more than its core library, and you might be wondering +how your pattern options magically end up in the development environmnt under +**Design options**: + +![Design options menu](./options.png) + +To make this happen, we add extra information to the options configuration. +You can add anything you want, here is a made-up example: + +```mjs +options: { + shipWith: { + dflt: 'pickUp', + list: ['pickUp', 'post', 'courier'], + menu: 'shipping', + extraNote: 'Pick-up Monday to Friday 10:00, to 19:00' + }, +} +``` + +It's just a silly example, but there's two important take-aways here: + +- You don't have to use options in your design. You can add options for things + that are not about the desing, but that you still want to capture the user's + input for (like shipping preferences in this case). +- You can add extra properties to an option. Each option type has it's required + properties. But you can add more and use them as you see fit. + +Which is exactly what we do at FreeSewing, so I'd like to mention the `menu` one: + +## Setting `menu` on your option + +If you set a `menu` property on your option, the FreeSewing frontend will use +this to organize your various options in a menu structure. + +### Sub menus +You can a nested menu structure with dot-notation. So `style.pockets` will +create a `pockets` submenu under the `style` menu and put your option there. + +### Hiding options + +If your `menu` property holds a falsy value, the option will be hidden from the +menu. + +### Hiding options conditionally + +If the `menu` property of your option holds a function, that function will be called with the following signature: + +```mjs +function( + settings, // The settings provided by the user + mergedOptions, // The user-provided options merged with the defaults +) { + // return menu value here +} +``` + +This is typically used to hide options conditionally. + +Include example diff --git a/markdown/dev/tutorials/pattern-design/part3/menu/options.png b/markdown/dev/tutorials/pattern-design/part3/menu/options.png new file mode 100644 index 00000000000..5f4f9ffd1eb Binary files /dev/null and b/markdown/dev/tutorials/pattern-design/part3/menu/options.png differ diff --git a/markdown/dev/tutorials/pattern-design/part3/paperless/en.md b/markdown/dev/tutorials/pattern-design/part3/paperless/en.md index f96956f3134..573ea0ed6e3 100644 --- a/markdown/dev/tutorials/pattern-design/part3/paperless/en.md +++ b/markdown/dev/tutorials/pattern-design/part3/paperless/en.md @@ -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). + Refer to [the list of macros](/reference/macros/) for more details. - + ```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 } ``` diff --git a/markdown/dev/tutorials/pattern-design/part3/sa/en.md b/markdown/dev/tutorials/pattern-design/part3/sa/en.md new file mode 100644 index 00000000000..d9361de4948 --- /dev/null +++ b/markdown/dev/tutorials/pattern-design/part3/sa/en.md @@ -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: + + +```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 +} +``` + + +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`. diff --git a/markdown/dev/tutorials/pattern-design/part3/testing-our-pattern/en.md b/markdown/dev/tutorials/pattern-design/part3/tests/en.md similarity index 99% rename from markdown/dev/tutorials/pattern-design/part3/testing-our-pattern/en.md rename to markdown/dev/tutorials/pattern-design/part3/tests/en.md index a7472ca8b45..31aa5a77183 100644 --- a/markdown/dev/tutorials/pattern-design/part3/testing-our-pattern/en.md +++ b/markdown/dev/tutorials/pattern-design/part3/tests/en.md @@ -1,6 +1,6 @@ --- -title: "Testing a design" -order: 1250 +title: Testing your designs +order: 70 --- Update this for v3 diff --git a/markdown/dev/tutorials/pattern-design/part3/testing-our-pattern/test-option-lengthratio.png b/markdown/dev/tutorials/pattern-design/part3/tests/test-option-lengthratio.png similarity index 100% rename from markdown/dev/tutorials/pattern-design/part3/testing-our-pattern/test-option-lengthratio.png rename to markdown/dev/tutorials/pattern-design/part3/tests/test-option-lengthratio.png diff --git a/markdown/dev/tutorials/pattern-design/part3/testing-our-pattern/test-option-neckratio.png b/markdown/dev/tutorials/pattern-design/part3/tests/test-option-neckratio.png similarity index 100% rename from markdown/dev/tutorials/pattern-design/part3/testing-our-pattern/test-option-neckratio.png rename to markdown/dev/tutorials/pattern-design/part3/tests/test-option-neckratio.png diff --git a/markdown/dev/tutorials/pattern-design/part3/testing-our-pattern/test-option-widthratio.png b/markdown/dev/tutorials/pattern-design/part3/tests/test-option-widthratio.png similarity index 100% rename from markdown/dev/tutorials/pattern-design/part3/testing-our-pattern/test-option-widthratio.png rename to markdown/dev/tutorials/pattern-design/part3/tests/test-option-widthratio.png diff --git a/sites/sde/design/from-scratch/src/bib.mjs b/sites/sde/design/from-scratch/src/bib.mjs new file mode 100644 index 00000000000..69e74910c6c --- /dev/null +++ b/sites/sde/design/from-scratch/src/bib.mjs @@ -0,0 +1,8 @@ +function draftBib({ part }) { + return part +} + +export const bib = { + name: 'fromscratch.bib', + draft: draftBib, +} diff --git a/sites/sde/design/from-scratch/src/index.mjs b/sites/sde/design/from-scratch/src/index.mjs index 032ffc52c34..af6837e7746 100644 --- a/sites/sde/design/from-scratch/src/index.mjs +++ b/sites/sde/design/from-scratch/src/index.mjs @@ -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 } diff --git a/sites/sde/design/from-scratch/src/scratch.mjs b/sites/sde/design/from-scratch/src/scratch.mjs deleted file mode 100644 index 2237fbcc3d4..00000000000 --- a/sites/sde/design/from-scratch/src/scratch.mjs +++ /dev/null @@ -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, -} diff --git a/sites/sde/design/tutorial/src/bib.mjs b/sites/sde/design/tutorial/src/bib.mjs index a89baab7b5f..d11e1da4144 100644 --- a/sites/sde/design/tutorial/src/bib.mjs +++ b/sites/sde/design/tutorial/src/bib.mjs @@ -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 diff --git a/sites/shared/config/next.mjs b/sites/shared/config/next.mjs index 94dee520f7f..36232fd61b7 100644 --- a/sites/shared/config/next.mjs +++ b/sites/shared/config/next.mjs @@ -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',