diff --git a/config/exceptions.yaml b/config/exceptions.yaml
index 27bec9534ad..ed52c27c108 100644
--- a/config/exceptions.yaml
+++ b/config/exceptions.yaml
@@ -35,6 +35,8 @@ packageJson:
author: SeaZeeZee (https://github.com/SeaZeeZee)
lunetius: &starf
author: Starfetch (https://github.com/starfetch)
+ magde:
+ author: clegganator259 (https://github.com/clegganator259)
new-design:
exports: '!'
bin:
diff --git a/config/software/designs.json b/config/software/designs.json
index f4a64a98798..1188fd8936a 100644
--- a/config/software/designs.json
+++ b/config/software/designs.json
@@ -63,6 +63,13 @@
"difficulty": 2,
"tags": ["accessories", "historical", "bags"]
},
+ "magde": {
+ "description": "A FreeSewing pattern for a bike messenger bag",
+ "code": "clegganator259",
+ "design": "clegganator259",
+ "difficulty": 3,
+ "tags": ["accessories", "bags"]
+ },
"octoplushy": {
"description": "A FreeSewing pattern for an octopus plushy toy",
"code": "Wouter Van Wageningen",
diff --git a/designs/magde/CHANGELOG.md b/designs/magde/CHANGELOG.md
new file mode 100644
index 00000000000..0e0ea3da423
--- /dev/null
+++ b/designs/magde/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Change log for: @freesewing/magde
+
+
+
+This is the **initial release**, and the start of this change log.
+
+> Prior to version 2, FreeSewing was not a JavaScript project.
+> As such, that history is out of scope for this change log.
+
diff --git a/designs/magde/README.md b/designs/magde/README.md
new file mode 100644
index 00000000000..dd08f2012da
--- /dev/null
+++ b/designs/magde/README.md
@@ -0,0 +1,296 @@
+
+
+
+
+
+
+
+
+
+
+
+
+# @freesewing/magde
+
+A FreeSewing pattern for a bike messenger bag
+
+
+
+
+> #### Note: Version 3 is a work in progress
+>
+> We are working on a new major version (v3) but it is not ready for prime-time.
+> For production use, please refer to our v2 packages (the `latest` on NPM)
+> or [the `v2` branch in our monorepo](https://github.com/freesewing/freesewing/tree/v2).
+>
+> We the `main` branch and `next` packages on NPM holds v3 code. But it's alpha for now.
+
+## What am I looking at? π€
+
+This repository is our *monorepo* holding all our NPM designs, plugins, other NPM packages, and (web)sites.
+
+This folder holds: @freesewing/magde
+
+If you're not entirely sure what to do or how to start, type this command:
+
+```
+npm run tips
+```
+
+> If you don't want to set up a dev environment, you can run it in your browser:
+>
+> [](https://gitpod.io/#https://github.com/freesewing/freesewing)
+>
+> We recommend that you fork our repository and then
+> put `gitpod.io/# to start up a browser-based dev environment of your own.
+
+## About FreeSewing π
+
+Where the world of makers and developers collide, that's where you'll find FreeSewing.
+
+If you're a maker, checkout [freesewing.org](https://freesewing.org/) where you can generate
+our sewing patterns adapted to your measurements.
+
+If you're a developer, our documentation is on [freesewing.dev](https://freesewing.dev/).
+Our [core library](https://freesewing.dev/reference/api/) is a *batteries-included* toolbox
+for parametric design of sewing patterns. But we also provide a range
+of [plugins](https://freesewing.dev/reference/plugins/) that further extend the
+functionality of the platform.
+
+If you have NodeJS installed, you can try it right now by running:
+
+```bash
+npx create-freesewing-pattern
+```
+
+Or, consult our getting started guides
+for [Linux](https://freesewing.dev/tutorials/getting-started-linux/),
+[MacOS](https://freesewing.dev/tutorials/getting-started-mac/),
+or [Windows](https://freesewing.dev/tutorials/getting-started-windows/).
+
+We also have a [pattern design tutorial](https://freesewing.dev/tutorials/pattern-design/) that
+walks you through your first parametric design,
+and [a friendly community](https://freesewing.org/community/where/) with
+people who can help you when you get stuck.
+
+## Support FreeSewing: Become a patron π₯°
+
+FreeSewing is an open source project run by a community,
+and financially supported by our patrons.
+
+If you feel what we do is worthwhile, and you can spend a few coind without
+hardship, then you should [join us and become a patron](https://freesewing.org/community/join).
+
+## Links π©βπ»
+
+ - π» Makers website: [freesewing.org](https://freesewing.org)
+ - π» Developers website: [freesewing.dev](https://freesewing.dev)
+ - π¬ Chat: On Discord via [discord.freesewing.org](https://discord.freesewing.org/)
+ - β
Todo list/Kanban board: On Github via [todo.freesewing.org](https://todo.freesewing.org/)
+ - π¦ Twitter: [@freesewing_org](https://twitter.com/freesewing_org)
+ - π· Instagram: [@freesewing_org](https://instagram.com/freesewing_org)
+
+## License: MIT π€
+
+Β© [Joost De Cock](https://github.com/joostdecock).
+See [the license file](https://github.com/freesewing/freesewing/blob/develop/LICENSE) for details.
+
+## Where to get help π€―
+
+Our [chatrooms on Discord](https://chat.freesewing.org/) are the best place to ask questions,
+share your feedback, or just hang out.
+
+If you want to report a problem, please [create an issue](https://github.com/freesewing/freesewing/issues/new).
+
+
+
+## Contributors β¨
+
+Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+
+
+
+
+
+
+
+
+
+
+
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
+
diff --git a/designs/magde/build.mjs b/designs/magde/build.mjs
new file mode 100644
index 00000000000..99ace216bc8
--- /dev/null
+++ b/designs/magde/build.mjs
@@ -0,0 +1,35 @@
+/* This script will build the package with esbuild */
+import esbuild from 'esbuild'
+import pkg from './package.json' assert { type: 'json' }
+
+// Create banner based on package info
+const banner = `/**
+ * ${pkg.name} | v${pkg.version}
+ * ${pkg.description}
+ * (c) ${new Date().getFullYear()} ${pkg.author}
+ * @license ${pkg.license}
+ */`
+
+// Shared esbuild options
+const options = {
+ banner: { js: banner },
+ bundle: true,
+ entryPoints: ['src/index.mjs'],
+ format: 'esm',
+ outfile: 'dist/index.mjs',
+ external: ['@freesewing'],
+ metafile: process.env.VERBOSE ? true : false,
+ minify: process.env.NO_MINIFY ? false : true,
+ sourcemap: true,
+}
+
+// Let esbuild generate the build
+const build = async () => {
+ const result = await esbuild.build(options).catch(() => process.exit(1))
+
+ if (process.env.VERBOSE) {
+ const info = await esbuild.analyzeMetafile(result.metafile)
+ console.log(info)
+ }
+}
+build()
diff --git a/designs/magde/data.mjs b/designs/magde/data.mjs
new file mode 100644
index 00000000000..70f691b1d70
--- /dev/null
+++ b/designs/magde/data.mjs
@@ -0,0 +1,4 @@
+// This file is auto-generated | All changes you make will be overwritten.
+export const name = '@freesewing/magde'
+export const version = '3.0.0-alpha.8'
+export const data = { name, version }
diff --git a/designs/magde/package.json b/designs/magde/package.json
new file mode 100644
index 00000000000..4c7d9edb919
--- /dev/null
+++ b/designs/magde/package.json
@@ -0,0 +1,69 @@
+{
+ "name": "@freesewing/magde",
+ "version": "3.0.0-alpha.8",
+ "description": "A FreeSewing pattern for a bike messenger bag",
+ "author": "clegganator259 (https://github.com/clegganator259)",
+ "homepage": "https://freesewing.org/",
+ "repository": "github:freesewing/freesewing",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/freesewing/freesewing/issues"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://freesewing.org/patrons/join"
+ },
+ "keywords": [
+ "freesewing",
+ "design",
+ "diy",
+ "fashion",
+ "made to measure",
+ "parametric design",
+ "pattern",
+ "sewing",
+ "sewing pattern"
+ ],
+ "type": "module",
+ "module": "dist/index.mjs",
+ "exports": {
+ ".": "./dist/index.mjs"
+ },
+ "scripts": {
+ "build": "node build.mjs",
+ "clean": "rimraf dist",
+ "mbuild": "NO_MINIFY=1 node build.mjs",
+ "symlink": "mkdir -p ./node_modules/@freesewing && cd ./node_modules/@freesewing && ln -s -f ../../../* . && cd -",
+ "test": "npx mocha tests/*.test.mjs",
+ "vbuild": "VERBOSE=1 node build.mjs",
+ "lab": "cd ../../sites/lab && yarn start",
+ "tips": "node ../../scripts/help.mjs",
+ "lint": "npx eslint 'src/**' 'tests/*.mjs'",
+ "prettier": "npx prettier --write 'src/*.mjs' 'tests/*.mjs'",
+ "testci": "npx mocha tests/*.test.mjs --reporter ../../tests/reporters/terse.js",
+ "cibuild_step5": "node build.mjs",
+ "wbuild": "node build.mjs",
+ "wcibuild_step5": "node build.mjs"
+ },
+ "peerDependencies": {
+ "@freesewing/core": "3.0.0-alpha.8",
+ "@freesewing/plugin-bundle": "3.0.0-alpha.8"
+ },
+ "dependencies": {},
+ "devDependencies": {
+ "mocha": "10.0.0",
+ "chai": "4.2.0"
+ },
+ "files": [
+ "dist/*",
+ "README.md"
+ ],
+ "publishConfig": {
+ "access": "public",
+ "tag": "next"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=8"
+ }
+}
diff --git a/designs/magde/src/backPanel.mjs b/designs/magde/src/backPanel.mjs
new file mode 100644
index 00000000000..2d4e5b4bbfb
--- /dev/null
+++ b/designs/magde/src/backPanel.mjs
@@ -0,0 +1,130 @@
+import { pluginBundle } from '@freesewing/plugin-bundle'
+
+function draftBackPanel({
+ options,
+ Point,
+ Path,
+ points,
+ paths,
+ complete,
+ sa,
+ paperless,
+ macro,
+ part,
+}) {
+ // Width is halved as this is cut on a fold
+ const width = (options.size * 500) / 2
+ const height = options.size * 300
+ const depth = options.size * 150
+ const taperWidth = width * options.taperRatio
+ points.origin = new Point(0, 0)
+ points.topRightCorner = new Point(width, 0)
+ points.bottomRightCorner = new Point(taperWidth + depth, height)
+ points.baseFlapBackRight = new Point(taperWidth, height)
+ points.baseFlapFrontRight = new Point(taperWidth, height + depth)
+ points.bottomLeftCorner = new Point(0, height + depth)
+
+ paths.seam = new Path()
+ .move(points.bottomLeftCorner)
+ .line(points.baseFlapFrontRight)
+ .line(points.baseFlapBackRight)
+ .line(points.bottomRightCorner)
+ .line(points.topRightCorner)
+ .line(points.origin)
+ .close()
+ .attr('class', 'fabric')
+
+ // Complete?
+ if (complete) {
+ macro('cutonfold', {
+ from: points.origin,
+ to: points.bottomLeftCorner,
+ })
+
+ macro('title', {
+ at: new Point(taperWidth / 2, height / 2),
+ title: 'Back Panel',
+ nr: '1',
+ })
+
+ paths.foldLine = new Path()
+ .move(new Point(0, points.baseFlapBackRight.y))
+ .line(points.baseFlapBackRight)
+ .setClass('dotted note')
+ .addText('Base Fold', 'center note')
+
+ if (sa) {
+ var bottomSeam = new Path()
+ .move(points.bottomLeftCorner)
+ .line(points.baseFlapFrontRight)
+ .addText('Flat fell', 'center text-sm')
+ .offset(2 * sa)
+ var sideFlapSeam = new Path()
+ .move(points.baseFlapFrontRight)
+ .line(points.baseFlapBackRight)
+ .line(points.bottomRightCorner)
+ .offset(2 * sa)
+ .trim()
+ var sideSeam = new Path()
+ .move(points.bottomRightCorner)
+ .line(points.topRightCorner)
+ .offset(sa)
+ var topSeam = new Path()
+ .move(points.topRightCorner)
+ .line(points.origin)
+ .offset(2 * sa)
+ paths.sa = bottomSeam
+ .join(sideFlapSeam)
+ .join(sideSeam)
+ .join(topSeam)
+ .trim()
+ .setClass('fabric sa')
+ }
+ }
+
+ // Paperless?
+ if (paperless) {
+ macro('hd', {
+ from: points.origin,
+ to: points.topRightCorner,
+ y: -(2 * sa + 15),
+ })
+ macro('hd', {
+ from: points.topRightCorner,
+ to: points.bottomRightCorner,
+ y: -(2 * sa + 15),
+ })
+ macro('hd', {
+ from: points.bottomRightCorner,
+ to: points.baseFlapBackRight,
+ y: points.baseFlapFrontRight.y + 2 * sa + 15,
+ })
+ macro('hd', {
+ from: points.baseFlapBackRight,
+ to: points.origin,
+ y: points.baseFlapFrontRight.y + 2 * sa + 15,
+ })
+ macro('vd', {
+ from: points.origin,
+ to: points.bottomRightCorner,
+ x: points.bottomRightCorner.x + 2 * sa + 15,
+ })
+ macro('vd', {
+ from: points.bottomRightCorner,
+ to: points.baseFlapFrontRight,
+ x: points.bottomRightCorner.x + 2 * sa + 15,
+ })
+ }
+
+ return part
+}
+
+export const backPanel = {
+ name: 'backPanel',
+ options: {
+ size: { pct: 100, min: 15, max: 200, menu: 'style' },
+ taperRatio: { pct: 60, min: 50, max: 100, menu: 'style' },
+ },
+ plugins: [pluginBundle],
+ draft: draftBackPanel,
+}
diff --git a/designs/magde/src/bodyLiner.mjs b/designs/magde/src/bodyLiner.mjs
new file mode 100644
index 00000000000..500ff540ce7
--- /dev/null
+++ b/designs/magde/src/bodyLiner.mjs
@@ -0,0 +1,212 @@
+import { pluginBundle } from '@freesewing/plugin-bundle'
+
+function draftBodyLiner({
+ options,
+ Point,
+ Path,
+ points,
+ paths,
+ complete,
+ sa,
+ paperless,
+ macro,
+ part,
+}) {
+ // Width is halved as this is cut on a fold
+ const width = (options.size * 500) / 2
+ const height = options.size * 300
+ const depth = options.size * 150
+ const taperWidth = width * options.taperRatio
+ const openingWidth = taperWidth * options.openingRatio
+ const openingHeight = height * options.openingRatio
+ const frontFlapHeight = height * options.flapHeightRatio
+ const frontFlapWidth = taperWidth * 0.8
+ points.origin = new Point(0, 0)
+ points.bodyTopRight = new Point(width, 0)
+ points.sideFlapFrontPoint = new Point(taperWidth + depth, height)
+ points.sideFlapBackPoint = new Point(taperWidth, height)
+ points.baseFrontRight = new Point(taperWidth, height + depth)
+ points.frontFlapMidRight = new Point(width, 2 * height + depth)
+ points.falseFrontFlapRight = new Point(frontFlapWidth, frontFlapHeight + 2 * height + depth)
+ points.frontFlapPeakRight = points.frontFlapMidRight.shiftFractionTowards(
+ points.falseFrontFlapRight,
+ 0.2
+ )
+ points.frontOpeningRight = new Point(openingWidth, openingHeight + height + depth)
+ points.frontOpeningLeft = new Point(0, openingHeight + height + depth)
+
+ paths.seam = new Path()
+ .move(points.frontOpeningLeft)
+ .line(points.frontOpeningRight)
+ .line(points.frontFlapPeakRight)
+ .line(points.frontFlapMidRight)
+ .line(points.baseFrontRight)
+ .line(points.sideFlapBackPoint)
+ .line(points.sideFlapFrontPoint)
+ .line(points.bodyTopRight)
+ .line(points.origin)
+ .close()
+ .attr('class', 'lining')
+
+ // Complete?
+ if (complete) {
+ macro('cutonfold', {
+ from: points.origin,
+ to: points.frontOpeningLeft,
+ })
+ points.label = new Point(taperWidth / 2, height / 2)
+ macro('title', {
+ at: points.label,
+ nr: '5',
+ title: 'Body Lining',
+ })
+
+ paths.foldLineSideFlap = new Path()
+ .move(points.bodyTopRight)
+ .line(points.sideFlapBackPoint)
+ .setClass('dotted note')
+ .addText('SideFlap Fold', 'center note')
+
+ paths.foldLineBaseBack = new Path()
+ .move(new Point(0, points.sideFlapBackPoint.y))
+ .line(points.sideFlapBackPoint)
+ .setClass('dotted note')
+ .addText('Base Fold', 'center note')
+ paths.foldLineBaseFront = new Path()
+ .move(new Point(0, points.baseFrontRight.y))
+ .line(points.baseFrontRight)
+ .setClass('dotted note')
+ .addText('Base Fold', 'center note')
+
+ if (sa) {
+ points.temp = points.baseFrontRight.shiftFractionTowards(points.sideFlapBackPoint, -0.2)
+ var baseFrenchSeams = new Path()
+ .move(points.baseFrontRight)
+ .line(points.sideFlapBackPoint)
+ .line(points.sideFlapFrontPoint)
+ .offset(2 * sa)
+ .addText('French Seam')
+ var openingBindingTop = new Path()
+ .move(points.frontOpeningLeft)
+ .line(points.frontOpeningRight)
+ .offset(-sa)
+ var openingBindingSideRough = new Path()
+ .move(points.frontOpeningRight)
+ .line(points.frontFlapPeakRight)
+ .offset(-sa)
+ var openingBinding = openingBindingTop
+ .join(
+ new Path()
+ .move(openingBindingSideRough.start())
+ .line(
+ openingBindingSideRough.intersects(
+ new Path()
+ .move(points.frontFlapPeakRight)
+ .line(points.frontFlapMidRight)
+ .line(points.baseFrontRight)
+ )[0]
+ )
+ )
+ .addText('Bind with seamtape', 'center text-sm')
+ .setClass('lining sa')
+ var frontSideSa = new Path()
+ .move(points.frontFlapMidRight)
+ .line(points.baseFrontRight)
+ .offset(sa)
+
+ var sideFlapSa = new Path()
+ .move(points.sideFlapFrontPoint)
+ .line(points.bodyTopRight)
+ .offset(sa)
+
+ paths.openingBinding = openingBinding
+ paths.baseSa = new Path()
+ .move(points.baseFrontRight)
+ .join(baseFrenchSeams)
+ .addText('French Seam', 'center text-lg')
+ .join(sideFlapSa)
+ .join(
+ new Path()
+ .move(points.bodyTopRight)
+ .line(points.origin)
+ .offset(2 * sa)
+ )
+ .trim()
+ .setClass('lining sa')
+ paths.frontAndTopSa = new Path()
+ .move(points.frontFlapMidRight)
+ .line(frontSideSa.start())
+ .line(frontSideSa.intersects(paths.baseSa)[0])
+ .trim()
+ .setClass('lining sa')
+ }
+ }
+
+ // Paperless?
+ if (paperless) {
+ macro('hd', {
+ from: points.bodyTopRight,
+ to: points.sideFlapFrontPoint,
+ y: points.bodyTopRight.y - 2 * sa - 15,
+ })
+ macro('hd', {
+ from: points.origin,
+ to: points.sideFlapFrontPoint,
+ y: points.bodyTopRight.y - 2 * sa - 25,
+ })
+ macro('hd', {
+ from: points.sideFlapBackPoint,
+ to: points.sideFlapFrontPoint,
+ y: points.sideFlapBackPoint.y - 15,
+ })
+ macro('hd', {
+ from: points.frontOpeningLeft,
+ to: points.frontOpeningRight,
+ y: points.frontOpeningRight.y + 2 * sa + 15,
+ })
+ macro('hd', {
+ from: points.frontOpeningLeft,
+ to: points.frontFlapPeakRight,
+ y: points.frontFlapPeakRight.y + 2 * sa,
+ })
+ macro('hd', {
+ from: points.frontFlapPeakRight,
+ to: points.frontFlapMidRight,
+ y: points.frontFlapPeakRight.y + 2 * sa,
+ })
+ macro('vd', {
+ from: points.bodyTopRight,
+ to: points.sideFlapFrontPoint,
+ x: points.sideFlapFrontPoint.x + 2 * sa,
+ })
+ macro('vd', {
+ from: points.sideFlapFrontPoint,
+ to: points.baseFrontRight,
+ x: points.sideFlapFrontPoint.x + 2 * sa,
+ })
+ macro('vd', {
+ from: points.baseFrontRight,
+ to: points.frontFlapMidRight,
+ x: points.sideFlapFrontPoint.x + 2 * sa,
+ })
+ macro('vd', {
+ from: points.frontFlapMidRight,
+ to: points.frontFlapPeakRight,
+ x: points.sideFlapFrontPoint.x + 2 * sa,
+ })
+ }
+
+ return part
+}
+
+export const bodyLiner = {
+ name: 'bodyLiner',
+ options: {
+ size: { pct: 100, min: 15, max: 200, menu: 'style' },
+ taperRatio: { pct: 60, min: 50, max: 100, menu: 'style' },
+ flapHeightRatio: { pct: 83, min: 60, max: 100, menu: 'style' },
+ openingRatio: { pct: 66, min: 30, max: 90, menu: 'style' },
+ },
+ plugins: [pluginBundle],
+ draft: draftBodyLiner,
+}
diff --git a/designs/magde/src/frontOrganiserBase.mjs b/designs/magde/src/frontOrganiserBase.mjs
new file mode 100644
index 00000000000..48d7ef23432
--- /dev/null
+++ b/designs/magde/src/frontOrganiserBase.mjs
@@ -0,0 +1,96 @@
+import { pluginBundle } from '@freesewing/plugin-bundle'
+
+function draftFrontOrganiserBase({
+ options,
+ Point,
+ Path,
+ Snippet,
+ points,
+ paths,
+ complete,
+ sa,
+ snippets,
+ paperless,
+ macro,
+ part,
+}) {
+ // Width is *not halved* as this is not cut on fold unlike most other pattern
+ // pieces
+ const width = options.size * 500
+ const height = options.size * 300
+ const taperWidth = width * options.taperRatio
+ const openingWidth = taperWidth * options.openingRatio
+ const openingHeight = height * options.openingRatio
+
+ points.origin = new Point(0, 0)
+ points.bottomRight = new Point(openingWidth, openingHeight)
+ points.bottomLeft = new Point(0, openingHeight)
+ points.topRight = new Point(openingWidth, 0)
+
+ paths.seam = new Path()
+ .move(points.origin)
+ .line(points.bottomLeft)
+ .line(points.bottomRight)
+ .line(points.topRight)
+ .line(points.origin)
+ .close()
+ .setClass('fabric')
+
+ // Complete?
+ if (complete) {
+ points.label = new Point(openingWidth / 4, openingHeight / 2)
+ macro('title', {
+ at: points.label,
+ title: 'Organiser Base',
+ nr: '7',
+ })
+
+ if (sa) {
+ paths.sa = paths.seam.offset(2 * sa).setClass('fabric sa')
+ paths.leftHem = new Path()
+ .move(points.bottomLeft)
+ .line(points.origin)
+ .addText('Rolled Hem', 'center')
+ paths.RighHem = new Path()
+ .move(points.topRight)
+ .line(points.bottomRight)
+ .addText('Rolled Hem', 'center')
+ paths.topHem = new Path()
+ .move(points.origin)
+ .line(points.topRight)
+ .addText('Rolled Hem', 'center')
+ paths.bottomSeam = new Path()
+ .move(points.bottomRight)
+ .line(points.bottomLeft)
+ .addText('Baste into Front Panel SA', 'center')
+ }
+ }
+
+ // Paperless?
+ if (paperless) {
+ macro('hd', {
+ from: points.origin,
+ to: points.bottomRight,
+ y: points.origin.y - 2 * sa - 15,
+ })
+ macro('vd', {
+ from: points.origin,
+ to: points.bottomRight,
+ x: points.bottomRight.x + 2 * sa + 15,
+ })
+ }
+
+ return part
+}
+
+export const frontOrganiserBase = {
+ name: 'frontOrganiserBase',
+ options: {
+ size: { pct: 100, min: 15, max: 200, menu: 'style' },
+ taperRatio: { pct: 60, min: 50, max: 100, menu: 'style' },
+ flapHeightRatio: { pct: 83, min: 60, max: 100, menu: 'style' },
+ openingRatio: { pct: 66, min: 30, max: 90, menu: 'style' },
+ },
+ plugins: [pluginBundle],
+ draft: draftFrontOrganiserBase,
+}
diff --git a/designs/magde/src/frontOrganiserFront.mjs b/designs/magde/src/frontOrganiserFront.mjs
new file mode 100644
index 00000000000..6a92505543b
--- /dev/null
+++ b/designs/magde/src/frontOrganiserFront.mjs
@@ -0,0 +1,119 @@
+import { pluginBundle } from '@freesewing/plugin-bundle'
+
+function draftFrontOrganiserFront({
+ options,
+ Point,
+ Path,
+ Snippet,
+ points,
+ paths,
+ complete,
+ sa,
+ snippets,
+ paperless,
+ macro,
+ part,
+}) {
+ // Width is *not halved* as this is not cut on fold unlike most other pattern
+ // pieces
+ const width = options.size * 500
+ const height = options.size * 300
+ const taperWidth = width * options.taperRatio
+ const openingWidth = taperWidth * options.openingRatio
+ const openingHeight = height * options.openingRatio
+ const penInsertXCoords = [
+ openingWidth / 2,
+ (5 * openingWidth) / 8,
+ (6 * openingWidth) / 8,
+ (7 * openingWidth) / 8,
+ ]
+
+ points.origin = new Point(0, 0)
+ points.bottomRight = new Point(openingWidth, openingHeight * 0.66)
+ points.bottomLeft = new Point(0, openingHeight * 0.66)
+ points.topRight = new Point(openingWidth, 0)
+
+ paths.seam = new Path()
+ .move(points.origin)
+ .line(points.bottomLeft)
+ .line(points.bottomRight)
+ .line(points.topRight)
+ .line(points.origin)
+ .close()
+ .setClass('fabric')
+
+ // Complete?
+ if (complete) {
+ points.label = new Point(openingWidth * 0.1, openingHeight / 2)
+ macro('title', {
+ at: points.label,
+ title: 'Organiser Front',
+ nr: '8',
+ })
+
+ if (sa) {
+ penInsertXCoords.forEach(function (xVal, i) {
+ paths[`penInsert${i}`] = new Path()
+ .move(new Point(xVal, 0))
+ .line(new Point(xVal, points.bottomRight.y))
+ .setClass('note sa')
+ .addText('Divider Seam', 'center text-color-note')
+ })
+ paths.sa = paths.seam.offset(2 * sa).setClass('fabric sa')
+ paths.leftHem = new Path()
+ .move(points.bottomLeft)
+ .line(points.origin)
+ .addText('Rolled Hem', 'center')
+ paths.RighHem = new Path()
+ .move(points.topRight)
+ .line(points.bottomRight)
+ .addText('Rolled Hem', 'center')
+ paths.topHem = new Path()
+ .move(points.origin)
+ .line(points.topRight)
+ .addText('Rolled Hem', 'center')
+ paths.bottomSeam = new Path()
+ .move(points.bottomRight)
+ .line(points.bottomLeft)
+ .addText('Baste into Front Panel SA', 'center')
+ }
+ }
+
+ // Paperless?
+ if (paperless) {
+ var verticalSeamsXCoords = [0].concat(penInsertXCoords).concat([openingWidth])
+ for (var i = 0; i < verticalSeamsXCoords.length - 1; i++) {
+ var thisX = verticalSeamsXCoords[i]
+ var nextX = verticalSeamsXCoords[i + 1]
+ macro('hd', {
+ from: new Point(thisX, 0),
+ to: new Point(nextX, 0),
+ y: points.bottomRight.y / 4,
+ })
+ }
+ macro('hd', {
+ from: points.origin,
+ to: points.bottomRight,
+ y: points.origin.y - 2 * sa - 15,
+ })
+ macro('vd', {
+ from: points.origin,
+ to: points.bottomRight,
+ x: points.bottomRight.x + 2 * sa + 15,
+ })
+ }
+
+ return part
+}
+
+export const frontOrganiserFront = {
+ name: 'frontOrganiserFront',
+ options: {
+ size: { pct: 100, min: 15, max: 200, menu: 'style' },
+ taperRatio: { pct: 60, min: 50, max: 100, menu: 'style' },
+ flapHeightRatio: { pct: 83, min: 60, max: 100, menu: 'style' },
+ openingRatio: { pct: 66, min: 30, max: 90, menu: 'style' },
+ },
+ plugins: [pluginBundle],
+ draft: draftFrontOrganiserFront,
+}
diff --git a/designs/magde/src/frontPanel.mjs b/designs/magde/src/frontPanel.mjs
new file mode 100644
index 00000000000..8644f207ad5
--- /dev/null
+++ b/designs/magde/src/frontPanel.mjs
@@ -0,0 +1,187 @@
+import { pluginBundle } from '@freesewing/plugin-bundle'
+
+function draftFrontPanel({
+ options,
+ Point,
+ Path,
+ Snippet,
+ points,
+ paths,
+ complete,
+ sa,
+ snippets,
+ paperless,
+ macro,
+ part,
+}) {
+ // Width is halved as this is cut on a fold
+ const width = (options.size * 500) / 2
+ const height = options.size * 300
+ const taperWidth = width * options.taperRatio
+ const openingWidth = taperWidth * options.openingRatio
+ const openingHeight = height * options.openingRatio
+ const frontFlapHeight = height * options.flapHeightRatio
+ const frontFlapWidth = taperWidth * 0.8
+ points.origin = new Point(0, 0)
+ points.frontFlapMidRight = new Point(width, height)
+ points.frontFlapBottom = new Point(taperWidth, 0)
+ points.falseFrontFlapRight = new Point(frontFlapWidth, frontFlapHeight + height)
+ points.frontFlapPeakRight = points.frontFlapMidRight.shiftFractionTowards(
+ points.falseFrontFlapRight,
+ 0.2
+ )
+ points.frontOpeningRight = new Point(openingWidth, openingHeight)
+ points.frontOpeningLeft = new Point(0, openingHeight)
+
+ paths.seam = new Path()
+ .move(points.frontOpeningLeft)
+ .line(points.frontOpeningRight)
+ .line(points.frontFlapPeakRight)
+ .line(points.frontFlapMidRight)
+ .line(points.frontFlapBottom)
+ .line(points.origin)
+ .close()
+ .attr('class', 'fabric')
+
+ // Complete?
+ if (complete) {
+ macro('cutonfold', {
+ from: points.origin,
+ to: points.frontOpeningLeft,
+ })
+
+ points.label = new Point(openingWidth / 4, openingHeight / 2)
+ macro('title', {
+ at: points.label,
+ title: 'Front Panel',
+ nr: '2',
+ })
+
+ paths.FrontOrganiserAlignment = new Path()
+ .move(new Point(points.frontOpeningRight.x, 0))
+ .line(points.frontOpeningRight)
+ .setClass('note dotted')
+ paths.velcro = paths.FrontOrganiserAlignment.join(
+ new Path()
+ .move(new Point(points.falseFrontFlapRight.x, points.frontOpeningRight.y))
+ .line(new Point(points.falseFrontFlapRight.x, 0))
+ ).setClass('various fill-various')
+ var midVelcroX = (points.frontOpeningRight.x + points.falseFrontFlapRight.x) / 2
+ paths.velcroLabel = new Path()
+ .move(new Point(midVelcroX, points.frontOpeningRight.y))
+ .line(new Point(midVelcroX, 0))
+ .addText('Velcro loop', 'note center')
+ .addClass('no-stroke')
+ snippets.webbingNotch = new Snippet('notch', new Point(midVelcroX, 0))
+
+ if (sa) {
+ var saPath = new Path()
+ .move(points.frontFlapMidRight)
+ .line(points.frontFlapBottom)
+ .line(points.origin)
+ .offset(sa * 2)
+ paths.sa = new Path().move(points.frontFlapMidRight).join(saPath).setClass('fabric sa')
+ var openingBindingTop = new Path()
+ .move(points.frontOpeningLeft)
+ .line(points.frontOpeningRight)
+ .offset(-sa)
+ var openingBindingSideRough = new Path()
+ .move(points.frontOpeningRight)
+ .line(points.frontFlapPeakRight)
+ .offset(-sa)
+ paths.openingBinding = openingBindingTop
+ .join(
+ new Path()
+ .move(openingBindingSideRough.start())
+ .line(
+ openingBindingSideRough.intersects(
+ new Path()
+ .move(points.frontFlapPeakRight)
+ .line(points.frontFlapMidRight)
+ .line(points.frontFlapBottom)
+ )[0]
+ )
+ )
+ .addText('Bind with seamtape', 'center text-sm')
+ .setClass('fabric sa')
+ var lidBindingRough = new Path()
+ .move(points.frontFlapPeakRight)
+ .line(points.frontFlapMidRight)
+ .offset(-sa)
+ lidBindingRough = new Path()
+ .move(lidBindingRough.start())
+ .line(lidBindingRough.start().shiftFractionTowards(lidBindingRough.end(), 3))
+ paths.lidBinding = new Path()
+ .move(
+ lidBindingRough.intersects(
+ new Path().move(points.frontOpeningRight).line(points.frontFlapPeakRight)
+ )[0]
+ )
+ .line(
+ lidBindingRough.intersects(
+ new Path().move(points.frontFlapMidRight).line(points.frontFlapBottom)
+ )[0]
+ )
+ .addText('Bind with lid', 'center text-sm')
+ .setClass('fabric sa')
+ }
+ }
+
+ // Paperless?
+ if (paperless) {
+ macro('hd', {
+ from: points.origin,
+ to: points.frontFlapBottom,
+ y: points.origin.y - 2 * sa - 15,
+ })
+ macro('hd', {
+ from: points.frontFlapBottom,
+ to: points.frontFlapMidRight,
+ y: points.origin.y - 2 * sa - 15,
+ })
+ macro('hd', {
+ from: points.frontFlapMidRight,
+ to: points.frontFlapPeakRight,
+ y: points.frontFlapPeakRight.y + 2 * sa + 15,
+ })
+ macro('hd', {
+ from: points.frontFlapPeakRight,
+ to: points.frontOpeningRight,
+ y: points.frontFlapPeakRight.y + 2 * sa + 15,
+ })
+ macro('hd', {
+ from: points.frontOpeningRight,
+ to: points.frontOpeningLeft,
+ y: points.frontFlapPeakRight.y + 2 * sa + 15,
+ })
+ macro('vd', {
+ from: points.origin,
+ to: points.frontOpeningLeft,
+ x: points.origin.x - 15,
+ })
+ macro('vd', {
+ from: points.origin,
+ to: points.frontFlapMidRight,
+ x: points.frontFlapMidRight.x + 15,
+ })
+ macro('vd', {
+ from: points.frontFlapMidRight,
+ to: points.frontFlapPeakRight,
+ x: points.frontFlapMidRight.x + 15,
+ })
+ }
+
+ return part
+}
+
+export const frontPanel = {
+ name: 'frontPanel',
+ options: {
+ size: { pct: 100, min: 15, max: 200, menu: 'style' },
+ taperRatio: { pct: 60, min: 50, max: 100, menu: 'style' },
+ flapHeightRatio: { pct: 83, min: 60, max: 100, menu: 'style' },
+ openingRatio: { pct: 66, min: 30, max: 90, menu: 'style' },
+ },
+ plugins: [pluginBundle],
+ draft: draftFrontPanel,
+}
diff --git a/designs/magde/src/index.mjs b/designs/magde/src/index.mjs
new file mode 100644
index 00000000000..908ade9b737
--- /dev/null
+++ b/designs/magde/src/index.mjs
@@ -0,0 +1,35 @@
+//
+
+import { Design } from '@freesewing/core'
+import { data } from '../data.mjs'
+// Parts
+import { bodyLiner } from './bodyLiner.mjs'
+import { frontPanel } from './frontPanel.mjs'
+import { frontOrganiserBase } from './frontOrganiserBase.mjs'
+import { frontOrganiserFront } from './frontOrganiserFront.mjs'
+import { backPanel } from './backPanel.mjs'
+import { lidOnePiece } from './lidOnePiece.mjs'
+import { twoPieceLidTop } from './twoPieceLidTop.mjs'
+import { twoPieceLidBottom } from './twoPieceLidBottom.mjs'
+import { lidLiner } from './lidLiner.mjs'
+import { strapAttachments } from './strapAttachments.mjs'
+
+// Create new design
+const Magde = new Design({
+ data,
+ parts: [
+ backPanel,
+ frontPanel,
+ frontOrganiserBase,
+ frontOrganiserFront,
+ lidOnePiece,
+ twoPieceLidTop,
+ twoPieceLidBottom,
+ strapAttachments,
+ bodyLiner,
+ lidLiner,
+ ],
+})
+
+// Named exports
+export { bodyLiner, Magde }
diff --git a/designs/magde/src/lidLiner.mjs b/designs/magde/src/lidLiner.mjs
new file mode 100644
index 00000000000..01a60197fc1
--- /dev/null
+++ b/designs/magde/src/lidLiner.mjs
@@ -0,0 +1,124 @@
+import { pluginBundle } from '@freesewing/plugin-bundle'
+
+function draftLidLiner({
+ options,
+ Point,
+ Path,
+ points,
+ paths,
+ complete,
+ sa,
+ paperless,
+ macro,
+ part,
+}) {
+ // Width is halved as this is cut on a fold
+ const width = (options.size * 500) / 2
+ const height = options.size * 300
+ const taperWidth = width * options.taperRatio
+ const lidFlapHeight = height * options.flapHeightRatio
+ const lidFlapWidth = taperWidth * 0.8
+ points.origin = new Point(0, 0)
+ points.lidTopRight = new Point(lidFlapWidth, 0)
+ points.lidBottomRight = new Point(width, lidFlapHeight)
+ points.lidBottomLeft = new Point(0, lidFlapHeight)
+
+ paths.seam = new Path()
+ .move(points.lidBottomLeft)
+ .line(points.lidBottomRight)
+ .line(points.lidTopRight)
+ .line(points.origin)
+ .close()
+ .attr('class', 'lining')
+
+ // Complete?
+ if (complete) {
+ macro('cutonfold', {
+ from: points.origin,
+ to: points.lidBottomLeft,
+ })
+
+ points.label = new Point(lidFlapWidth / 2, lidFlapHeight / 2)
+
+ macro('title', {
+ at: points.label,
+ title: 'Lid Liner',
+ nr: '6',
+ })
+
+ points.velcroPoint = new Path()
+ .move(new Point(0, 0.1 * height))
+ .line(new Point(points.lidBottomRight.x, 0.1 * height))
+ .intersects(new Path().move(points.lidTopRight).line(points.lidBottomRight))[0]
+ paths.velcro = new Path()
+ .move(new Point(0, points.velcroPoint.y))
+ .line(points.velcroPoint)
+ .line(points.lidTopRight)
+ .line(points.origin)
+ .close()
+ .setClass('various fill-various')
+ paths.velcroLabel = new Path()
+ .move(new Point(0, points.velcroPoint.y / 1.5))
+ .line(new Point(points.velcroPoint.x, points.velcroPoint.y / 1.5))
+ .addText('Velcro hook', 'text-note center')
+ .addClass('no-stroke')
+ if (sa) {
+ paths.sa = new Path()
+ .move(points.lidBottomLeft)
+ .line(points.lidBottomRight)
+ .offset(2 * sa)
+ .addText('Flat fell seam', 'left')
+ .line(points.lidBottomRight)
+ .setClass('lining sa')
+ }
+ var topBindingLineRough = new Path().move(points.lidTopRight).line(points.origin).offset(-sa)
+ var sideBindingLineRough = new Path()
+ .move(points.lidBottomRight)
+ .line(points.lidTopRight)
+ .offset(-sa)
+ paths.boundLine = new Path()
+ .move(sideBindingLineRough.intersectsY(lidFlapHeight)[0])
+ .line(topBindingLineRough.intersects(sideBindingLineRough)[0])
+ .line(topBindingLineRough.end())
+ .addText('Bind with tape once finished', 'center')
+ .setClass('lining sa')
+ }
+
+ // Paperless?
+ if (paperless) {
+ macro('hd', {
+ from: points.origin,
+ to: points.lidTopRight,
+ y: points.origin.y - 2 * sa - 15,
+ })
+ macro('hd', {
+ from: points.lidTopRight,
+ to: points.lidBottomRight,
+ y: points.origin.y - 2 * sa - 15,
+ })
+ macro('hd', {
+ from: points.lidBottomRight,
+ to: points.lidBottomLeft,
+ y: points.lidBottomLeft.y + 2 * sa + 15,
+ })
+ macro('vd', {
+ from: points.origin,
+ to: points.lidBottomRight,
+ x: points.lidBottomRight.x + 2 * sa + 15,
+ })
+ }
+
+ return part
+}
+
+export const lidLiner = {
+ name: 'lidLiner',
+ options: {
+ size: { pct: 100, min: 15, max: 200, menu: 'style' },
+ taperRatio: { pct: 60, min: 50, max: 100, menu: 'style' },
+ flapHeightRatio: { pct: 83, min: 60, max: 100, menu: 'style' },
+ openingRatio: { pct: 66, min: 30, max: 90, menu: 'style' },
+ },
+ plugins: [pluginBundle],
+ draft: draftLidLiner,
+}
diff --git a/designs/magde/src/lidOnePiece.mjs b/designs/magde/src/lidOnePiece.mjs
new file mode 100644
index 00000000000..268e3dcda43
--- /dev/null
+++ b/designs/magde/src/lidOnePiece.mjs
@@ -0,0 +1,117 @@
+import { pluginBundle } from '@freesewing/plugin-bundle'
+
+function draftLidOnePiece({
+ options,
+ Point,
+ Path,
+ Snippet,
+ points,
+ paths,
+ complete,
+ sa,
+ snippets,
+ paperless,
+ macro,
+ part,
+}) {
+ // Width is halved as this is cut on a fold
+ const width = (options.size * 500) / 2
+ const height = options.size * 300
+ const taperWidth = width * options.taperRatio
+ const lidFlapHeight = height * options.flapHeightRatio
+ const lidFlapWidth = taperWidth * 0.8
+ const openingWidth = taperWidth * options.openingRatio
+ points.origin = new Point(0, 0)
+ points.lidTopRight = new Point(lidFlapWidth, 0)
+ points.lidBottomRight = new Point(width, lidFlapHeight)
+ points.lidBottomLeft = new Point(0, lidFlapHeight)
+
+ paths.seam = new Path()
+ .move(points.lidBottomLeft)
+ .line(points.lidBottomRight)
+ .line(points.lidTopRight)
+ .line(points.origin)
+ .close()
+ .attr('class', 'fabric')
+
+ // Complete?
+ if (complete) {
+ macro('cutonfold', {
+ from: points.origin,
+ to: points.lidBottomLeft,
+ })
+
+ points.label = new Point(lidFlapWidth / 2, lidFlapHeight / 2)
+
+ macro('title', {
+ at: points.label,
+ title: 'Lid - One Piece',
+ nr: '3',
+ })
+ points.notchPoint = new Point((openingWidth + lidFlapWidth) / 2, lidFlapHeight * 0.2).addText(
+ 'Webbing Notch',
+ 'center'
+ )
+ snippets.webbingNotch = new Snippet('notch', points.notchPoint)
+ if (sa) {
+ paths.sa = new Path()
+ .move(points.lidBottomLeft)
+ .line(points.lidBottomRight)
+ .offset(2 * sa)
+ .addText('Flat fell seam', 'left')
+ .line(points.lidBottomRight)
+ .setClass('fabric sa')
+ }
+ var topBindingLineRough = new Path().move(points.lidTopRight).line(points.origin).offset(-sa)
+ var sideBindingLineRough = new Path()
+ .move(points.lidBottomRight)
+ .line(points.lidTopRight)
+ .offset(-sa)
+ paths.boundLine = new Path()
+ .move(sideBindingLineRough.intersectsY(lidFlapHeight)[0])
+ .line(topBindingLineRough.intersects(sideBindingLineRough)[0])
+ .line(topBindingLineRough.end())
+ .addText('Bind with tape once finished', 'center')
+ .setClass('fabric sa')
+ }
+
+ // Paperless?
+ if (paperless) {
+ macro('hd', {
+ from: points.origin,
+ to: points.lidTopRight,
+ y: points.origin.y - 2 * sa - 15,
+ })
+ macro('hd', {
+ from: points.lidTopRight,
+ to: points.lidBottomRight,
+ y: points.origin.y - 2 * sa - 15,
+ })
+ macro('hd', {
+ from: points.lidBottomRight,
+ to: points.lidBottomLeft,
+ y: points.lidBottomLeft.y + 2 * sa + 15,
+ })
+ macro('vd', {
+ from: points.origin,
+ to: points.lidBottomRight,
+ x: points.lidBottomRight.x + 2 * sa + 15,
+ })
+ }
+ if (!options.onePieceLid) part.hide()
+
+ return part
+}
+
+export const lidOnePiece = {
+ name: 'lidOnePiece',
+ options: {
+ size: { pct: 100, min: 15, max: 200, menu: 'style' },
+ taperRatio: { pct: 60, min: 50, max: 100, menu: 'style' },
+ flapHeightRatio: { pct: 83, min: 60, max: 100, menu: 'style' },
+ openingRatio: { pct: 66, min: 30, max: 90, menu: 'style' },
+ onePieceLid: { bool: false, menu: 'style' },
+ },
+ plugins: [pluginBundle],
+ draft: draftLidOnePiece,
+}
diff --git a/designs/magde/src/strapAttachments.mjs b/designs/magde/src/strapAttachments.mjs
new file mode 100644
index 00000000000..773c0fab6f7
--- /dev/null
+++ b/designs/magde/src/strapAttachments.mjs
@@ -0,0 +1,136 @@
+import { pluginBundle } from '@freesewing/plugin-bundle'
+
+function draftStrapAttachments({
+ options,
+ Point,
+ Path,
+ Snippet,
+ points,
+ paths,
+ snippets,
+ complete,
+ sa,
+ paperless,
+ macro,
+ part,
+}) {
+ // Width is halved as this is cut on a fold
+ const width = (options.size * 500) / 2
+ const height = options.size * 300
+ const taperWidth = width * options.taperRatio
+ const idealWebbingSize = height / 7.5
+ var webbingSize = idealWebbingSize
+ if (options.useCommonWebbingSizes) {
+ var lowerBound = 6
+ var upperBound = idealWebbingSize
+ const commonWebbingSizes = [6, 10, 12, 15, 20, 25, 30, 40, 45, 50]
+ // get the bigest size smaller than the ideal
+ var smallerSizes = commonWebbingSizes.filter(function (webbing) {
+ return webbing < idealWebbingSize
+ })
+ lowerBound = smallerSizes.length >= 1 ? smallerSizes.at(-1) : lowerBound
+ // get the smallest size bigger than the ideal
+ var largerSizes = commonWebbingSizes.filter(function (webbing) {
+ return webbing >= idealWebbingSize
+ })
+ upperBound = largerSizes.length >= 1 ? largerSizes[0] : upperBound
+
+ // Pick the value closest to ideal defaulting to smaller
+ if (upperBound - idealWebbingSize > idealWebbingSize - lowerBound) {
+ webbingSize = lowerBound
+ } else {
+ webbingSize = upperBound
+ }
+ } else {
+ webbingSize = idealWebbingSize
+ }
+
+ points.origin = new Point(0, 0)
+ points.webbingOpeningBottom = new Point(1.25 * webbingSize, 0)
+ points.bagAttachmentTop = new Point(0, 1.25 * webbingSize)
+ points.fakeBagCorner = new Point(height, points.bagAttachmentTop.y + (width - taperWidth))
+ points.frontFlapBottom = new Point(taperWidth, 0)
+ points.bagAttachmentBottom = points.bagAttachmentTop.shiftFractionTowards(
+ points.fakeBagCorner,
+ 0.33
+ )
+
+ paths.seam = new Path()
+ .move(points.bagAttachmentTop)
+ .line(points.bagAttachmentBottom)
+ .line(points.webbingOpeningBottom)
+ .line(points.origin)
+ .close()
+ .attr('class', 'fabric')
+
+ // Complete?
+ if (complete) {
+ macro('cutonfold', {
+ from: points.origin,
+ to: points.bagAttachmentTop,
+ })
+
+ points.label = points.origin.shiftFractionTowards(points.bagAttachmentBottom, 0.55)
+ macro('title', {
+ at: new Point(webbingSize, webbingSize),
+ cutlist: true,
+ title: 'Strap Attachment',
+ nr: '4',
+ scale: 0.3,
+ })
+
+ points.webbingCenterNotch = points.origin.shiftFractionTowards(points.webbingOpeningBottom, 0.5)
+ points.webbingCenterLabel = points.webbingCenterNotch
+ .clone()
+ .translate(0, -5)
+ .addText(`Center of webbing (${webbingSize}mm)`, 'center text-xs')
+
+ snippets.webbingCenter = new Snippet('notch', points.webbingCenterNotch)
+
+ if (sa) {
+ var bagAttachSeam = new Path().move(points.bagAttachmentTop).line(points.bagAttachmentBottom)
+ var attachSeamAllowance = bagAttachSeam.offset(sa)
+ var bagAttachmentSa = new Path()
+ .move(attachSeamAllowance.intersectsX(0)[0])
+ .line(attachSeamAllowance.end())
+ var restOfSa = new Path()
+ .move(points.bagAttachmentBottom)
+ .line(points.webbingOpeningBottom)
+ .line(points.origin)
+ .offset(sa)
+ paths.sa = bagAttachmentSa.join(restOfSa).close().setClass('fabric sa')
+ }
+ }
+
+ // Paperless?
+ if (paperless) {
+ macro('hd', {
+ from: points.origin,
+ to: points.webbingOpeningBottom,
+ y: points.origin.y - 2 * sa - 15,
+ })
+ macro('hd', {
+ from: points.origin,
+ to: points.bagAttachmentBottom,
+ y: points.bagAttachmentBottom.y + 2 * sa + 15,
+ })
+ macro('vd', {
+ from: points.origin,
+ to: points.bagAttachmentBottom,
+ x: points.bagAttachmentBottom.x + 2 * sa,
+ })
+ }
+
+ return part
+}
+
+export const strapAttachments = {
+ name: 'strapAttachments',
+ options: {
+ size: { pct: 100, min: 15, max: 200, menu: 'style' },
+ taperRatio: { pct: 60, min: 50, max: 100, menu: 'style' },
+ useCommonWebbingSizes: { bool: true, menu: 'style' },
+ },
+ plugins: [pluginBundle],
+ draft: draftStrapAttachments,
+}
diff --git a/designs/magde/src/twoPieceLidBottom.mjs b/designs/magde/src/twoPieceLidBottom.mjs
new file mode 100644
index 00000000000..c9534db122e
--- /dev/null
+++ b/designs/magde/src/twoPieceLidBottom.mjs
@@ -0,0 +1,110 @@
+import { pluginBundle } from '@freesewing/plugin-bundle'
+
+function draftTwoPieceLidBottom({
+ options,
+ Point,
+ Path,
+ points,
+ paths,
+ complete,
+ sa,
+ paperless,
+ macro,
+ part,
+}) {
+ // Width is halved as this is cut on a fold
+ const width = (options.size * 500) / 2
+ const height = options.size * 300
+ const taperWidth = width * options.taperRatio
+ const lidFlapHeight = height * options.flapHeightRatio
+ const lidFlapWidth = taperWidth * 0.8
+ points.origin = new Point(0, 0)
+ points.lidTopRight = new Point(lidFlapWidth, 0)
+ points.lidBottomRight = new Point(width, lidFlapHeight)
+ points.lidBottomLeft = new Point(0, lidFlapHeight)
+ points.lidSeamRight = points.lidTopRight.shiftFractionTowards(points.lidBottomRight, 0.25)
+ points.lidSeamLeft = new Point(0, points.lidSeamRight.y)
+
+ paths.seam = new Path()
+ .move(points.lidSeamLeft)
+ .line(points.lidSeamRight)
+ .line(points.lidTopRight)
+ .line(points.origin)
+ .close()
+ .attr('class', 'fabric')
+
+ // Complete?
+ if (complete) {
+ // macro('cutonfold', {
+ // from: points.lidTopLeft,
+ // to: points.lidSeamLeft,
+ // })
+
+ points.label = new Point(points.lidSeamRight.x / 3, points.lidSeamRight.y / 1.5)
+
+ macro('title', {
+ at: points.label,
+ title: 'Lid Bottom - Two Piece',
+ nr: '3.2',
+ scale: 0.4,
+ })
+ if (sa) {
+ var bindingLineSideRough = new Path()
+ .move(points.lidSeamRight)
+ .line(points.lidTopRight)
+ .offset(-sa)
+ var bindingLineTopRough = new Path().move(points.lidTopRight).line(points.origin).offset(-sa)
+ // points.tmp1 = bindingLineSideRough.intersectsY(points.lidSeamRight.y)[0]
+ paths.bindingLine = new Path()
+ .move(bindingLineSideRough.intersectsY(points.lidSeamLeft.y)[0])
+ .line(bindingLineSideRough.intersects(bindingLineTopRough)[0])
+ .line(bindingLineTopRough.end())
+ .addClass('fabric sa')
+ paths.sa = new Path()
+ .move(points.lidSeamLeft)
+ .line(points.lidSeamRight)
+ .offset(2 * sa)
+ .line(points.lidSeamRight)
+ .setClass('fabric sa')
+ }
+ }
+
+ // Paperless?
+ if (paperless) {
+ macro('hd', {
+ from: points.origin,
+ to: points.lidSeamRight,
+ y: points.origin.y - 2 * sa - 15,
+ })
+ macro('hd', {
+ from: points.lidTopRight,
+ to: points.lidSeamRight,
+ y: points.origin.y - 2 * sa - 15,
+ })
+ macro('hd', {
+ from: points.lidSeamRight,
+ to: points.lidSeamLeft,
+ y: points.lidSeamLeft.y + 2 * sa + 15,
+ })
+ macro('vd', {
+ from: points.origin,
+ to: points.lidSeamLeft,
+ x: points.lidSeamRight.x + 2 * sa,
+ })
+ }
+ if (options.onePieceLid) part.hide()
+ return part
+}
+
+export const twoPieceLidBottom = {
+ name: 'twoPieceLidBottom',
+ options: {
+ size: { pct: 100, min: 15, max: 200, menu: 'style' },
+ taperRatio: { pct: 60, min: 50, max: 100, menu: 'style' },
+ flapHeightRatio: { pct: 83, min: 60, max: 100, menu: 'style' },
+ openingRatio: { pct: 66, min: 30, max: 90, menu: 'style' },
+ onePieceLid: { bool: false, menu: 'style' },
+ },
+ plugins: [pluginBundle],
+ draft: draftTwoPieceLidBottom,
+}
diff --git a/designs/magde/src/twoPieceLidTop.mjs b/designs/magde/src/twoPieceLidTop.mjs
new file mode 100644
index 00000000000..46460f38749
--- /dev/null
+++ b/designs/magde/src/twoPieceLidTop.mjs
@@ -0,0 +1,117 @@
+import { pluginBundle } from '@freesewing/plugin-bundle'
+
+function draftTwoPieceLidTop({
+ options,
+ Point,
+ Path,
+ Snippet,
+ points,
+ paths,
+ complete,
+ sa,
+ snippets,
+ paperless,
+ macro,
+ part,
+}) {
+ // Width is halved as this is cut on a fold
+ const width = (options.size * 500) / 2
+ const height = options.size * 300
+ const taperWidth = width * options.taperRatio
+ const lidFlapHeight = height * options.flapHeightRatio
+ const lidFlapWidth = taperWidth * 0.8
+ const openingWidth = taperWidth * options.openingRatio
+ points.origin = new Point(0, 0)
+ points.lidTopRight = new Point(lidFlapWidth, 0)
+ points.lidBottomRight = new Point(width, lidFlapHeight)
+ points.lidBottomLeft = new Point(0, lidFlapHeight)
+ points.lidSeamRight = points.lidTopRight.shiftFractionTowards(points.lidBottomRight, 0.2)
+ points.lidSeamLeft = new Point(0, points.lidSeamRight.y)
+
+ paths.seam = new Path()
+ .move(points.lidBottomLeft)
+ .line(points.lidBottomRight)
+ .line(points.lidSeamRight)
+ .line(points.lidSeamLeft)
+ .close()
+ .attr('class', 'fabric')
+
+ // Complete?
+ if (complete) {
+ macro('cutonfold', {
+ from: points.lidSeamLeft,
+ to: points.lidBottomLeft,
+ })
+
+ points.label = new Point(lidFlapWidth / 2, lidFlapHeight / 2)
+
+ macro('title', {
+ at: points.label,
+ title: 'Lid Top - Two Piece',
+ nr: '3.1',
+ })
+ points.notchPoint = new Point((openingWidth + lidFlapWidth) / 2, points.lidSeamLeft.y).addText(
+ 'Webbing Notch',
+ 'center'
+ )
+ snippets.webbingNotch = new Snippet('bnotch', points.notchPoint)
+
+ if (sa) {
+ paths.topSa = new Path()
+ .move(points.lidSeamRight)
+ .join(
+ new Path()
+ .move(points.lidSeamRight)
+ .line(points.lidSeamLeft)
+ .offset(2 * sa)
+ )
+ .setClass('fabric sa')
+ paths.bottomSa = new Path()
+ .move(points.lidBottomLeft)
+ .line(points.lidBottomRight)
+ .offset(2 * sa)
+ .line(points.lidBottomRight)
+ .setClass('fabric sa')
+ }
+ }
+
+ // Paperless?
+ if (paperless) {
+ macro('hd', {
+ from: points.origin,
+ to: points.lidSeamRight,
+ y: points.lidSeamLeft.y - 2 * sa - 15,
+ })
+ macro('hd', {
+ from: points.lidSeamRight,
+ to: points.lidBottomRight,
+ y: points.lidSeamLeft.y - 2 * sa - 15,
+ })
+ macro('hd', {
+ from: points.lidBottomRight,
+ to: points.lidBottomLeft,
+ y: points.lidBottomLeft.y + 2 * sa + 15,
+ })
+ macro('vd', {
+ from: points.lidSeamRight,
+ to: points.lidBottomRight,
+ x: points.lidBottomRight.x + 2 * sa,
+ })
+ }
+
+ if (options.onePieceLid) part.hide()
+ return part
+}
+
+export const twoPieceLidTop = {
+ name: 'twoPieceLidTop',
+ options: {
+ size: { pct: 100, min: 15, max: 200, menu: 'style' },
+ taperRatio: { pct: 60, min: 50, max: 100, menu: 'style' },
+ flapHeightRatio: { pct: 83, min: 60, max: 100, menu: 'style' },
+ openingRatio: { pct: 66, min: 30, max: 90, menu: 'style' },
+ onePieceLid: { bool: false, menu: 'style' },
+ },
+ plugins: [pluginBundle],
+ draft: draftTwoPieceLidTop,
+}
diff --git a/designs/magde/tests/shared.test.mjs b/designs/magde/tests/shared.test.mjs
new file mode 100644
index 00000000000..1d7256bd762
--- /dev/null
+++ b/designs/magde/tests/shared.test.mjs
@@ -0,0 +1,16 @@
+// This file is auto-generated | Any changes you make will be overwritten.
+import { Magde } from '../src/index.mjs'
+
+// Shared tests
+import { testPatternConfig } from '../../../tests/designs/config.mjs'
+import { testPatternDrafting } from '../../../tests/designs/drafting.mjs'
+import { testPatternSampling } from '../../../tests/designs/sampling.mjs'
+
+// Test config
+testPatternConfig(Magde)
+
+// Test drafting - Change the second parameter to `true` to log errors
+testPatternDrafting(Magde, false)
+
+// Test sampling - Change the second parameter to `true` to log errors
+testPatternSampling(Magde, false)