diff --git a/CHANGELOG.md b/CHANGELOG.md
index d8ce771d605..105bacdba2e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -685,6 +685,12 @@
### yuri
+#### Changed
+
+ - Migrated from Rollup to Esbuild for all builds
+
+### examples
+
#### Changed
- Migrated from Rollup to Esbuild for all builds
@@ -1272,6 +1278,16 @@
### yuri
+#### Changed
+
+ - Switched to default import for version from package.json
+
+### examples
+
+#### Added
+
+ - Added plugin_gore example
+
#### Changed
- Switched to default import for version from package.json
@@ -2527,6 +2543,13 @@
- Added the (disabled) waistbandHeight option from Titan
- Changed to Titan's waistbandWidth option
+### examples
+
+#### Added
+
+ - Added examples for bartack plugin
+ - Added examples for new buttonhole-start/end snippets
+
### plugin-buttons
#### Added
@@ -4012,6 +4035,12 @@
### wahid
+#### Added
+
+ - Initial release
+
+### examples
+
#### Added
- Initial release
diff --git a/config/dependencies.yaml b/config/dependencies.yaml
index 20b7dd25f44..452e4aa2cc6 100644
--- a/config/dependencies.yaml
+++ b/config/dependencies.yaml
@@ -65,6 +65,10 @@ diana:
peer:
'@freesewing/brian': *freesewing
'@freesewing/plugin-bust': *freesewing
+examples:
+ peer:
+ '@freesewing/plugin-mirror': *freesewing
+ '@freesewing/plugin-gore': *freesewing
holmes:
_:
'@freesewing/plugin-gore': *freesewing
diff --git a/config/keywords.yaml b/config/keywords.yaml
index 7b04674c8cf..f6b93f28536 100644
--- a/config/keywords.yaml
+++ b/config/keywords.yaml
@@ -24,6 +24,10 @@ design:
- pattern
- sewing
- sewing pattern
+examples:
+ - documentation
+ - example
+ - parametric design
i18n:
- i18n
- internationalisation
diff --git a/config/software/designs.json b/config/software/designs.json
index d235ed76bb6..03ad76be50b 100644
--- a/config/software/designs.json
+++ b/config/software/designs.json
@@ -328,6 +328,10 @@
}
},
"utilities": {
+ "examples": {
+ "description": "A FreeSewing pattern holding examples for our documentation",
+ "code": "Joost De Cock"
+ },
"legend": {
"description": "A FreeSewing pattern to document pattern notation",
"code": "Joost De Cock"
diff --git a/designs/examples/.eslintrc.yml b/designs/examples/.eslintrc.yml
new file mode 100644
index 00000000000..b39fd0463e8
--- /dev/null
+++ b/designs/examples/.eslintrc.yml
@@ -0,0 +1,18 @@
+env:
+ browser: true
+ es2021: true
+extends: eslint:recommended
+overrides:
+ - files: ["*.yaml", "*.yml"]
+ plugins: ["yaml"]
+ extends: ["plugin:yaml/recommended"]
+parserOptions:
+ ecmaVersion: latest
+ sourceType: module
+rules: {}
+globals:
+ it: readonly
+ describe: readonly
+ process: readonly
+ __dirname: readonly
+
diff --git a/designs/examples/CHANGELOG.md b/designs/examples/CHANGELOG.md
new file mode 100644
index 00000000000..0173d7207ac
--- /dev/null
+++ b/designs/examples/CHANGELOG.md
@@ -0,0 +1,38 @@
+# Change log for: @freesewing/examples
+
+
+## 2.21.0 (2022-06-27)
+
+### Changed
+
+ - Migrated from Rollup to Esbuild for all builds
+
+## 2.20.0 (2022-01-24)
+
+### Added
+
+ - Added plugin_gore example
+
+### Changed
+
+ - Switched to default import for version from package.json
+
+## 2.15.0 (2021-04-15)
+
+### Added
+
+ - Added examples for bartack plugin
+ - Added examples for new buttonhole-start/end snippets
+
+## 2.0.0 (2019-08-25)
+
+### Added
+
+ - Initial release
+
+
+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/examples/README.md b/designs/examples/README.md
new file mode 100644
index 00000000000..31fba58bc1f
--- /dev/null
+++ b/designs/examples/README.md
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+
+
+
+
+# @freesewing/examples
+
+A FreeSewing pattern holding examples for our documentation
+
+
+
+
+> #### 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).
+
+## 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/examples
+
+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/examples/build.mjs b/designs/examples/build.mjs
new file mode 100644
index 00000000000..aa4dfabb290
--- /dev/null
+++ b/designs/examples/build.mjs
@@ -0,0 +1,36 @@
+/* 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
+let result
+(async () => {
+ result = await esbuild.build(options).catch(() => process.exit(1))
+
+ if (process.env.VERBOSE) {
+ const info = await esbuild.analyzeMetafile(result.metafile)
+ console.log(info)
+ }
+
+})()
diff --git a/designs/examples/config/index.js b/designs/examples/config/index.js
new file mode 100644
index 00000000000..c7219e59e22
--- /dev/null
+++ b/designs/examples/config/index.js
@@ -0,0 +1,122 @@
+import pkg from '../package.json' assert { type: 'json' }
+
+const { version } = pkg
+
+export default {
+ version,
+ name: 'examples',
+ design: 'Joost De Cock',
+ code: 'Joost De Cock',
+ department: 'womenswear',
+ type: 'pattern',
+ difficulty: 1,
+ tags: ['example'],
+ optionGroups: {
+ fit: ['fixme'],
+ },
+ measurements: [],
+ dependencies: {
+ //point_attr: 'path_attr'
+ },
+ parts: [
+ 'point_attr',
+ 'path_move',
+ 'path_line',
+ 'path_curve',
+ 'path__curve',
+ 'path_curve_',
+ 'path_close',
+ 'path_ops',
+ 'path_attr',
+ 'path_clone',
+ 'path_divide',
+ 'path_edge',
+ 'path_end',
+ 'path_intersects',
+ 'path_intersectsx',
+ 'path_intersectsy',
+ 'path_join',
+ 'path_length',
+ 'path_noop',
+ 'path_offset',
+ 'path_reverse',
+ 'path_shiftalong',
+ 'path_shiftfractionalong',
+ 'path_split',
+ 'path_start',
+ 'path_translate',
+ 'path_trim',
+ 'plugin_banner',
+ 'plugin_bartack',
+ 'plugin_bartackalong',
+ 'plugin_bartackfractionalong',
+ 'plugin_buttons',
+ 'plugin_cutonfold',
+ 'plugin_dimension',
+ 'plugin_gore',
+ 'plugin_grainline',
+ 'plugin_logo',
+ 'plugin_mirror',
+ 'plugin_notches',
+ 'plugin_round',
+ 'plugin_scalebox',
+ 'plugin_sprinkle',
+ 'plugin_title',
+ 'point_angle',
+ 'point_attr',
+ 'point_clone',
+ 'point_copy',
+ 'point_dist',
+ 'point_dx',
+ 'point_dy',
+ 'point_flipx',
+ 'point_flipy',
+ 'point_shift',
+ 'point_shiftfractiontowards',
+ 'point_shifttowards',
+ 'point_shiftoutwards',
+ 'point_sitson',
+ 'point_sitsroughlyon',
+ 'point_rotate',
+ 'point_translate',
+ 'settings_sa',
+ 'snippet',
+ 'snippet_attr',
+ 'snippet_clone',
+ 'snippets_bnotch',
+ 'snippets_notch',
+ 'snippets_button',
+ 'snippets_buttonhole',
+ 'snippets_buttonhole_start',
+ 'snippets_buttonhole_end',
+ 'snippets_snapsocket',
+ 'snippets_snapstud',
+ 'snippets_logo',
+ 'utils_linesintersect',
+ 'utils_beamsintersect',
+ 'utils_beamintersectsx',
+ 'utils_beamintersectsy',
+ 'utils_lineintersectscurve',
+ 'utils_curvesintersect',
+ 'utils_pointonbeam',
+ 'utils_pointonline',
+ 'utils_pointoncurve',
+ 'utils_circlesintersect',
+ 'utils_beamintersectscircle',
+ 'utils_lineintersectscircle',
+ 'utils_curveintersectsy',
+ 'utils_curveintersectsx',
+ 'utils_splitcurve',
+ 'docs_overview',
+ 'docs_coords',
+ ],
+ options: {
+ focus: '',
+ // Optiongroups are needed for now, because workbench
+ fixme: {
+ pct: 50,
+ min: 0,
+ max: 100,
+ },
+ },
+}
diff --git a/designs/examples/data.mjs b/designs/examples/data.mjs
new file mode 100644
index 00000000000..0617f3ee8e5
--- /dev/null
+++ b/designs/examples/data.mjs
@@ -0,0 +1,4 @@
+// This file is auto-generated | All changes you make will be overwritten.
+export const name = "@freesewing/examples"
+export const version = "3.0.0-alpha.0"
+export const data = { name, version }
diff --git a/designs/examples/package.json b/designs/examples/package.json
new file mode 100644
index 00000000000..63b468a69e4
--- /dev/null
+++ b/designs/examples/package.json
@@ -0,0 +1,65 @@
+{
+ "name": "@freesewing/examples",
+ "version": "3.0.0-alpha.0",
+ "description": "A FreeSewing pattern holding examples for our documentation",
+ "author": "Joost De Cock (https://github.com/joostdecock)",
+ "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",
+ "documentation",
+ "example",
+ "parametric design"
+ ],
+ "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"
+ },
+ "peerDependencies": {
+ "@freesewing/core": "^3.0.0-alpha.0",
+ "@freesewing/plugin-bundle": "^3.0.0-alpha.0",
+ "@freesewing/config-helpers": "^3.0.0-alpha.0",
+ "@freesewing/plugin-mirror": "^3.0.0-alpha.0",
+ "@freesewing/plugin-gore": "^3.0.0-alpha.0"
+ },
+ "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/examples/src/docs.mjs b/designs/examples/src/docs.mjs
new file mode 100644
index 00000000000..e00000a3d42
--- /dev/null
+++ b/designs/examples/src/docs.mjs
@@ -0,0 +1,321 @@
+import { box } from './shared.mjs'
+
+export const docs_coords = {
+ name: 'examples.box_coords',
+ draft: ({ Point, points, paths, Path, part }) => {
+ points.origin = new Point(10, 10)
+ points.x = new Point(100, 10)
+ points.y = new Point(10, 50)
+ points.textX = points.x.shift(135, 2).attr('data-text', 'X')
+ points.textY = points.y.shift(180, 5).attr('data-text', 'Y')
+ paths.coords = new Path()
+ .move(points.y)
+ .line(points.origin)
+ .line(points.x)
+ .attr('class', 'mark')
+ .attr('marker-start', 'url(#dimensionFrom)')
+ .attr('marker-end', 'url(#dimensionTo)')
+
+ return box(part, 100, 50)
+ },
+}
+
+export const docs_overview = {
+ name: 'examples.docs_overview',
+ draft: ({ Point, points, Path, paths, options, part }) => {
+ /**
+ * Returs the value passed to it randomized with a given tolerance
+ */
+ const about = (value, tolerance = 5) => {
+ let randomized = (tolerance / 100) * Math.random() * value
+ let fixed = (1 - tolerance / 100) * value
+
+ return fixed + randomized
+ }
+
+ /**
+ * like about, but also randomly makes value negative
+ * This is for degrees
+ */
+ const nabout = (value, tolerance = 5) => {
+ if (Math.random() > 0.5) return about(value, tolerance)
+ else return -1 * about(value, tolerance)
+ }
+
+ /**
+ * Draws a w*h box that's randomized by tolerance to give it
+ * that hand-drawn look.
+ *
+ * Returns a Path object
+ */
+ const box = (name, origin, width, height, tolerance = 10) => {
+ let base = height
+ if (width < height) base = width
+ let t = base * (tolerance / 100)
+ points[name + 'TopLeft'] = new Point(about(origin.x, t), about(origin.y, t))
+ points[name + 'BottomLeft'] = new Point(about(origin.x, t), about(origin.y + height, t))
+ points[name + 'BottomRight'] = new Point(
+ about(origin.x + width, t),
+ about(origin.y + height, t)
+ )
+ points[name + 'TopRight'] = new Point(about(origin.x + width, t), about(origin.y, t))
+ points[name + 'Mid'] = points[name + 'TopLeft'].shiftFractionTowards(
+ points[name + 'BottomRight'],
+ 0.5
+ )
+ points[name + 'Mid'].y += 3
+
+ let f = 0.3
+ let r = tolerance / 2
+ points[name + 'TopLeftCp1'] = points[name + 'TopLeft']
+ .shiftFractionTowards(points[name + 'BottomLeft'], about(f))
+ .rotate(nabout(r), points[name + 'TopLeft'])
+ points[name + 'TopLeftCp2'] = points[name + 'TopLeft']
+ .shiftFractionTowards(points[name + 'TopRight'], about(f))
+ .rotate(nabout(r), points[name + 'TopLeft'])
+ points[name + 'BottomLeftCp1'] = points[name + 'BottomLeft']
+ .shiftFractionTowards(points[name + 'TopLeft'], about(f))
+ .rotate(nabout(r), points[name + 'BottomLeft'])
+ points[name + 'BottomLeftCp2'] = points[name + 'BottomLeft']
+ .shiftFractionTowards(points[name + 'BottomRight'], about(f))
+ .rotate(nabout(r), points[name + 'BottomLeft'])
+ points[name + 'BottomRightCp1'] = points[name + 'BottomRight']
+ .shiftFractionTowards(points[name + 'BottomLeft'], about(f))
+ .rotate(nabout(r), points[name + 'BottomRight'])
+ points[name + 'BottomRightCp2'] = points[name + 'BottomRight']
+ .shiftFractionTowards(points[name + 'TopRight'], about(f))
+ .rotate(nabout(r), points[name + 'BottomRight'])
+ points[name + 'TopRightCp1'] = points[name + 'TopRight']
+ .shiftFractionTowards(points[name + 'BottomRight'], about(f))
+ .rotate(nabout(r), points[name + 'TopRight'])
+ points[name + 'TopRightCp2'] = points[name + 'TopRight']
+ .shiftFractionTowards(points[name + 'TopLeft'], about(f))
+ .rotate(nabout(r), points[name + 'TopRight'])
+
+ return new Path()
+ .move(points[name + 'TopLeft'])
+ .curve(
+ points[name + 'TopLeftCp1'],
+ points[name + 'BottomLeftCp1'],
+ points[name + 'BottomLeft']
+ )
+ .curve(
+ points[name + 'BottomLeftCp2'],
+ points[name + 'BottomRightCp1'],
+ points[name + 'BottomRight']
+ )
+ .curve(
+ points[name + 'BottomRightCp2'],
+ points[name + 'TopRightCp1'],
+ points[name + 'TopRight']
+ )
+ .curve(points[name + 'TopRightCp2'], points[name + 'TopLeftCp2'], points[name + 'TopLeft'])
+ .close()
+ .attr('class', options.focus === name ? 'note' : 'fabric')
+ }
+
+ /**
+ * Draws an arrow from to
+ * Returns a Path object
+ */
+ const arrow = (name, text = '', tolerance = 10) => {
+ let from = points[name + 'From']
+ let to = points[name + 'To']
+ from = from.shiftTowards(to, 3)
+ to = to.shiftTowards(from, 3)
+ let base = from.dist(to)
+ let t = base * (tolerance / 100)
+ from.x = about(from.x, t)
+ from.x = about(from.x, t)
+ to.x = about(to.x, t)
+ to.x = about(to.x, t)
+ let f = 0.3
+ let r = tolerance / 2
+ points[name + 'FromCp'] = from.shiftFractionTowards(to, about(f)).rotate(nabout(r), from)
+ points[name + 'ToCp'] = to.shiftFractionTowards(from, about(f)).rotate(nabout(r), to)
+ points[name + 'Tip1'] = to.shiftTowards(from, about(3.8)).rotate(about(15), to)
+ points[name + 'Tip2'] = to.shiftTowards(from, about(3.5)).rotate(about(-15), to)
+ let path = new Path()
+ .move(from)
+ .curve(points[name + 'FromCp'], points[name + 'ToCp'], to)
+ .move(points[name + 'Tip1'])
+ .line(to)
+ .line(points[name + 'Tip2'])
+ .attr('class', 'fabric')
+ if (options.focus === name) path = path.attr('class', 'note')
+ if (text)
+ return path
+ .attr('data-text', ' ' + text)
+ .attr('data-text-class', 'scribble')
+ .attr('data-text-class', options.focus === name ? 'fill-note' : '')
+ else return path
+ }
+
+ const drawBox = (name, x, y, width, height, tolerance = 5, text = true) => {
+ points[name + 'Origin'] = new Point(x, y)
+ paths[name] = box(name, points[name + 'Origin'], width, height, tolerance)
+ if (text) {
+ points[name + 'Mid'].attr('data-text', name).attr('data-text-class', 'center scribble')
+ if (options.focus === name) points[name + 'Mid'].attr('data-text-class', 'fill-note')
+ }
+ }
+
+ const svgLogo = (anchor, size = 1) => {
+ points.svg15 = anchor
+ .shift(45, 4 * size)
+ .attr('data-circle', 1.2 * size)
+ .attr('data-circle-class', 'fill-fabric')
+ points.svg3 = anchor
+ .shift(0, 4 * size)
+ .attr('data-circle', 1.2 * size)
+ .attr('data-circle-class', 'fill-fabric')
+ points.svg45 = anchor
+ .shift(-45, 4 * size)
+ .attr('data-circle', 1.2 * size)
+ .attr('data-circle-class', 'fill-fabric')
+ points.svg6 = anchor
+ .shift(-90, 4 * size)
+ .attr('data-circle', 1.2 * size)
+ .attr('data-circle-class', 'fill-fabric')
+ points.svg75 = anchor
+ .shift(-135, 4 * size)
+ .attr('data-circle', 1.2 * size)
+ .attr('data-circle-class', 'fill-fabric')
+ points.svg9 = anchor
+ .shift(180, 4 * size)
+ .attr('data-circle', 1.2 * size)
+ .attr('data-circle-class', 'fill-fabric')
+ points.svg105 = anchor
+ .shift(135, 4 * size)
+ .attr('data-circle', 1.2 * size)
+ .attr('data-circle-class', 'fill-fabric')
+ points.svg12 = anchor
+ .shift(90, 4 * size)
+ .attr('data-circle', 1.2 * size)
+ .attr('data-circle-class', 'fill-fabric')
+ points.svgText = anchor
+ .clone()
+ .attr('data-text', 'SVG')
+ .attr('data-text-class', 'text-xl scribble')
+ points.svgText.x += size * 7
+ points.svgText.y += 6
+ paths.svgLogo = new Path()
+ .move(points.svg15)
+ .line(points.svg75)
+ .move(points.svg3)
+ .line(points.svg9)
+ .move(points.svg45)
+ .line(points.svg105)
+ .move(points.svg6)
+ .line(points.svg12)
+ .attr('class', 'stroke-l')
+ }
+ const reactLogo = (anchor, size = 1) => {
+ h = 3 * size
+ w = 6 * size
+ points.reactTop1 = anchor.shift(45, w)
+ points.reactBottom1 = anchor.shift(-135, w)
+ points.reactTop1Cp1 = points.reactTop1.shift(135, h)
+ points.reactTop1Cp2 = points.reactTop1.shift(-45, h)
+ points.reactBottom1Cp1 = points.reactBottom1.shift(135, h)
+ points.reactBottom1Cp2 = points.reactBottom1.shift(-45, h)
+ points.reactTop2 = points.reactTop1.rotate(60, anchor)
+ points.reactBottom2 = points.reactBottom1.rotate(60, anchor)
+ points.reactTop2Cp1 = points.reactTop1Cp1.rotate(60, anchor)
+ points.reactTop2Cp2 = points.reactTop1Cp2.rotate(60, anchor)
+ points.reactBottom2Cp1 = points.reactBottom1Cp1.rotate(60, anchor)
+ points.reactBottom2Cp2 = points.reactBottom1Cp2.rotate(60, anchor)
+ points.reactTop3 = points.reactTop1.rotate(-60, anchor)
+ points.reactBottom3 = points.reactBottom1.rotate(-60, anchor)
+ points.reactTop3Cp1 = points.reactTop1Cp1.rotate(-60, anchor)
+ points.reactTop3Cp2 = points.reactTop1Cp2.rotate(-60, anchor)
+ points.reactBottom3Cp1 = points.reactBottom1Cp1.rotate(-60, anchor)
+ points.reactBottom3Cp2 = points.reactBottom1Cp2.rotate(-60, anchor)
+ points.svgLogo = anchor
+ .clone()
+ .attr('data-circle', 1 * size)
+ .attr('data-circle-class', 'fill-fabric')
+ points.reactText = anchor
+ .clone()
+ .attr('data-text', 'React')
+ .attr('data-text-class', 'text-xl scribble')
+ points.reactText.x += size * 7
+ points.reactText.y += 6
+
+ paths.reactLogo = new Path()
+ .move(points.reactTop1)
+ .curve(points.reactTop1Cp1, points.reactBottom1Cp1, points.reactBottom1)
+ .curve(points.reactBottom1Cp2, points.reactTop1Cp2, points.reactTop1)
+ .close()
+ .move(points.reactTop2)
+ .curve(points.reactTop2Cp1, points.reactBottom2Cp1, points.reactBottom2)
+ .curve(points.reactBottom2Cp2, points.reactTop2Cp2, points.reactTop2)
+ .close()
+ .move(points.reactTop3)
+ .curve(points.reactTop3Cp1, points.reactBottom3Cp1, points.reactBottom3)
+ .curve(points.reactBottom3Cp2, points.reactTop3Cp2, points.reactTop3)
+ .close()
+ }
+
+ // Other parts first so they're behind
+ drawBox('Part4', 4, -19, 40, 65, 5, false)
+ drawBox('Part3', 1, -16, 40, 65, 5, false)
+ drawBox('Part2', -2, -13, 40, 65, 5, false)
+ drawBox('Part', -5, -10, 40, 65, 5)
+ paths.Part4.attr('class', 'fill-bg')
+ paths.Part3.attr('class', 'fill-bg')
+ paths.Part2.attr('class', 'fill-bg')
+ paths.Part.attr('class', 'fill-bg')
+ points.PartMid.y = points.PartTopLeft.y + 9
+ let x = 0
+ let y = 0
+ let w = 30
+ let h = 15
+ drawBox('Points', x, y, w, h)
+ y += 18
+ drawBox('Paths', x, y, w, h)
+ y += 18
+ drawBox('Snippets', x, y, w, h)
+ x = -35
+ y = -3
+ w = 25
+ h = 20
+ drawBox('config', x, y, w, h)
+ y += 23
+ drawBox('Store', x, y, w, h)
+ x = -40
+ y = -30
+ drawBox('Pattern', x, y, 90, 90)
+ points.PatternMid.y = points.PatternTopLeft.y + 9
+
+ drawBox('settings', -100, 6, 40, 20)
+ drawBox('draft', 80, 3, 20, 25)
+
+ points.arrow1From = points.settingsTopRight.shiftFractionTowards(
+ points.settingsBottomRight,
+ 0.5
+ )
+ points.arrow1To = points.PatternTopLeft.shiftFractionTowards(points.PatternBottomLeft, 0.5)
+ paths.arrow1 = arrow('arrow1')
+ points.arrow2From = points.PatternTopRight.shiftFractionTowards(points.PatternBottomRight, 0.5)
+ points.arrow2To = points.draftTopLeft.shiftFractionTowards(points.draftBottomLeft, 0.5)
+ paths.arrow2 = arrow('arrow2', 'draft()')
+
+ svgLogo(points.draftMid.shift(70, 50))
+ reactLogo(points.draftMid.shift(-80, 36))
+
+ points.arrow3From = points.draftTopLeft.shiftFractionTowards(points.draftTopRight, 0.5)
+ points.arrow3To = points.svgText.clone()
+ paths.arrow3 = arrow('arrow3', 'render()')
+ points.arrow4From = points.draftBottomLeft.shiftFractionTowards(points.draftBottomRight, 0.5)
+ points.arrow4To = points.reactText.clone().shift(40, 15)
+ paths.arrow4 = arrow('arrow4')
+
+ paths.extend = new Path()
+ .move(points.draftTopRight)
+ .line(points.draftTopRight.shift(0, 40))
+ .attr('class', 'hidden')
+
+ return part
+ },
+}
diff --git a/designs/examples/src/index.mjs b/designs/examples/src/index.mjs
new file mode 100644
index 00000000000..73e2d676f09
--- /dev/null
+++ b/designs/examples/src/index.mjs
@@ -0,0 +1,387 @@
+import { Design } from '@freesewing/core'
+import { pluginBundle } from '@freesewing/plugin-bundle'
+import { gorePlugin } from '@freesewing/plugin-gore'
+import { data } from '../data.mjs'
+
+// Path API
+import {
+ path__curve,
+ path_addclass,
+ path_addtext,
+ path_attr,
+ path_move,
+ path_line,
+ path_curve,
+ path_curve_,
+ path_close,
+ path_ops,
+ path_clone,
+ path_divide,
+ path_edge,
+ path_end,
+ path_intersects,
+ path_intersectsx,
+ path_intersectsy,
+ path_join,
+ path_length,
+ path_noop,
+ path_offset,
+ path_reverse,
+ path_shiftalong,
+ path_shiftfractionalong,
+ path_smurve,
+ path_smurve_,
+ path_split,
+ path_start,
+ path_translate,
+ path_trim,
+} from './path.mjs'
+
+// Point API
+import {
+ point_addcircle,
+ point_addtext,
+ point_angle,
+ point_attr,
+ point_clone,
+ point_copy,
+ point_dist,
+ point_dx,
+ point_dy,
+ point_flipx,
+ point_flipy,
+ point_setcircle,
+ point_settext,
+ point_shift,
+ point_shiftfractiontowards,
+ point_shifttowards,
+ point_shiftoutwards,
+ point_sitson,
+ point_sitsroughlyon,
+ point_rotate,
+ point_translate,
+} from './point.mjs'
+
+// Snippet API
+import { snippet, snippet_attr, snippet_clone } from './snippet.mjs'
+
+// Utils API
+import {
+ utils_linesintersect,
+ utils_beamsintersect,
+ utils_beamintersectsx,
+ utils_beamintersectsy,
+ utils_lineintersectscurve,
+ utils_curvesintersect,
+ utils_pointonbeam,
+ utils_pointonline,
+ utils_pointoncurve,
+ utils_circlesintersect,
+ utils_beamintersectscircle,
+ utils_lineintersectscircle,
+ utils_curveintersectsx,
+ utils_curveintersectsy,
+ utils_splitcurve,
+} from './utils.mjs'
+
+// Plugins
+import {
+ plugin_banner,
+ plugin_bartack,
+ plugin_bartackalong,
+ plugin_bartackfractionalong,
+ plugin_buttons,
+ plugin_cutonfold,
+ plugin_dimension,
+ plugin_gore,
+ plugin_grainline,
+ plugin_logo,
+ plugin_mirror,
+ plugin_notches,
+ plugin_round,
+ plugin_sprinkle,
+ plugin_scalebox,
+ plugin_title,
+} from './plugins.mjs'
+
+// Snippets
+import {
+ snippet_bnotch,
+ snippet_notch,
+ snippet_button,
+ snippet_buttonhole,
+ snippet_buttonholestart,
+ snippet_buttonholeend,
+ snippet_snapsocket,
+ snippet_snapstud,
+ snippet_logo,
+} from './snippets.mjs'
+
+// Stacks
+import {
+ stacks_top,
+ stacks_left,
+ stacks_right,
+ stacks_bottom,
+ stacks_leftEye,
+ stacks_rightEye,
+ stacks_mouth,
+} from './stacks.mjs'
+
+// Settings
+import { settings_sa } from './settings.mjs'
+
+// Docs illustrations
+import { docs_coords, docs_overview } from './docs.mjs'
+
+// Setup our new design
+const Examples = new Design({
+ data,
+ parts: [
+ // Path API
+ path__curve,
+ path_addclass,
+ path_addtext,
+ path_attr,
+ path_move,
+ path_line,
+ path_curve,
+ path_curve_,
+ path_close,
+ path_ops,
+ path_clone,
+ path_divide,
+ path_edge,
+ path_end,
+ path_intersects,
+ path_intersectsx,
+ path_intersectsy,
+ path_join,
+ path_length,
+ path_noop,
+ path_offset,
+ path_reverse,
+ path_shiftalong,
+ path_shiftfractionalong,
+ path_smurve,
+ path_smurve_,
+ path_split,
+ path_start,
+ path_translate,
+ path_trim,
+
+ // Point API
+ point_addcircle,
+ point_addtext,
+ point_angle,
+ point_attr,
+ point_clone,
+ point_copy,
+ point_dist,
+ point_dx,
+ point_dy,
+ point_flipx,
+ point_flipy,
+ point_setcircle,
+ point_settext,
+ point_shift,
+ point_shiftfractiontowards,
+ point_shifttowards,
+ point_shiftoutwards,
+ point_sitson,
+ point_sitsroughlyon,
+ point_rotate,
+ point_translate,
+
+ // Snippet API
+ snippet,
+ snippet_attr,
+ snippet_clone,
+
+ // Utils API
+ utils_linesintersect,
+ utils_beamsintersect,
+ utils_beamintersectsx,
+ utils_beamintersectsy,
+ utils_lineintersectscurve,
+ utils_curvesintersect,
+ utils_pointonbeam,
+ utils_pointonline,
+ utils_pointoncurve,
+ utils_circlesintersect,
+ utils_beamintersectscircle,
+ utils_lineintersectscircle,
+ utils_curveintersectsx,
+ utils_curveintersectsy,
+ utils_splitcurve,
+
+ // Plugins
+ plugin_banner,
+ plugin_bartack,
+ plugin_bartackalong,
+ plugin_bartackfractionalong,
+ plugin_buttons,
+ plugin_cutonfold,
+ plugin_dimension,
+ plugin_gore,
+ plugin_grainline,
+ plugin_logo,
+ plugin_mirror,
+ plugin_notches,
+ plugin_round,
+ plugin_sprinkle,
+ plugin_scalebox,
+ plugin_title,
+
+ // Snippets
+ snippet_bnotch,
+ snippet_notch,
+ snippet_button,
+ snippet_buttonhole,
+ snippet_buttonholestart,
+ snippet_buttonholeend,
+ snippet_snapsocket,
+ snippet_snapstud,
+ snippet_logo,
+
+ // Stacks
+ stacks_top,
+ stacks_left,
+ stacks_right,
+ stacks_bottom,
+ stacks_leftEye,
+ stacks_rightEye,
+ stacks_mouth,
+
+ // Settings
+ settings_sa,
+
+ // Docs
+ docs_coords,
+ docs_overview,
+ ],
+ plugins: [pluginBundle, gorePlugin],
+})
+
+// Named exports
+export {
+ // Path API
+ path__curve,
+ path_addclass,
+ path_addtext,
+ path_attr,
+ path_move,
+ path_line,
+ path_curve,
+ path_curve_,
+ path_close,
+ path_ops,
+ path_clone,
+ path_divide,
+ path_edge,
+ path_end,
+ path_intersects,
+ path_intersectsx,
+ path_intersectsy,
+ path_join,
+ path_length,
+ path_noop,
+ path_offset,
+ path_reverse,
+ path_shiftalong,
+ path_shiftfractionalong,
+ path_smurve,
+ path_smurve_,
+ path_split,
+ path_start,
+ path_translate,
+ path_trim,
+
+ // Point API
+ point_addcircle,
+ point_addtext,
+ point_angle,
+ point_attr,
+ point_clone,
+ point_copy,
+ point_dist,
+ point_dx,
+ point_dy,
+ point_flipx,
+ point_flipy,
+ point_setcircle,
+ point_settext,
+ point_shift,
+ point_shiftfractiontowards,
+ point_shifttowards,
+ point_shiftoutwards,
+ point_sitson,
+ point_sitsroughlyon,
+ point_rotate,
+ point_translate,
+
+ // Snippet API
+ snippet,
+ snippet_attr,
+ snippet_clone,
+
+ // Utils API
+ utils_linesintersect,
+ utils_beamsintersect,
+ utils_beamintersectsx,
+ utils_beamintersectsy,
+ utils_lineintersectscurve,
+ utils_curvesintersect,
+ utils_pointonbeam,
+ utils_pointonline,
+ utils_pointoncurve,
+ utils_circlesintersect,
+ utils_beamintersectscircle,
+ utils_lineintersectscircle,
+ utils_curveintersectsx,
+ utils_curveintersectsy,
+ utils_splitcurve,
+
+ // Plugins
+ plugin_banner,
+ plugin_bartack,
+ plugin_bartackalong,
+ plugin_bartackfractionalong,
+ plugin_buttons,
+ plugin_cutonfold,
+ plugin_dimension,
+ plugin_gore,
+ plugin_grainline,
+ plugin_logo,
+ plugin_mirror,
+ plugin_notches,
+ plugin_round,
+ plugin_sprinkle,
+ plugin_scalebox,
+ plugin_title,
+
+ // Snippets
+ snippet_bnotch,
+ snippet_notch,
+ snippet_button,
+ snippet_buttonhole,
+ snippet_buttonholestart,
+ snippet_buttonholeend,
+ snippet_snapsocket,
+ snippet_snapstud,
+ snippet_logo,
+
+ // Stacks
+ stacks_top,
+ stacks_left,
+ stacks_right,
+ stacks_bottom,
+ stacks_leftEye,
+ stacks_rightEye,
+ stacks_mouth,
+
+ // Docs
+ docs_coords,
+ docs_overview,
+ Examples,
+}
diff --git a/designs/examples/src/path.mjs b/designs/examples/src/path.mjs
new file mode 100644
index 00000000000..d3ce4366009
--- /dev/null
+++ b/designs/examples/src/path.mjs
@@ -0,0 +1,752 @@
+import { box } from './shared.mjs'
+
+export const path__curve = {
+ name: 'examples.path__curve',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.from = new Point(5, 20)
+ points.cp2 = new Point(60, 30)
+ points.to = new Point(90, 20)
+
+ paths.line = new Path()
+ .move(points.from)
+ ._curve(points.cp2, points.to)
+ .attr('data-text', 'Path._curve()')
+ .attr('data-text-class', 'text-sm center fill-note')
+
+ return box(part, 100, 25)
+ },
+}
+
+export const path_addclass = {
+ name: 'examples.path_addclass',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.from = new Point(5, 10)
+ points.to = new Point(95, 10)
+
+ paths.line = new Path().move(points.from).line(points.to).addClass('note dashed')
+
+ return box(part, 100, 20)
+ },
+}
+
+export const path_addtext = {
+ name: 'examples.path_addtext',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.from = new Point(5, 10)
+ points.to = new Point(95, 10)
+
+ paths.line = new Path().move(points.from).line(points.to).addText('FreeSewing rocks')
+
+ return box(part, 100, 20)
+ },
+}
+
+export const path_attr = {
+ name: 'examples.path_attr',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.B = new Point(10, 50)
+ points.BCp2 = new Point(40, 10)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, 90)
+
+ paths.example = new Path()
+ .move(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+ .attr('class', 'canvas')
+ .attr('data-text', 'supportFreesewingBecomeAPatron')
+ .attr('data-text-class', 'text-xs center')
+
+ return part
+ },
+}
+
+export const path_clone = {
+ name: 'examples.path_clone',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+
+ paths.example = new Path()
+ .move(points.A)
+ .line(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+
+ paths.clone = paths.example
+ .clone()
+ .setClass('note lashed stroke-xl')
+ .attr('style', 'stroke-opacity: 0.5')
+
+ return part
+ },
+}
+
+export const path_close = {
+ name: 'examples.path_close',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.from = new Point(10, 20)
+ points.cp2 = new Point(60, 30)
+ points.to = new Point(90, 20)
+
+ paths.line = new Path()
+ .move(points.from)
+ ._curve(points.cp2, points.to)
+ .close()
+ .reverse() // To keep text from being upside-down
+ .setText('Path._close()', 'text-sm right fill-note')
+
+ return box(part, 100, 25)
+ },
+}
+
+export const path_curve = {
+ name: 'examples.path_curve',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.from = new Point(10, 20)
+ points.cp1 = new Point(40, 0)
+ points.cp2 = new Point(60, 30)
+ points.to = new Point(90, 20)
+
+ paths.line = new Path()
+ .move(points.from)
+ .curve(points.cp1, points.cp2, points.to)
+ .setText('Path.curve()', 'text-sm center fill-note')
+
+ return box(part, 100, 25)
+ },
+}
+
+export const path_curve_ = {
+ name: 'examples.path_curve_',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.from = new Point(10, 20)
+ points.cp1 = new Point(40, 0)
+ points.to = new Point(90, 20)
+
+ paths.line = new Path()
+ .move(points.from)
+ .curve_(points.cp1, points.to)
+ .attr('data-text', 'Path.curve_()')
+ .attr('data-text-class', 'text-sm center fill-note')
+
+ return box(part, 100, 25)
+ },
+}
+
+export const path_divide = {
+ name: 'examples.path_divide',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.A = new Point(55, 40)
+ points.B = new Point(10, 70)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 60)
+ points.CCp1 = new Point(50, -30)
+ points.D = new Point(50, 80)
+ points.DCp1 = new Point(140, 50)
+
+ paths.example = new Path()
+ .move(points.A)
+ .line(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+ .curve(points.DCp1, points.DCp1, points.D)
+ .close()
+
+ let style = 'stroke-width: 4; stroke-opacity: 0.5;'
+ let i = 0
+ for (let p of paths.example.divide()) {
+ i++
+ paths[i] = p.attr('style', style).attr('style', `stroke: hsl(${i * 70}, 100%, 50%)`)
+ }
+
+ return part
+ },
+}
+
+export const path_edge = {
+ name: 'examples.path_edge',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+ points.D = new Point(-60, 90)
+ points.E = new Point(90, 190)
+
+ paths.demo = new Path()
+ .move(points.A)
+ .line(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+ .curve(points.E, points.D, points.A)
+ .close()
+
+ for (let i of [
+ 'topLeft',
+ 'topRight',
+ 'bottomLeft',
+ 'bottomRight',
+ 'top',
+ 'left',
+ 'bottom',
+ 'right',
+ ])
+ snippets[i] = new Snippet('notch', paths.demo.edge(i))
+
+ return part
+ },
+}
+
+export const path_end = {
+ name: 'examples.path_end',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+
+ paths.demo = new Path().move(points.A).line(points.B).curve(points.BCp2, points.CCp1, points.C)
+
+ snippets.end = new Snippet('notch', paths.demo.end())
+
+ return part
+ },
+}
+
+export const path_intersects = {
+ name: 'examples.path_intersects',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+ points.D = new Point(50, 130)
+ points.DCp1 = new Point(150, 30)
+
+ points._A = new Point(55, 40)
+ points._B = new Point(0, 55)
+ points._BCp2 = new Point(40, -20)
+ points._C = new Point(90, 40)
+ points._CCp1 = new Point(50, -30)
+ points._D = new Point(40, 120)
+ points._DCp1 = new Point(180, 40)
+
+ paths.demo1 = new Path()
+ .move(points.A)
+ .line(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+ .curve(points.DCp1, points.DCp1, points.D)
+ paths.demo2 = new Path()
+ .move(points._A)
+ .line(points._B)
+ .curve(points._BCp2, points._CCp1, points._C)
+ .curve(points._DCp1, points._DCp1, points._D)
+
+ for (let p of paths.demo1.intersects(paths.demo2)) {
+ snippets[part.getId()] = new Snippet('notch', p)
+ }
+
+ return part
+ },
+}
+
+export const path_intersectsx = {
+ name: 'examples.path_intersectsx',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, part }) => {
+ points.A = new Point(95, 50)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+ points.D = new Point(50, 130)
+ points.DCp1 = new Point(150, 30)
+
+ points.top = new Point(60, -10)
+ points.bot = new Point(60, 140)
+
+ paths.line = new Path().move(points.top).line(points.bot).attr('class', 'lining dashed')
+
+ paths.demo = new Path()
+ .move(points.A)
+ .line(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+ .curve(points.DCp1, points.DCp1, points.D)
+
+ for (let p of paths.demo.intersectsX(60)) {
+ snippets[part.getId()] = new Snippet('notch', p)
+ }
+
+ return part
+ },
+}
+
+export const path_intersectsy = {
+ name: 'examples.path_intersectsy',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, part }) => {
+ points.A = new Point(55, 40)
+ points.B = new Point(10, 70)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 60)
+ points.CCp1 = new Point(50, -30)
+ points.D = new Point(50, 80)
+ points.DCp1 = new Point(140, 50)
+
+ points.top = new Point(10, 58)
+ points.bot = new Point(130, 58)
+
+ paths.line = new Path().move(points.top).line(points.bot).attr('class', 'lining dashed')
+
+ paths.demo = new Path()
+ .move(points.A)
+ .line(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+ .curve(points.DCp1, points.DCp1, points.D)
+ for (let p of paths.demo.intersectsY(58)) {
+ snippets[part.getId()] = new Snippet('notch', p)
+ }
+
+ return part
+ },
+}
+
+export const path_join = {
+ name: 'examples.path_join',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+
+ paths.path1 = new Path().move(points.A).line(points.B).attr('class', 'various')
+
+ paths.path2 = new Path()
+ .move(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+ .attr('class', 'canvas')
+
+ paths.joint = paths.path1
+ .join(paths.path2)
+ .attr('class', 'note lashed stroke-l')
+ .attr('style', 'stroke-opacity: 0.5')
+
+ return part
+ },
+}
+
+export const path_length = {
+ name: 'examples.path_length',
+ draft: ({ Point, points, Path, paths, macro, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+
+ paths.example = new Path()
+ .move(points.A)
+ .line(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+
+ macro('pd', {
+ path: paths.example,
+ d: -20,
+ })
+
+ macro('pd', {
+ path: new Path().move(points.B).line(points.A),
+ d: 10,
+ })
+
+ macro('pd', {
+ path: new Path().move(points.B).curve(points.BCp2, points.CCp1, points.C),
+ d: -10,
+ })
+
+ return part
+ },
+}
+
+export const path_line = {
+ name: 'examples.path_line',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.from = new Point(10, 10)
+ points.to = new Point(90, 10)
+
+ paths.line = new Path()
+ .move(points.from)
+ .line(points.to)
+ .attr('data-text', 'Path.line()')
+ .attr('data-text-class', 'text-sm center fill-note')
+
+ return box(part, 100, 15)
+ },
+}
+
+export const path_move = {
+ name: 'examples.path_move',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.to = new Point(50, 10)
+ .attr('data-text', 'Path.move()')
+ .attr('data-text-class', 'fill-note center')
+
+ paths.noline = new Path().move(points.to)
+
+ return box(part, 100, 15)
+ },
+}
+
+export const path_noop = {
+ name: 'examples.path_noop',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.left = new Point(10, 10)
+ points.dartLeft = new Point(40, 10)
+ points.dartTip = new Point(50, 50)
+ points.dartRight = new Point(60, 10)
+ points.right = new Point(90, 10)
+
+ paths.without = new Path()
+ .move(points.left)
+ .line(points.dartLeft)
+ .noop('dart')
+ .line(points.right)
+
+ paths.withDart = paths.without
+ .insop('dart', new Path().line(points.dartTip).line(points.dartRight))
+ .attr('style', 'stroke-width: 2px; stroke-opacity: 0.5; stroke: orange;')
+
+ return part
+ },
+}
+
+export const path_offset = {
+ name: 'examples.path_offset',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+
+ paths.example = new Path()
+ .move(points.A)
+ .line(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+ .line(points.A)
+ .close()
+
+ paths.offset = paths.example.offset(-10).attr('class', 'interfacing')
+
+ paths.lineOffset = new Path().move(points.A).line(points.B).offset(-5).attr('class', 'various')
+
+ paths.curveOffset = new Path()
+ .move(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+ .offset(-5)
+ .attr('class', 'canvas')
+
+ return part
+ },
+}
+
+export const path_ops = {
+ name: 'examples.path_ops',
+ draft: ({ Point, points, Path, paths, options, part }) => {
+ const textClasses = (label) =>
+ options.focus === label ? 'center text-xs fill-note' : 'center text-xs'
+
+ points.A = new Point(10, 10)
+ .attr('data-text', 'Path.move()')
+ .attr('data-text-class', textClasses('move'))
+ points.B = new Point(70, 30)
+ points.BCp2 = new Point(40, 10)
+ points.C = new Point(90, -50)
+ points.CCp1 = new Point(125, -30)
+ points.D = new Point(20, -50)
+ points.DCp = new Point(40, 0)
+ points.E = new Point(-20, -20)
+ points.ECp = new Point(-20, -50)
+
+ paths.line = new Path()
+ .move(points.A)
+ .line(points.B)
+ .attr('data-text', 'Path.line()')
+ .attr('data-text-class', textClasses('line'))
+
+ paths.curve = new Path()
+ .move(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+ .attr('data-text', 'Path.curve()')
+ .attr('data-text-class', textClasses('curve'))
+
+ paths._curve = new Path()
+ .move(points.C)
+ ._curve(points.DCp, points.D)
+ .attr('data-text', 'Path._curve()')
+ .attr('data-text-class', textClasses('_curve'))
+
+ paths.curve_ = new Path()
+ .move(points.D)
+ .curve_(points.ECp, points.E)
+ .attr('data-text', 'Path.curve_()')
+ .attr('data-text-class', textClasses('curve_'))
+
+ paths.close = new Path()
+ .move(points.E)
+ .line(points.A)
+ .attr('data-text', 'Path.close()')
+ .attr('data-text-class', textClasses('close'))
+
+ paths.example = paths.line.join(paths.curve).join(paths._curve).join(paths.curve_).close()
+
+ return part
+ },
+}
+
+export const path_reverse = {
+ name: 'examples.path_reverse',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+
+ paths.example = new Path()
+ .move(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+ .attr('data-text', 'freesewingIsMadeByJoostDeCockAndContributors')
+ .attr('data-text-class', 'text-xs fill-note')
+
+ paths.reverse = paths.example
+ .reverse()
+ .attr('data-text', 'freesewingIsMadeByJoostDeCockAndContributors')
+ .attr('data-text-class', 'text-xs fill-lining')
+
+ return part
+ },
+}
+
+export const path_shiftalong = {
+ name: 'examples.path_shiftalong',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+
+ paths.example = new Path()
+ .move(points.A)
+ .line(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+
+ points.x1 = paths.example
+ .shiftAlong(20)
+ .attr('data-text', '2cm')
+ .attr('data-text-class', 'center fill-note')
+ .attr('data-text-lineheight', 6)
+ points.x2 = paths.example
+ .shiftAlong(90)
+ .attr('data-text', '9cm')
+ .attr('data-text-class', 'center fill-note')
+ .attr('data-text-lineheight', 6)
+
+ snippets.x1 = new Snippet('notch', points.x1)
+ snippets.x2 = new Snippet('notch', points.x2)
+
+ return part
+ },
+}
+
+export const path_shiftfractionalong = {
+ name: 'examples.path_shiftfractionalong',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+
+ paths.example = new Path()
+ .move(points.A)
+ .line(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+
+ points.x1 = paths.example
+ .shiftFractionAlong(0.2)
+ .attr('data-text', '20%')
+ .attr('data-text-class', 'center fill-note')
+ .attr('data-text-lineheight', 6)
+ points.x2 = paths.example
+ .shiftFractionAlong(0.9)
+ .attr('data-text', '90%')
+ .attr('data-text-class', 'center fill-note')
+ .attr('data-text-lineheight', 6)
+
+ snippets.xl = new Snippet('notch', points.x1)
+ snippets.x2 = new Snippet('notch', points.x2)
+
+ return part
+ },
+}
+
+export const path_smurve = {
+ name: 'examples.path_smurve',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.from = new Point(10, 20)
+ points.cp1 = new Point(40, 10)
+ points.cp2 = new Point(60, 30)
+ points.to = new Point(90, 20)
+ points.scp2 = new Point(140, 30)
+ points.sto = new Point(170, 20)
+
+ paths.line = new Path()
+ .move(points.from)
+ .curve(points.cp1, points.cp2, points.to)
+ .smurve(points.scp2, points.sto)
+ .attr('data-text', 'Path.smurve()')
+ .attr('data-text-class', 'text-sm center fill-note')
+
+ return box(part, 180, 40)
+ },
+}
+
+export const path_smurve_ = {
+ name: 'examples.path_smurve_',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.from = new Point(10, 20)
+ points.cp1 = new Point(40, 10)
+ points.cp2 = new Point(60, 30)
+ points.to = new Point(90, 20)
+ points.sto = new Point(170, 20)
+
+ paths.line = new Path()
+ .move(points.from)
+ .curve(points.cp1, points.cp2, points.to)
+ .smurve_(points.sto)
+ .attr('data-text', 'Path.smurve_()')
+ .attr('data-text-class', 'text-sm center fill-note')
+
+ return box(part, 180, 40)
+ },
+}
+
+export const path_split = {
+ name: 'examples.path_split',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+ points.D = new Point(50, 130)
+ points.DCp1 = new Point(150, 30)
+
+ paths.demo = new Path()
+ .move(points.D)
+ .curve(points.DCp1, points.DCp1, points.C)
+ .curve(points.CCp1, points.BCp2, points.B)
+ .line(points.A)
+
+ points.split = paths.demo.shiftFractionAlong(0.75)
+ snippets.x = new Snippet('x', points.split)
+
+ let style = 'stroke-width: 3; stroke-opacity: 0.5;'
+ let halves = paths.demo.split(points.split)
+ for (let i in halves) {
+ paths[i] = halves[i].attr('style', style).attr('style', `stroke: hsl(${i * 70}, 100%, 50%)`)
+ }
+
+ return part
+ },
+}
+
+export const path_start = {
+ name: 'examples.path_start',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+
+ paths.example = new Path()
+ .move(points.A)
+ .line(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+
+ snippets.start = new Snippet('notch', paths.example.start())
+ return part
+ },
+}
+
+export const path_translate = {
+ name: 'examples.path_translate',
+ draft: ({ Point, points, Path, paths, macro, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+
+ paths.A = new Path().move(points.A).line(points.B).curve(points.BCp2, points.CCp1, points.C)
+
+ paths.B = paths.A.translate(60, 30)
+
+ points.step1 = points.B.shift(0, 60)
+ points.step2 = points.step1.shift(-90, 30)
+ macro('ld', {
+ from: points.B,
+ to: points.step1,
+ noStartMarker: true,
+ })
+ macro('ld', {
+ from: points.step1,
+ to: points.step2,
+ noStartMarker: true,
+ })
+
+ return part
+ },
+}
+
+export const path_trim = {
+ name: 'examples.path_trim',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.center = new Point(0, 0)
+ points.base = new Point(0, 10)
+ points.tip = new Point(0, 50)
+ points.tipCpRight = new Point(30, 50)
+ points.tipCpLeft = new Point(-30, 50)
+ paths.example = new Path().move(points.base)
+ for (let i = 0; i < 4; i++) {
+ points['base' + i] = points.base.rotate(60 * i, points.center)
+ points['tip' + i] = points.tip.rotate(60 * i, points.center)
+ points['tipCpRight' + i] = points.tipCpRight.rotate(60 * i, points.center)
+ points['tipCpLeft' + i] = points.tipCpLeft.rotate(60 * i, points.center)
+ if (i < 2) {
+ paths.example
+ .line(points['base' + i])
+ .curve(points['base' + i], points['tipCpLeft' + i], points['tip' + i])
+ .curve(points['tipCpRight' + i], points['base' + i], points['base' + i])
+ } else {
+ paths.example
+ .line(points['base' + i])
+ .line(points['tip' + i])
+ .line(points['tipCpRight' + i])
+ .line(points['base' + i])
+ }
+ }
+
+ paths.offset = paths.example.offset(10).attr('class', 'lining dotted stroke-sm')
+
+ paths.trimmed = paths.offset
+ .trim()
+ .attr('class', 'various stroke-xl')
+ .attr('style', 'stroke-opacity: 0.5;')
+ return part
+ },
+}
diff --git a/designs/examples/src/plugins.mjs b/designs/examples/src/plugins.mjs
new file mode 100644
index 00000000000..e8f436e8f94
--- /dev/null
+++ b/designs/examples/src/plugins.mjs
@@ -0,0 +1,342 @@
+import { box } from './shared.mjs'
+
+export const plugin_banner = {
+ name: 'examples.plugin_banner',
+ draft: ({ points, Point, paths, Path, macro, part }) => {
+ points.from = new Point(0, 0)
+ points.to = new Point(320, 0)
+
+ paths.banner = new Path().move(points.from).line(points.to)
+
+ macro('banner', {
+ path: paths.banner,
+ text: 'banner plugin',
+ })
+
+ // Prevent clipping of text
+ paths.box = new Path().move(new Point(0, -20)).line(new Point(0, 20)).attr('class', 'hidden')
+
+ return part
+ },
+}
+
+export const plugin_bartack = {
+ name: 'examples.plugin_bartack',
+ draft: ({ Point, points, macro, part }) => {
+ points.a = new Point(15, 15)
+
+ macro('bartack', {
+ anchor: points.a,
+ angle: 30,
+ length: 15,
+ })
+
+ return box(part, 60, 30)
+ },
+}
+
+export const plugin_bartackalong = {
+ name: 'examples.plugin_bartackalong',
+ draft: ({ Point, Path, points, paths, macro, part }) => {
+ points.a = new Point(15, 15)
+ points.b = new Point(20, 20)
+ points.c = new Point(30, 20)
+ points.d = new Point(35, 15)
+ points.e = new Point(20, 10)
+ points.f = new Point(30, 10)
+
+ paths.a = new Path().move(points.a).curve(points.b, points.c, points.d).hide()
+
+ macro('bartackAlong', {
+ path: paths.a,
+ })
+
+ macro('sprinkle', {
+ snippet: 'notch',
+ on: ['e', 'f'],
+ })
+
+ return box(part, 60, 30)
+ },
+}
+
+export const plugin_bartackfractionalong = {
+ name: 'examples.plugin_bartackfractionalong',
+ draft: ({ Point, Path, points, paths, macro, part }) => {
+ points.a = new Point(15, 15)
+ points.b = new Point(20, 20)
+ points.c = new Point(30, 20)
+ points.d = new Point(35, 15)
+ points.e = new Point(20, 10)
+ points.f = new Point(30, 10)
+
+ paths.a = new Path().move(points.a).curve(points.b, points.c, points.d).hide()
+
+ macro('bartackFractionAlong', {
+ path: paths.a,
+ start: 0.2,
+ end: 0.8,
+ })
+
+ macro('sprinkle', {
+ snippet: 'notch',
+ on: ['e', 'f'],
+ })
+
+ return box(part, 60, 30)
+ },
+}
+
+export const plugin_buttons = {
+ name: 'examples.plugin_buttons',
+ draft: ({ Point, snippets, Snippet, part }) => {
+ snippets.button = new Snippet('button', new Point(20, 10))
+ snippets.buttonhole = new Snippet('buttonhole', new Point(40, 10))
+ snippets.buttonholeStart = new Snippet('buttonhole-start', new Point(60, 10))
+ snippets.buttonholeEnd = new Snippet('buttonhole-end', new Point(80, 10))
+ snippets.snapMale = new Snippet('snap-stud', new Point(100, 10))
+ snippets.snapFemale = new Snippet('snap-socket', new Point(120, 10))
+
+ return box(part, 140, 20)
+ },
+}
+
+export const plugin_cutonfold = {
+ name: 'examples.plugin_cutonfold',
+ draft: ({ Point, points, Path, paths, macro, part }) => {
+ points.topLeft = new Point(0, 0)
+ points.topRight = new Point(150, 0)
+ points.bottomRight = new Point(150, 30)
+ points.bottomLeft = new Point(0, 30)
+
+ paths.box = new Path()
+ .move(points.topLeft)
+ .line(points.topRight)
+ .line(points.bottomRight)
+ .line(points.bottomLeft)
+ .close()
+
+ macro('cutonfold', {
+ from: points.bottomLeft,
+ to: points.bottomRight,
+ grainline: true,
+ })
+
+ return part
+ },
+}
+
+export const plugin_dimension = {
+ name: 'examples.plugin_dimension',
+ draft: ({ Point, points, Path, paths, macro, part }) => {
+ points.A = new Point(0, 0)
+ points.B = new Point(0, 100)
+ points.C = new Point(50, 100)
+ points.D = new Point(100, 50)
+ points.DCp1 = new Point(100, 0)
+
+ paths.box = new Path()
+ .move(points.A)
+ .line(points.B)
+ .line(points.C)
+ .line(points.D)
+ .curve(points.DCp1, points.A, points.A)
+ .close()
+
+ macro('vd', {
+ from: points.A,
+ to: points.B,
+ x: points.A.x - 15,
+ })
+
+ macro('hd', {
+ from: points.B,
+ to: points.C,
+ y: points.B.y + 15,
+ })
+
+ macro('ld', {
+ from: points.C,
+ to: points.D,
+ d: -15,
+ })
+
+ macro('ld', {
+ from: points.C,
+ to: points.D,
+ d: -30,
+ text: 'Custom text',
+ })
+
+ macro('pd', {
+ path: new Path().move(points.A).curve(points.A, points.DCp1, points.D),
+ d: -15,
+ })
+
+ return part
+ },
+}
+
+export const plugin_gore = {
+ name: 'examples.plugin_gore',
+ draft: ({ Point, points, macro, part }) => {
+ points.anchor = new Point(0, 0)
+
+ macro('gore', {
+ from: points.anchor,
+ radius: 60,
+ gores: 5,
+ extraLength: 20,
+ })
+
+ return part
+ },
+}
+
+export const plugin_grainline = {
+ name: 'examples.plugin_grainline',
+ draft: ({ Point, points, macro, part }) => {
+ points.grainlineFrom = new Point(10, 10)
+ points.grainlineTo = new Point(100, 10)
+
+ macro('grainline', {
+ from: points.grainlineFrom,
+ to: points.grainlineTo,
+ })
+
+ return box(part, 110, 15)
+ },
+}
+
+export const plugin_logo = {
+ name: 'examples.plugin_logo',
+ draft: ({ points, Point, snippets, Snippet, part }) => {
+ points.anchor = new Point(50, 25)
+
+ snippets.logo = new Snippet('logo', points.anchor).attr('data-scale', 0.666)
+
+ return box(part, 100, 35)
+ },
+}
+
+export const plugin_mirror = {
+ name: 'examples.plugin_mirror',
+ draft: ({ Point, Path, points, paths, macro, part }) => {
+ points.a = new Point(5, 5)
+ points.b = new Point(45, 30)
+ points.c = new Point(5, 30)
+ points.d = new Point(45, 5)
+ points.mid = new Point(25, 15)
+
+ paths.a = new Path().move(points.a).curve(points.b, points.c, points.d)
+
+ macro('mirror', {
+ mirror: [points.b, points.d],
+ points: [points.mid],
+ paths: [paths.a],
+ })
+
+ macro('sprinkle', {
+ snippet: 'notch',
+ on: ['mid', 'mirroredMid'],
+ })
+
+ return box(part, 100, 40)
+ },
+}
+
+export const plugin_notches = {
+ name: 'examples.plugin_notches',
+ draft: ({ Point, snippets, Snippet, part }) => {
+ snippets.notch = new Snippet('notch', new Point(60, 10))
+ snippets.bnotch = new Snippet('bnotch', new Point(80, 10))
+
+ return box(part, 140, 20)
+ },
+}
+
+export const plugin_round = {
+ name: 'examples.plugin_round',
+ draft: ({ Point, points, Path, paths, macro, part }) => {
+ points.topLeft = new Point(0, 0)
+ points.bottomLeft = new Point(0, 30)
+ points.topRight = new Point(100, 0)
+ points.bottomRight = new Point(100, 30)
+
+ paths.demo = new Path()
+ .move(points.topLeft)
+ .line(points.bottomLeft)
+ .line(points.bottomRight)
+ .line(points.topRight)
+ .close()
+ .attr('class', 'note dashed')
+
+ macro('round', {
+ from: points.topLeft,
+ to: points.bottomRight,
+ via: points.bottomLeft,
+ radius: 10,
+ prefix: 'bl',
+ })
+ macro('round', {
+ from: points.bottomRight,
+ to: points.topLeft,
+ via: points.topRight,
+ radius: 20,
+ prefix: 'tr',
+ })
+
+ return part
+ },
+}
+
+export const plugin_scalebox = {
+ name: 'examples.plugin_scalebox',
+ draft: ({ Point, points, macro, part }) => {
+ points.anchor1 = new Point(0, 0)
+ points.anchor2 = new Point(70, 0)
+
+ macro('scalebox', { at: points.anchor1 })
+ macro('miniscale', { at: points.anchor2 })
+
+ return part
+ },
+}
+
+export const plugin_sprinkle = {
+ name: 'examples.plugin_sprinkle',
+ draft: ({ Point, points, macro, part }) => {
+ points.a = new Point(10, 10)
+ points.b = new Point(20, 15)
+ points.c = new Point(30, 10)
+ points.d = new Point(40, 15)
+ points.e = new Point(50, 10)
+ points.f = new Point(60, 15)
+ points.g = new Point(70, 10)
+ points.h = new Point(80, 15)
+ points.i = new Point(90, 10)
+
+ macro('sprinkle', {
+ snippet: 'button',
+ on: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'],
+ })
+
+ return box(part, 100, 25)
+ },
+}
+
+export const plugin_title = {
+ name: 'examples.plugin_title',
+ draft: ({ Point, points, macro, part }) => {
+ points.title = new Point(90, 45)
+
+ macro('title', {
+ at: points.title,
+ nr: 4,
+ title: 'sleeve',
+ prefix: 'test',
+ })
+
+ return box(part, 200, 70)
+ },
+}
diff --git a/designs/examples/src/point.mjs b/designs/examples/src/point.mjs
new file mode 100644
index 00000000000..ef299c4d345
--- /dev/null
+++ b/designs/examples/src/point.mjs
@@ -0,0 +1,457 @@
+import { box } from './shared.mjs'
+
+export const point_addcircle = {
+ name: 'examples.point_addcircle',
+ draft: ({ Point, points, part }) => {
+ points.a = new Point(30, 10).addCircle(3, 'lining dashed').addCircle(7, 'mark dashed')
+
+ points.b = new Point(50, 10)
+ .addCircle(1, 'interfacing')
+ .addCircle(3, 'fabric')
+ .addCircle(5, 'lining')
+ .addCircle(7, 'mark')
+ .addCircle(9, 'note')
+
+ points.c = new Point(70, 10).addCircle(3, 'interfacing').addCircle(7, 'mark lashed')
+ return box(part, 100, 20)
+ },
+}
+
+export const point_addtext = {
+ name: 'examples.point_addtext',
+ draft: ({ Point, points, part }) => {
+ points.anchor = new Point(100, 25)
+ .addText('supportFreesewingBecomeAPatron', 'center')
+ .addText('please?')
+
+ return box(part, 200, 50)
+ },
+}
+
+export const point_angle = {
+ name: 'examples.point_angle',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.sun = new Point(10, 5)
+ points.moon = points.sun.shift(-15, 70)
+ points.text = points.sun
+ .shiftFractionTowards(points.moon, 0.8)
+ .attr('data-text', points.sun.angle(points.moon) + 'Β°')
+ .attr('data-text-class', 'text-sm fill-note center')
+
+ paths.line = new Path().move(points.sun).line(points.moon).attr('class', 'dashed')
+
+ return part
+ },
+}
+
+export const point_attr = {
+ name: 'examples.point_attr',
+ draft: ({ Point, points, part }) => {
+ points.anchor = new Point(100, 25)
+ .attr('data-text', 'supportFreesewingBecomeAPatron')
+ .attr('data-text-class', 'center')
+
+ return box(part, 200, 50)
+ },
+}
+
+export const point_clone = {
+ name: 'examples.point_clone',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.A = new Point(25, 25)
+ .attr('data-text', 'Point A')
+ .attr('data-text-class', 'text-xl')
+ .attr('data-text-fill-opacity', '0.5')
+ points.B = points.A.clone().attr('data-text', 'Point B')
+
+ snippets.x = new Snippet('notch', points.A)
+
+ return box(part)
+ },
+}
+
+export const point_copy = {
+ name: 'examples.point_copy',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.A = new Point(50, 25).attr('data-text', 'Point A').attr('data-text-class', 'text-xl')
+ points.B = points.A.copy().attr('data-text', 'Point B')
+
+ snippets.x = new Snippet('notch', points.A)
+
+ return box(part)
+ },
+}
+
+export const point_dist = {
+ name: 'examples.point_dist',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.from = new Point(10, 10)
+ points.to = new Point(80, 70)
+
+ points.text = points.from
+ .shiftFractionTowards(points.to, 0.6)
+ .attr('data-text', points.from.dist(points.to) + 'mm')
+ .attr('data-text-class', 'text-sm fill-note center')
+
+ paths.line = new Path().move(points.from).line(points.to).attr('class', 'dashed')
+
+ return part
+ },
+}
+
+export const point_dx = {
+ name: 'examples.point_dx',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.from = new Point(10, 10)
+ points.to = new Point(80, 70)
+
+ paths.line = new Path().move(points.from).line(points.to).attr('class', 'dashed')
+
+ points.totop = points.from.shift(0, points.from.dx(points.to))
+
+ points.text_dx = points.from
+ .shiftFractionTowards(points.totop, 0.6)
+ .shiftFractionTowards(points.to, 0.1)
+ .attr('data-text', points.from.dx(points.to) + 'mm')
+ .attr('data-text-class', 'text-sm fill-note center')
+
+ paths.line_dx = new Path().move(points.from).line(points.totop).attr('class', 'dashed')
+
+ paths.line_dy = new Path().move(points.to).line(points.totop).attr('class', 'dashed')
+
+ return part
+ },
+}
+
+export const point_dy = {
+ name: 'examples.point_dy',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.from = new Point(10, 10)
+ points.to = new Point(80, 70)
+
+ paths.line = new Path().move(points.from).line(points.to).attr('class', 'dashed')
+
+ points.totop = points.from.shift(0, points.from.dx(points.to))
+
+ paths.line_dx = new Path().move(points.from).line(points.totop).attr('class', 'dashed')
+
+ points.text_dy = points.totop
+ .shiftFractionTowards(points.to, 0.4)
+ .attr('data-text', points.from.dy(points.to) + 'mm')
+ .attr('data-text-class', 'text-sm fill-note right')
+
+ paths.line_dy = new Path().move(points.to).line(points.totop).attr('class', 'dashed')
+
+ return part
+ },
+}
+
+export const point_flipx = {
+ name: 'examples.point_flipy',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.top = new Point(50, 10)
+ points.out1 = new Point(70, 30)
+ points.in1 = new Point(55, 35)
+ points.out2 = new Point(75, 50)
+ points.in2 = new Point(60, 55)
+ points.out3 = new Point(80, 70)
+ points.in3 = new Point(55, 70)
+ points.trunkOut = new Point(55, 80)
+ points.trunkIn = new Point(50, 80)
+
+ points._out1 = points.out1.flipX(points.top)
+ points._in1 = points.in1.flipX(points.top)
+ points._out2 = points.out2.flipX(points.top)
+ points._in2 = points.in2.flipX(points.top)
+ points._out3 = points.out3.flipX(points.top)
+ points._in3 = points.in3.flipX(points.top)
+ points._trunkOut = points.trunkOut.flipX(points.top)
+
+ points.bottom = new Point(50, 80)
+
+ paths.tree = new Path()
+ .move(points.top)
+ .line(points.out1)
+ .line(points.in1)
+ .line(points.out2)
+ .line(points.in2)
+ .line(points.out3)
+ .line(points.in3)
+ .line(points.trunkOut)
+ .line(points._trunkOut)
+ .line(points._in3)
+ .line(points._out3)
+ .line(points._in2)
+ .line(points._out2)
+ .line(points._in1)
+ .line(points._out1)
+ .close()
+
+ paths.mirror = new Path().move(points.top).line(points.bottom).attr('class', 'note dashed')
+
+ return part
+ },
+}
+
+export const point_flipy = {
+ name: 'examples.point_flipy',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.start = new Point(0, 50)
+ points.churchTowerWallLeft = new Point(10, 50)
+ points.churchTowerRoofLeft = new Point(10, 30)
+ points.churchTowerTop = new Point(15, 10)
+ points.churchTowerRoofRight = new Point(20, 30)
+ points.churchRoofRight = new Point(50, 30)
+ points.churchWallRight = new Point(50, 50)
+ points.houseWallLeft = new Point(65, 50)
+ points.houseRoofLeft = new Point(65, 35)
+ points.houseRoofTop = new Point(75, 25)
+ points.houseRoofRight = new Point(85, 35)
+ points.houseWallRight = new Point(85, 50)
+ points.end = new Point(95, 50)
+
+ points.mirror = new Point(0, 60)
+ points.mirrorLineEnd = new Point(95, 60)
+
+ points._start = points.start.flipY(points.mirror)
+ points._churchTowerWallLeft = points.churchTowerWallLeft.flipY(points.mirror)
+ points._churchTowerRoofLeft = points.churchTowerRoofLeft.flipY(points.mirror)
+ points._churchTowerTop = points.churchTowerTop.flipY(points.mirror)
+ points._churchTowerRoofRight = points.churchTowerRoofRight.flipY(points.mirror)
+ points._churchRoofRight = points.churchRoofRight.flipY(points.mirror)
+ points._churchWallRight = points.churchWallRight.flipY(points.mirror)
+ points._houseWallLeft = points.houseWallLeft.flipY(points.mirror)
+ points._houseRoofLeft = points.houseRoofLeft.flipY(points.mirror)
+ points._houseRoofTop = points.houseRoofTop.flipY(points.mirror)
+ points._houseRoofRight = points.houseRoofRight.flipY(points.mirror)
+ points._houseWallRight = points.houseWallRight.flipY(points.mirror)
+ points._end = points.end.flipY(points.mirror)
+
+ paths.skylineTop = new Path()
+ .move(points.start)
+ .line(points.churchTowerWallLeft)
+ .line(points.churchTowerRoofLeft)
+ .line(points.churchTowerTop)
+ .line(points.churchTowerRoofRight)
+ .line(points.churchRoofRight)
+ .line(points.churchWallRight)
+ .line(points.houseWallLeft)
+ .line(points.houseRoofLeft)
+ .line(points.houseRoofTop)
+ .line(points.houseRoofRight)
+ .line(points.houseWallRight)
+ .line(points.end)
+
+ paths.skylineBottom = new Path()
+ .move(points._start)
+ .line(points._churchTowerWallLeft)
+ .line(points._churchTowerRoofLeft)
+ .line(points._churchTowerTop)
+ .line(points._churchTowerRoofRight)
+ .line(points._churchRoofRight)
+ .line(points._churchWallRight)
+ .line(points._houseWallLeft)
+ .line(points._houseRoofLeft)
+ .line(points._houseRoofTop)
+ .line(points._houseRoofRight)
+ .line(points._houseWallRight)
+ .line(points._end)
+
+ paths.mirrorLine = new Path()
+ .move(points.mirror)
+ .line(points.mirrorLineEnd)
+ .attr('class', 'note dashed')
+
+ return part
+ },
+}
+
+export const point_rotate = {
+ name: 'examples.point_rotate',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.sun = new Point(40, 40)
+ points.moon = new Point(70, 40)
+ let step = 360 / 36
+ for (let i = 1; i < 37; i++) {
+ let angle = step * i
+ points[`moon${i}`] = points.moon.rotate(angle, points.sun)
+ paths[`moon${i}`] = new Path().move(points.sun).line(points[`moon${i}`])
+ }
+
+ return part
+ },
+}
+
+export const point_setcircle = {
+ name: 'examples.point_setcircle',
+ draft: ({ Point, points, part }) => {
+ points.a = new Point(30, 10).setCircle(3, 'lining dashed').setCircle(7, 'mark dashed')
+
+ points.b = new Point(50, 10)
+ .setCircle(1, 'interfacing')
+ .setCircle(3, 'fabric')
+ .setCircle(5, 'lining')
+ .setCircle(7, 'mark')
+ .setCircle(9, 'note')
+
+ points.c = new Point(70, 10).setCircle(3, 'interfacing').setCircle(7, 'mark lashed')
+ return box(part, 100, 20)
+ },
+}
+
+export const point_settext = {
+ name: 'examples.point_settext',
+ draft: ({ Point, points, part }) => {
+ points.anchor = new Point(100, 25)
+ .setText('supportFreesewingBecomeAPatron', 'center')
+ .setText('please?')
+
+ return box(part, 200, 50)
+ },
+}
+
+export const point_shift = {
+ name: 'examples.point_shift',
+ draft: ({ Point, points, macro, part }) => {
+ points.A = new Point(90, 40).attr('data-text', 'Point A').attr('data-text-class', 'right')
+ points.B = points.A.shift(155, 70)
+ .attr('data-text', 'Point B is point A shifted 7cm\nat a 155 degree angle')
+ .attr('data-text-lineheight', 6)
+
+ macro('ld', {
+ from: points.B,
+ to: points.A,
+ d: -10,
+ })
+
+ return box(part, 100, 45)
+ },
+}
+
+export const point_shiftfractiontowards = {
+ name: 'examples.point_shiftfractiontowards',
+ draft: ({ Point, points, Path, paths, macro, part }) => {
+ points.A = new Point(90, 70).attr('data-text', 'Point A')
+ points.B = new Point(10, 10).attr('data-text', 'Point B')
+ points.C = points.A.shiftFractionTowards(points.B, 0.5)
+ .attr('data-text', 'Point C is point A shifted 50%\nin the direction of point B')
+ .attr('data-text-class', 'center')
+ .attr('data-text-lineheight', 6)
+
+ paths.direction = new Path().move(points.A).line(points.B).attr('class', 'note dashed')
+
+ macro('ld', {
+ from: points.C,
+ to: points.A,
+ d: -10,
+ })
+
+ macro('ld', {
+ from: points.B,
+ to: points.A,
+ d: 20,
+ })
+
+ return part
+ },
+}
+
+export const point_shiftoutwards = {
+ name: 'examples.point_shiftoutwards',
+ draft: ({ Point, points, Path, paths, macro, part }) => {
+ points.A = new Point(90, 70).attr('data-text', 'Point A')
+ points.B = new Point(30, 30).attr('data-text', 'Point B')
+ points.C = points.A.shiftOutwards(points.B, 30)
+ .attr('data-text', 'Point C is point A shifted 3cm\nbeyond point B')
+ .attr('data-text-lineheight', 6)
+
+ paths.direction = new Path().move(points.A).line(points.C).attr('class', 'note dashed')
+
+ macro('ld', {
+ from: points.C,
+ to: points.B,
+ d: -10,
+ })
+
+ return box(part, 110, 75)
+ },
+}
+
+export const point_shifttowards = {
+ name: 'examples.point_shifttowards',
+ draft: ({ Point, points, Path, paths, macro, part }) => {
+ points.A = new Point(90, 70).attr('data-text', 'Point A')
+ points.B = new Point(10, 10).attr('data-text', 'Point B')
+ points.C = points.A.shiftTowards(points.B, 35)
+ .attr('data-text', 'Point C is point A shifted 3.5cm\nin the direction of point B')
+ .attr('data-text-class', 'center')
+ .attr('data-text-lineheight', 6)
+
+ paths.direction = new Path().move(points.A).line(points.B).attr('class', 'note dashed')
+
+ macro('ld', {
+ from: points.C,
+ to: points.A,
+ d: -10,
+ })
+
+ return box(part, 110, 80)
+ },
+}
+
+export const point_sitson = {
+ name: 'examples.point_sitson',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ let s
+ for (let i = 0; i < 10; i++) {
+ points[`a${i}`] = new Point(i * 10, 40)
+ points[`b${i}`] = new Point(i * 10, i * 8)
+ if (points[`a${i}`].sitsOn(points[`b${i}`])) s = 'notch'
+ else s = 'bnotch'
+ snippets[`b${i}`] = new Snippet(s, points[`b${i}`])
+ snippets[`a${i}`] = new Snippet(s, points[`a${i}`])
+ }
+
+ return box(part)
+ },
+}
+
+export const point_sitsroughlyon = {
+ name: 'examples.point_sitsroughlyon',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ box(part)
+
+ let s
+ for (let i = 0; i < 10; i++) {
+ points[`a${i}`] = new Point(i * 10, 40)
+ points[`b${i}`] = new Point(i * 10, i * 8)
+ if (points[`a${i}`].sitsRoughlyOn(points[`b${i}`])) s = 'notch'
+ else s = 'bnotch'
+ snippets[`b${i}`] = new Snippet(s, points[`b${i}`])
+ snippets[`a${i}`] = new Snippet(s, points[`a${i}`])
+ }
+
+ return part
+ },
+}
+
+export const point_translate = {
+ name: 'examples.point_translate',
+ draft: ({ Point, points, macro, part }) => {
+ points.A = new Point(20, 20).attr('data-text', 'Point A')
+ points.B = points.A.translate(120, 60)
+ .attr('data-text', 'Point B is point A with a\ntranslate(120, 60)\ntransform applied')
+ .attr('data-text-class', 'right')
+ .attr('data-text-dy', -6)
+ .attr('data-text-lineheight', 6)
+
+ macro('ld', {
+ from: points.A,
+ to: points.B,
+ text: 'translate(120,60)',
+ noStartMarker: true,
+ })
+
+ return box(part, 150, 85)
+ },
+}
diff --git a/designs/examples/src/settings.mjs b/designs/examples/src/settings.mjs
new file mode 100644
index 00000000000..a8ffe9ea574
--- /dev/null
+++ b/designs/examples/src/settings.mjs
@@ -0,0 +1,22 @@
+export const settings_sa = {
+ name: 'examples.settings_sa',
+ draft: ({ Point, points, Path, paths, part }) => {
+ points.A = new Point(45, 60)
+ points.B = new Point(10, 30)
+ points.BCp2 = new Point(40, 20)
+ points.C = new Point(90, 30)
+ points.CCp1 = new Point(50, -30)
+
+ paths.example = new Path()
+ .move(points.A)
+ .line(points.B)
+ .curve(points.BCp2, points.CCp1, points.C)
+ .line(points.A)
+ .close()
+ .attr('class', 'fabric')
+
+ paths.offset = paths.example.offset(-10).attr('class', 'fabric sa')
+
+ return part
+ },
+}
diff --git a/designs/examples/src/shared.mjs b/designs/examples/src/shared.mjs
new file mode 100644
index 00000000000..e99bb3fbd3c
--- /dev/null
+++ b/designs/examples/src/shared.mjs
@@ -0,0 +1,13 @@
+/**
+ * This draws a (diagonal in a) box
+ * with with w and height h with a non-visible line.
+ * This is to force our examples parts to a certain size
+ */
+export function box(part, w = 100, h = 50) {
+ part.paths.box = new part.Path()
+ .move(new part.Point(0, 0))
+ .line(new part.Point(w, h))
+ .setClass('hidden noxray')
+
+ return part
+}
diff --git a/designs/examples/src/snippet.mjs b/designs/examples/src/snippet.mjs
new file mode 100644
index 00000000000..152a6635f07
--- /dev/null
+++ b/designs/examples/src/snippet.mjs
@@ -0,0 +1,39 @@
+import { box } from './shared.mjs'
+
+export const snippet = {
+ name: 'examples.snippet',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.anchor1 = new Point(20, 15)
+ points.anchor2 = new Point(50, 15)
+ points.anchor3 = new Point(80, 15)
+ snippets.demo1 = new Snippet('button', points.anchor1)
+ snippets.demo2 = new Snippet('buttonhole', points.anchor2)
+ snippets.demo3 = new Snippet('logo', points.anchor3).attr('data-scale', 0.5)
+
+ return box(part)
+ },
+}
+
+export const snippet_attr = {
+ name: 'examples.snippet_attr',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.anchor = new Point(50, 15)
+ snippets.demo = new Snippet('logo', points.anchor)
+ .attr('data-scale', 0.8)
+ .attr('data-rotate', 180)
+
+ return box(part)
+ },
+}
+
+export const snippet_clone = {
+ name: 'examples.snippet_clone',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.anchor = new Point(35, 35)
+ snippets.demo = new Snippet('logo', points.anchor).attr('style', 'color: #f006')
+
+ snippets.clone = snippets.demo.clone().attr('data-scale', 0.5)
+
+ return box(part)
+ },
+}
diff --git a/designs/examples/src/snippets.mjs b/designs/examples/src/snippets.mjs
new file mode 100644
index 00000000000..95c2e3d3ea4
--- /dev/null
+++ b/designs/examples/src/snippets.mjs
@@ -0,0 +1,91 @@
+import { box } from './shared.mjs'
+
+export const snippet_bnotch = {
+ name: 'examples.snippet_bnotch',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.anchor = new Point(50, 5)
+ snippets.demo = new Snippet('bnotch', points.anchor)
+
+ return box(part, 100, 10)
+ },
+}
+
+export const snippet_button = {
+ name: 'examples.snippet_button',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.anchor = new Point(50, 5)
+ snippets.demo = new Snippet('button', points.anchor)
+
+ return box(part, 100, 10)
+ },
+}
+
+export const snippet_buttonholeend = {
+ name: 'examples.snippet_buttonholeend',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.anchor = new Point(50, 0)
+ snippets.demo = new Snippet('buttonhole-end', points.anchor)
+
+ return box(part, 100, 10)
+ },
+}
+
+export const snippet_buttonholestart = {
+ name: 'examples.snippet_buttonholestart',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.anchor = new Point(50, 10)
+ snippets.demo = new Snippet('buttonhole-start', points.anchor)
+
+ return box(part, 100, 10)
+ },
+}
+
+export const snippet_buttonhole = {
+ name: 'examples.snippet_buttonhole',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.anchor = new Point(50, 5)
+ snippets.demo = new Snippet('buttonhole', points.anchor)
+
+ return box(part, 100, 10)
+ },
+}
+
+export const snippet_logo = {
+ name: 'examples.snippet_logo',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.anchor = new Point(50, 35)
+ snippets.demo = new Snippet('logo', points.anchor)
+
+ return box(part, 100, 50)
+ },
+}
+
+export const snippet_notch = {
+ name: 'examples.snippet_notch',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.anchor = new Point(50, 5)
+ snippets.demo = new Snippet('notch', points.anchor)
+
+ return box(part, 100, 10)
+ },
+}
+
+export const snippet_snapsocket = {
+ name: 'examples.snippet_snapsocket',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.anchor = new Point(50, 5)
+ snippets.demo = new Snippet('snap-socket', points.anchor)
+
+ return box(part, 100, 10)
+ },
+}
+
+export const snippet_snapstud = {
+ name: 'examples.snippet_snapstud',
+ draft: ({ Point, points, Snippet, snippets, part }) => {
+ points.anchor = new Point(50, 5)
+ snippets.demo = new Snippet('snap-stud', points.anchor)
+
+ return box(part, 100, 10)
+ },
+}
diff --git a/designs/examples/src/stacks.mjs b/designs/examples/src/stacks.mjs
new file mode 100644
index 00000000000..8b0cd0255f6
--- /dev/null
+++ b/designs/examples/src/stacks.mjs
@@ -0,0 +1,128 @@
+const stack = (settings, partName) => {
+ if (settings?.options?.stackIt === 'Do stack') return 'example'
+ else return partName
+}
+
+export const stacks_top = {
+ name: 'examples.stacks_top',
+ stack,
+ measurements: ['head'],
+ options: {
+ size: { pct: 50, min: 5, max: 100, menu: 'stack' },
+ x: { pct: 0, min: -100, max: 100, menu: 'stack' },
+ y: { pct: 0, min: -100, max: 100, menu: 'stack' },
+ stackIt: { dflt: 'Do stack', list: ['Do stack', 'Do not stack'], menu: 'stack' },
+ },
+ draft: ({ store, Point, points, Path, paths, options, measurements, part }) => {
+ store.set('size', measurements.head * options.size)
+ store.set('x', measurements.head * options.x)
+ store.set('y', measurements.head * options.y)
+ points.from = new Point(store.get('x'), store.get('y'))
+ points.to = points.from.shift(0, store.get('size'))
+ paths.line = new Path().move(points.from).line(points.to).attr('class', 'fabric stroke-4xl')
+
+ return part
+ },
+}
+
+export const stacks_right = {
+ name: 'examples.stacks_right',
+ stack,
+ after: stacks_top,
+ draft: ({ store, Point, points, Path, paths, part }) => {
+ points.from = new Point(store.get('x') + store.get('size'), store.get('y'))
+ points.to = points.from.shift(-90, store.get('size'))
+ paths.line = new Path().move(points.from).line(points.to).attr('class', 'fabric stroke-4xl')
+
+ return part
+ },
+}
+
+export const stacks_bottom = {
+ name: 'examples.stacks_bottom',
+ stack,
+ after: stacks_top,
+ draft: ({ store, Point, points, Path, paths, part }) => {
+ points.from = new Point(store.get('x') + store.get('size'), store.get('y') + store.get('size'))
+ points.to = points.from.shift(180, store.get('size'))
+ paths.line = new Path().move(points.from).line(points.to).attr('class', 'fabric stroke-4xl')
+
+ return part
+ },
+}
+
+export const stacks_left = {
+ name: 'examples.stacks_left',
+ stack,
+ after: stacks_top,
+ draft: ({ store, Point, points, Path, paths, part }) => {
+ points.from = new Point(store.get('x'), store.get('y') + store.get('size'))
+ points.to = points.from.shift(90, store.get('size'))
+ paths.line = new Path().move(points.from).line(points.to).attr('class', 'fabric stroke-4xl')
+
+ return part
+ },
+}
+
+export const stacks_leftEye = {
+ name: 'examples.stacks_leftEye',
+ stack,
+ after: stacks_top,
+ draft: ({ store, Point, points, part }) => {
+ points.leftEye = new Point(
+ store.get('x') + store.get('size') * 0.35,
+ store.get('y') + store.get('size') * 0.4
+ )
+ .attr('data-circle', store.get('size') * 0.1)
+ .attr('data-circle-class', 'stroke-6xl')
+
+ return part
+ },
+}
+
+export const stacks_rightEye = {
+ name: 'examples.stacks_rightEye',
+ stack,
+ after: stacks_top,
+ draft: ({ store, Point, points, part }) => {
+ points.rightEye = new Point(
+ store.get('x') + store.get('size') * 0.65,
+ store.get('y') + store.get('size') * 0.4
+ )
+ .attr('data-circle', store.get('size') * 0.08)
+ .attr('data-circle-class', 'stroke-7xl')
+
+ return part
+ },
+}
+
+export const stacks_mouth = {
+ name: 'examples.stacks_mouth',
+ stack,
+ after: stacks_top,
+ draft: ({ store, Point, points, paths, Path, part }) => {
+ points.left = new Point(
+ store.get('x') + store.get('size') * 0.15,
+ store.get('y') + store.get('size') * 0.5
+ )
+ points.right = new Point(
+ store.get('x') + store.get('size') * 0.85,
+ store.get('y') + store.get('size') * 0.5
+ )
+ points.leftCp = new Point(
+ store.get('x') + store.get('size') * 0.35,
+ store.get('y') + store.get('size') * 0.8
+ )
+ points.rightCp = new Point(
+ store.get('x') + store.get('size') * 0.65,
+ store.get('y') + store.get('size') * 0.8
+ )
+
+ paths.mouth = new Path()
+ .move(points.left)
+ .curve(points.leftCp, points.rightCp, points.right)
+ .attr('class', 'fabric stroke-7xl')
+
+ return part
+ },
+}
diff --git a/designs/examples/src/utils.mjs b/designs/examples/src/utils.mjs
new file mode 100644
index 00000000000..201fa7d40f0
--- /dev/null
+++ b/designs/examples/src/utils.mjs
@@ -0,0 +1,445 @@
+import { box } from './shared.mjs'
+
+export const utils_beamintersectscircle = {
+ name: 'examples.utils_beamintersectscircle',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, utils, part }) => {
+ points.A = new Point(95, 45).attr('data-circle', 35).attr('data-circle-class', 'fabric')
+ points.B = new Point(55, 50)
+ points.C = new Point(75, 30)
+ points.D = new Point(55, 65)
+ points.E = new Point(115, 5)
+ points.F = new Point(65, 75)
+ points.G = new Point(125, 15)
+
+ paths.line1 = new Path().move(points.B).line(points.C)
+ paths.line2 = new Path().move(points.D).line(points.E)
+ paths.line3 = new Path().move(points.F).line(points.G)
+
+ let intersections1 = utils.beamIntersectsCircle(
+ points.A,
+ points.A.attributes.get('data-circle'),
+ points.B,
+ points.C
+ )
+ let intersections2 = utils.beamIntersectsCircle(
+ points.A,
+ points.A.attributes.get('data-circle'),
+ points.D,
+ points.E,
+ 'y'
+ )
+ let intersections3 = utils.beamIntersectsCircle(
+ points.A,
+ points.A.attributes.get('data-circle'),
+ points.F,
+ points.G
+ )
+
+ snippets.first1 = new Snippet('bnotch', intersections1[0])
+ snippets.second1 = new Snippet('notch', intersections1[1])
+ snippets.first2 = new Snippet('bnotch', intersections2[0])
+ snippets.second2 = new Snippet('notch', intersections2[1])
+ snippets.first3 = new Snippet('bnotch', intersections3[0])
+ snippets.second3 = new Snippet('notch', intersections3[1])
+
+ return box(part, 200, 80)
+ },
+}
+
+export const utils_beamintersectsx = {
+ name: 'examples.utils_beamintersectsx',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, utils, part }) => {
+ points.A = new Point(10, 10)
+ points.B = new Point(90, 30)
+
+ paths.AB = new Path().move(points.A).line(points.B)
+
+ snippets.x = new Snippet('notch', utils.beamIntersectsX(points.A, points.B, 40))
+
+ paths.help = new Path()
+ .move(new Point(40, 5))
+ .line(new Point(40, 35))
+ .attr('class', 'note dashed')
+
+ return part
+ },
+}
+
+export const utils_beamintersectsy = {
+ name: 'examples.utils_beamintersectsy',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, utils, part }) => {
+ points.A = new Point(10, 10)
+ points.B = new Point(50, 40)
+
+ paths.AB = new Path().move(points.A).line(points.B)
+
+ snippets.x = new Snippet('notch', utils.beamIntersectsY(points.A, points.B, 30))
+
+ paths.help = new Path()
+ .move(new Point(0, 30))
+ .line(new Point(50, 30))
+ .attr('class', 'note dashed')
+
+ return part
+ },
+}
+
+export const utils_beamsintersect = {
+ name: 'examples.utils_beamsintersect',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, utils, part }) => {
+ points.A = new Point(10, 10)
+ points.B = new Point(50, 40)
+ points.C = new Point(45, 20)
+ points.D = new Point(60, 15)
+
+ paths.AB = new Path().move(points.A).line(points.B)
+ paths.CD = new Path().move(points.C).line(points.D)
+
+ snippets.x = new Snippet('notch', utils.beamsIntersect(points.A, points.B, points.C, points.D))
+
+ return part
+ },
+}
+
+export const utils_circlesintersect = {
+ name: 'examples.utils_circlesintersect',
+ draft: ({ Point, points, Snippet, snippets, utils, part }) => {
+ points.A = new Point(10, 10).attr('data-circle', 15).attr('data-circle-class', 'fabric')
+ points.B = new Point(30, 30).attr('data-circle', 35).attr('data-circle-class', 'fabric')
+ points.C = new Point(90, 10).attr('data-circle', 15).attr('data-circle-class', 'various')
+ points.D = new Point(110, 30).attr('data-circle', 35).attr('data-circle-class', 'various')
+
+ let intersections1 = utils.circlesIntersect(
+ points.A,
+ points.A.attributes.get('data-circle'),
+ points.B,
+ points.B.attributes.get('data-circle')
+ )
+ let intersections2 = utils.circlesIntersect(
+ points.C,
+ points.C.attributes.get('data-circle'),
+ points.D,
+ points.D.attributes.get('data-circle'),
+ 'y'
+ )
+
+ snippets.first1 = new Snippet('bnotch', intersections1[0])
+ snippets.second1 = new Snippet('notch', intersections1[1])
+ snippets.first2 = new Snippet('bnotch', intersections2[0])
+ snippets.second2 = new Snippet('notch', intersections2[1])
+
+ return part
+ },
+}
+
+export const utils_curveintersectsx = {
+ name: 'examples.utils_curveintersectsx',
+ draft: ({ Point, points, Path, paths, utils, snippets, Snippet, part }) => {
+ points.start = new Point(10, 15)
+ points.cp1 = new Point(80, 10)
+ points.cp2 = new Point(-50, 80)
+ points.end = new Point(110, 70)
+
+ paths.curve = new Path().move(points.start).curve(points.cp1, points.cp2, points.end)
+
+ for (let x of [30, 40]) {
+ points['from' + x] = new Point(x, 10)
+ points['to' + x] = new Point(x, 80)
+ paths['line' + x] = new Path()
+ .move(points['from' + x])
+ .line(points['to' + x])
+ .attr('class', 'lining dashed')
+ }
+
+ snippets.i40 = new Snippet(
+ 'notch',
+ utils.curveIntersectsX(points.start, points.cp1, points.cp2, points.end, 40)
+ )
+
+ for (let p of utils.curveIntersectsX(points.start, points.cp1, points.cp2, points.end, 30))
+ snippets[p.y] = new Snippet('notch', p)
+
+ return part
+ },
+}
+
+export const utils_curveintersectsy = {
+ name: 'examples.utils_curveintersectsy',
+ draft: ({ Point, points, Path, paths, utils, snippets, Snippet, part }) => {
+ points.start = new Point(10, 45)
+ points.cp1 = new Point(50, 10)
+ points.cp2 = new Point(0, 80)
+ points.end = new Point(110, 70)
+
+ paths.curve = new Path().move(points.start).curve(points.cp1, points.cp2, points.end)
+
+ for (let y of [40, 50]) {
+ points['from' + y] = new Point(10, y)
+ points['to' + y] = new Point(110, y)
+ paths['line' + y] = new Path()
+ .move(points['from' + y])
+ .line(points['to' + y])
+ .attr('class', 'lining dashed')
+ }
+
+ snippets.i50 = new Snippet(
+ 'notch',
+ utils.curveIntersectsY(points.start, points.cp1, points.cp2, points.end, 50)
+ )
+
+ for (let p of utils.curveIntersectsY(points.start, points.cp1, points.cp2, points.end, 40))
+ snippets[p.x] = new Snippet('notch', p)
+
+ return part
+ },
+}
+
+export const utils_curvesintersect = {
+ name: 'examples.utils_curvesintersect',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, utils, part }) => {
+ points.A = new Point(10, 10)
+ points.Acp = new Point(310, 40)
+ points.B = new Point(110, 70)
+ points.Bcp = new Point(-210, 40)
+
+ points.C = new Point(20, -5)
+ points.Ccp = new Point(60, 300)
+ points.D = new Point(100, 85)
+ points.Dcp = new Point(70, -220)
+ paths.curveA = new Path().move(points.A).curve(points.Acp, points.Bcp, points.B)
+ paths.curveB = new Path().move(points.C).curve(points.Ccp, points.Dcp, points.D)
+
+ for (let p of utils.curvesIntersect(
+ points.A,
+ points.Acp,
+ points.Bcp,
+ points.B,
+ points.C,
+ points.Ccp,
+ points.Dcp,
+ points.D
+ )) {
+ snippets[part.getId()] = new Snippet('notch', p)
+ }
+
+ return part
+ },
+}
+
+export const utils_lineintersectscircle = {
+ name: 'examples.utils_lineintersectscircle',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, utils, part }) => {
+ points.A = new Point(95, 45).attr('data-circle', 35).attr('data-circle-class', 'fabric')
+ points.B = new Point(55, 50)
+ points.C = new Point(75, 30)
+
+ points.D = new Point(55, 65)
+ points.E = new Point(115, 5)
+ points.F = new Point(65, 75)
+ points.G = new Point(125, 15)
+
+ paths.line1 = new Path().move(points.B).line(points.C)
+ paths.line2 = new Path().move(points.D).line(points.E)
+ paths.line3 = new Path().move(points.F).line(points.G)
+
+ let intersections1 = utils.lineIntersectsCircle(
+ points.A,
+ points.A.attributes.get('data-circle'),
+ points.B,
+ points.C
+ )
+ let intersections2 = utils.lineIntersectsCircle(
+ points.A,
+ points.A.attributes.get('data-circle'),
+ points.D,
+ points.E,
+ 'y'
+ )
+ let intersections3 = utils.lineIntersectsCircle(
+ points.A,
+ points.A.attributes.get('data-circle'),
+ points.F,
+ points.G
+ )
+ snippets.first1 = new Snippet('bnotch', intersections1[0])
+ snippets.first2 = new Snippet('bnotch', intersections2[0])
+ snippets.second2 = new Snippet('notch', intersections2[1])
+ snippets.first3 = new Snippet('bnotch', intersections3[0])
+ snippets.second3 = new Snippet('notch', intersections3[1])
+
+ return box(part, 200, 80)
+ },
+}
+
+export const utils_lineintersectscurve = {
+ name: 'examples.utils_lineintersectscurve',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, utils, part }) => {
+ points.A = new Point(10, 10)
+ points.Acp = new Point(310, 40)
+ points.B = new Point(110, 70)
+ points.Bcp = new Point(-210, 40)
+ points.E = new Point(20, -5)
+ points.D = new Point(100, 85)
+ paths.curve = new Path().move(points.A).curve(points.Acp, points.Bcp, points.B)
+ paths.line = new Path().move(points.E).line(points.D)
+
+ for (let p of utils.lineIntersectsCurve(
+ points.D,
+ points.E,
+ points.A,
+ points.Acp,
+ points.Bcp,
+ points.B
+ )) {
+ snippets[part.getId()] = new Snippet('notch', p)
+ }
+
+ return part
+ },
+}
+
+export const utils_linesintersect = {
+ name: 'examples.utils_linesintersect',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, utils, part }) => {
+ points.A = new Point(10, 10)
+ points.B = new Point(50, 40)
+ points.C = new Point(15, 30)
+ points.D = new Point(60, 15)
+
+ paths.AB = new Path().move(points.A).line(points.B)
+ paths.CD = new Path().move(points.C).line(points.D)
+
+ snippets.X = new Snippet('notch', utils.linesIntersect(points.A, points.B, points.C, points.D))
+
+ return part
+ },
+}
+
+export const utils_pointonbeam = {
+ name: 'examples.utils_pointonbeam',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, utils, part }) => {
+ points.from1 = new Point(10, 10)
+ points.to1 = new Point(90, 60)
+ points.from2 = new Point(10, 30)
+ points.to2 = new Point(90, 80)
+ points.b1 = new Point(170, 110)
+ points.b2 = new Point(170, 130)
+
+ let scatter = []
+ for (let i = 1; i < 36; i++) {
+ for (let j = 1; j < 27; j++) {
+ scatter.push(new Point(i * 10, j * 10))
+ }
+ }
+ let snippet
+ for (let point of scatter) {
+ if (utils.pointOnBeam(points.from1, points.to1, point)) snippet = 'notch'
+ else snippet = 'bnotch'
+ snippets[part.getId()] = new Snippet(snippet, point)
+ if (utils.pointOnBeam(points.from2, points.to2, point, 0.01)) {
+ snippet = 'notch'
+ } else snippet = 'bnotch'
+ snippets[part.getId()] = new Snippet(snippet, point)
+ }
+ paths.line1 = new Path().move(points.from1).line(points.to1).attr('class', 'fabric stroke-lg')
+ paths.lne1 = new Path().move(points.to1).line(points.b1).attr('class', 'fabric dashed')
+ paths.line2 = new Path().move(points.from2).line(points.to2).attr('class', 'fabric stroke-lg')
+ paths.lne2 = new Path().move(points.to2).line(points.b2).attr('class', 'fabric dashed')
+
+ return part
+ },
+}
+
+export const utils_pointoncurve = {
+ name: 'examples.utils_pointoncurve',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, utils, part }) => {
+ points.start = new Point(10, 10)
+ points.cp1 = new Point(90, 10)
+ points.cp2 = new Point(10, 60)
+ points.end = new Point(90, 60)
+
+ let scatter = []
+ for (let i = 1; i < 19; i++) {
+ for (let j = 1; j < 14; j++) {
+ scatter.push(new Point(i * 10, j * 10))
+ }
+ }
+ let snippet
+ for (let point of scatter) {
+ if (utils.pointOnCurve(points.start, points.cp1, points.cp2, points.end, point)) {
+ snippet = 'notch'
+ } else snippet = 'bnotch'
+ snippets[part.getId()] = new Snippet(snippet, point)
+ }
+ paths.curve = new Path()
+ .move(points.start)
+ .curve(points.cp1, points.cp2, points.end)
+ .attr('class', 'fabric stroke-lg')
+
+ return part
+ },
+}
+
+export const utils_pointonline = {
+ name: 'examples.utils_pointonline',
+ draft: ({ Point, points, Path, paths, Snippet, snippets, utils, part }) => {
+ points.from1 = new Point(10, 10)
+ points.to1 = new Point(90, 60)
+ points.from2 = new Point(10, 30)
+ points.to2 = new Point(90, 80)
+ points.b1 = new Point(170, 110)
+ points.b2 = new Point(170, 130)
+
+ let scatter = []
+ for (let i = 1; i < 36; i++) {
+ for (let j = 1; j < 27; j++) {
+ scatter.push(new Point(i * 10, j * 10))
+ }
+ }
+ let snippet
+ for (let point of scatter) {
+ if (utils.pointOnLine(points.from1, points.to1, point)) snippet = 'notch'
+ else snippet = 'bnotch'
+ snippets[part.getId()] = new Snippet(snippet, point)
+ if (utils.pointOnLine(points.from2, points.to2, point, 0.01)) {
+ snippet = 'notch'
+ } else snippet = 'bnotch'
+ snippets[part.getId()] = new Snippet(snippet, point)
+ }
+ paths.line1 = new Path().move(points.from1).line(points.to1).attr('class', 'fabric stroke-lg')
+ paths.lne1 = new Path().move(points.to1).line(points.b1).attr('class', 'fabric dashed')
+ paths.line2 = new Path().move(points.from2).line(points.to2).attr('class', 'fabric stroke-lg')
+ paths.lne2 = new Path().move(points.to2).line(points.b2).attr('class', 'fabric dashed')
+
+ return part
+ },
+}
+
+export const utils_splitcurve = {
+ name: 'examples.utils_splitcurve',
+ draft: ({ Point, points, Path, paths, utils, part }) => {
+ points.from = new Point(40, 10)
+ points.to = new Point(40, 80)
+ paths.line = new Path().move(points.from).line(points.to).attr('class', 'lining dashed')
+
+ points.start = new Point(10, 15)
+ points.cp1 = new Point(80, 10)
+ points.cp2 = new Point(-50, 80)
+ points.end = new Point(110, 70)
+
+ points.i40 = utils.curveIntersectsX(points.start, points.cp1, points.cp2, points.end, 40)
+
+ let parts = utils.splitCurve(points.start, points.cp1, points.cp2, points.end, points.i40)
+
+ let colors = ['lining', 'interfacing']
+ for (let p of parts) {
+ let color = colors.pop()
+ paths[color] = new Path()
+ .move(p.start)
+ .curve(p.cp1, p.cp2, p.end)
+ .attr('class', 'stroke-xl ' + color)
+ }
+
+ return part
+ },
+}
diff --git a/designs/examples/tests/shared.test.mjs b/designs/examples/tests/shared.test.mjs
new file mode 100644
index 00000000000..88702efb737
--- /dev/null
+++ b/designs/examples/tests/shared.test.mjs
@@ -0,0 +1,16 @@
+// This file is auto-generated | Any changes you make will be overwritten.
+import { Examples } 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(Examples)
+
+// Test drafting - Change the second parameter to `true` to log errors
+testPatternDrafting(Examples, false)
+
+// Test sampling - Change the second parameter to `true` to log errors
+//testPatternSampling(Examples, false)