diff --git a/packages/holmes/README.md b/packages/holmes/README.md
new file mode 100644
index 00000000000..7a1323f72f4
--- /dev/null
+++ b/packages/holmes/README.md
@@ -0,0 +1,31 @@
+# holmes
+
+> Sherlock Holmes hat
+
+[](https://www.npmjs.com/package/holmes) [](https://standardjs.com)
+
+## Install
+
+```bash
+npm install --save holmes
+```
+
+## Usage
+
+```jsx
+import React, { Component } from 'react'
+
+import MyComponent from 'holmes'
+
+class Example extends Component {
+ render () {
+ return (
+
+ )
+ }
+}
+```
+
+## License
+
+ © [AlfaLyr](https://github.com/AlfaLyr)
diff --git a/packages/holmes/config/index.js b/packages/holmes/config/index.js
new file mode 100644
index 00000000000..20a4b558082
--- /dev/null
+++ b/packages/holmes/config/index.js
@@ -0,0 +1,38 @@
+import { version } from "../package.json";
+
+// ?? 🤔 ?? --> https://en.freesewing.dev/packages/core/config
+
+export default {
+ name: "holmes",
+ version,
+ design: "AlfaLyr",
+ code: "AlfaLyr",
+ department: "accessories",
+ type: "pattern",
+ difficulty: 3,
+ tags: [
+ "freesewing",
+ "design",
+ "diy",
+ "fashion",
+ "made to measure",
+ "parametric design",
+ "pattern",
+ "sewing",
+ "sewing pattern"
+ ],
+ optionGroups: {
+ style: ["lengthRatio", "goreNumber", "brimAngle", "brimWidth"]
+ },
+ measurements: ["headCircumference"],
+ dependencies: {},
+ inject: {},
+ hide: [],
+ parts: ["gore", "brim", "ear"],
+ options: {
+ lengthRatio: { pct: 55, min: 40, max: 60 },
+ goreNumber: { count: 6, min: 4, max: 20 },
+ brimAngle: { deg: 45, min: 10, max:90 },
+ brimWidth: { mm: 30, min: 5, max: 100 }
+ }
+};
diff --git a/packages/holmes/drawings/brimangle.svg b/packages/holmes/drawings/brimangle.svg
new file mode 100644
index 00000000000..447a436a21d
--- /dev/null
+++ b/packages/holmes/drawings/brimangle.svg
@@ -0,0 +1,130 @@
+
+
diff --git a/packages/holmes/drawings/brimwidth.svg b/packages/holmes/drawings/brimwidth.svg
new file mode 100644
index 00000000000..6d388c64b96
--- /dev/null
+++ b/packages/holmes/drawings/brimwidth.svg
@@ -0,0 +1,130 @@
+
+
diff --git a/packages/holmes/drawings/gorenumber.svg b/packages/holmes/drawings/gorenumber.svg
new file mode 100644
index 00000000000..011befa3fa8
--- /dev/null
+++ b/packages/holmes/drawings/gorenumber.svg
@@ -0,0 +1,142 @@
+
+
diff --git a/packages/holmes/drawings/lengthratio.svg b/packages/holmes/drawings/lengthratio.svg
new file mode 100644
index 00000000000..8290280e175
--- /dev/null
+++ b/packages/holmes/drawings/lengthratio.svg
@@ -0,0 +1,130 @@
+
+
diff --git a/packages/holmes/drawings/linedrawing.svg b/packages/holmes/drawings/linedrawing.svg
new file mode 100644
index 00000000000..f7ec886cb80
--- /dev/null
+++ b/packages/holmes/drawings/linedrawing.svg
@@ -0,0 +1,94 @@
+
+
diff --git a/packages/holmes/example/README.md b/packages/holmes/example/README.md
new file mode 100644
index 00000000000..0fd98a71fff
--- /dev/null
+++ b/packages/holmes/example/README.md
@@ -0,0 +1,96 @@
+
+
+
+FreeSewing v2
+
+A JavaScript library for made-to-measure sewing patterns
+
+
+
+
+
+
+
+# holmes example
+
+This project was bootstrapped with [Create Freesewing Pattern](https://en.freesewing.dev/create-freesewing-pattern):
+
+```js
+npm init freesewing-pattern
+```
+
+This example folder is part of the local development environment.
+It is **not** part of the pattern's source code.
+
+To run this example, follow these steps:
+
+ - In the folder above this one, run: `yarn start` (or `npm start`)
+ - Then, in new terminal, run the same command in this folder: `yarn start` (or `npm start`)
+
+This will spin up the development environment, similar to [our online demo](https://holmes.freesewing.dev/).
+
+## About FreeSewing 🤔
+
+Where the world of makers and developers collide, that's where you'll find FreeSewing.
+
+Our [core library](https://freesewing.dev/en/freesewing) is a *batteries-included* toolbox
+for parametric design of sewing patterns. It's a modular system (check our list
+of [plugins](https://freesewing.dev/en/plugins) and getting started is as simple as:
+
+```bash
+npm init freesewing-pattern
+```
+
+The [getting started] section on [freesewing.dev](https://freesewing.dev/) is a good
+entrypoint to our documentation, but you'll find a lot more there, including
+our [API documentation](https://freesewing.dev/en/freesewing/api),
+as well as [examples](https://freesewing.dev/en/freesewing/examples),
+and [best practices](https://freesewing.dev/en/do).
+
+If you're a maker, checkout [freesewing.org](https://freesewing/) where you can generate
+our sewing patterns adapted to your measurements.
+
+## 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, you too
+should [become a patron](https://freesewing.org/patrons/join).
+
+## Links 👩💻
+
+ - 💻 Makers website: [freesewing.org](https://freesewing.org)
+ - 💻 Developers website: [freesewing.dev](https://freesewing.org)
+ - 💬 Chat: [gitter.im/freesewing](https://gitter.im/freesewing/freesewing)
+ - 🐦 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 [chatroom on Gitter](https://gitter.im) is 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).
+
diff --git a/packages/holmes/example/package.json b/packages/holmes/example/package.json
new file mode 100644
index 00000000000..64bc5b4a24c
--- /dev/null
+++ b/packages/holmes/example/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "holmes-example",
+ "homepage": "https://AlfaLyr.github.io/holmes",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@freesewing/components": "latest",
+ "@freesewing/core": "latest",
+ "@freesewing/css-theme": "latest",
+ "@freesewing/i18n": "latest",
+ "@freesewing/models": "latest",
+ "@freesewing/mui-theme": "latest",
+ "@freesewing/pattern-info": "latest",
+ "@freesewing/plugin-bundle": "latest",
+ "@freesewing/plugin-theme": "latest",
+ "@freesewing/plugin-i18n": "latest",
+ "@freesewing/plugin-svgattr": "latest",
+ "@freesewing/utils": "latest",
+ "@material-ui/core": "^4.4.0",
+ "@material-ui/icons": "^4.2.1",
+ "@material-ui/lab": "^v4.0.0-alpha.25",
+ "pattern": "file:..",
+ "prismjs": "1.17.1",
+ "react": "^16.9",
+ "react-dom": "^16.9",
+ "react-scripts": "^3.1.1",
+ "file-saver": "^2.0.2",
+ "typeface-roboto-condensed": "latest"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": "react-app"
+ },
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ],
+ "devDependencies": {
+ "babel-plugin-prismjs": "1.1.1"
+ }
+}
diff --git a/packages/holmes/example/public/favicon.ico b/packages/holmes/example/public/favicon.ico
new file mode 100644
index 00000000000..95061a260f1
Binary files /dev/null and b/packages/holmes/example/public/favicon.ico differ
diff --git a/packages/holmes/example/public/index.html b/packages/holmes/example/public/index.html
new file mode 100644
index 00000000000..48b507bf0c7
--- /dev/null
+++ b/packages/holmes/example/public/index.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+ holmes
+
+
+
+
+
+
+
diff --git a/packages/holmes/example/public/manifest.json b/packages/holmes/example/public/manifest.json
new file mode 100644
index 00000000000..bae8e9ac3f8
--- /dev/null
+++ b/packages/holmes/example/public/manifest.json
@@ -0,0 +1,15 @@
+{
+ "short_name": "holmes",
+ "name": "holmes",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/packages/holmes/example/src/App.js b/packages/holmes/example/src/App.js
new file mode 100644
index 00000000000..b336f6f6cd1
--- /dev/null
+++ b/packages/holmes/example/src/App.js
@@ -0,0 +1,22 @@
+import React from 'react'
+import freesewing from '@freesewing/core'
+import Workbench from '@freesewing/components/Workbench'
+import 'typeface-roboto-condensed'
+import '@freesewing/css-theme'
+
+import Pattern from 'pattern'
+
+const App = props => {
+ let instance = new Pattern()
+ let config = instance.config
+ return (
+
+ )
+}
+
+export default App
diff --git a/packages/holmes/example/src/App.test.js b/packages/holmes/example/src/App.test.js
new file mode 100644
index 00000000000..4bf19359ea4
--- /dev/null
+++ b/packages/holmes/example/src/App.test.js
@@ -0,0 +1,9 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import App from './App'
+
+it('renders without crashing', () => {
+ const div = document.createElement('div')
+ ReactDOM.render(, div)
+ ReactDOM.unmountComponentAtNode(div)
+})
diff --git a/packages/holmes/example/src/index.js b/packages/holmes/example/src/index.js
new file mode 100644
index 00000000000..9dd7ba788d4
--- /dev/null
+++ b/packages/holmes/example/src/index.js
@@ -0,0 +1,11 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import App from './App'
+import * as serviceWorker from './serviceWorker'
+
+ReactDOM.render(, document.getElementById('root'))
+
+// If you want your app to work offline and load faster, you can change
+// unregister() to register() below. Note this comes with some pitfalls.
+// Learn more about service workers: http://bit.ly/CRA-PWA
+serviceWorker.unregister()
diff --git a/packages/holmes/example/src/serviceWorker.js b/packages/holmes/example/src/serviceWorker.js
new file mode 100644
index 00000000000..44e1b1b2f8c
--- /dev/null
+++ b/packages/holmes/example/src/serviceWorker.js
@@ -0,0 +1,123 @@
+// In production, we register a service worker to serve assets from local cache.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on the "N+1" visit to a page, since previously
+// cached resources are updated in the background.
+
+// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
+// This link also includes instructions on opting out of this behavior.
+
+const isLocalhost = Boolean(
+ window.location.hostname === 'localhost' ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === '[::1]' ||
+ // 127.0.0.1/8 is considered localhost for IPv4.
+ window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
+)
+
+export function register(config) {
+ if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location)
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+ return
+ }
+
+ window.addEventListener('load', () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
+
+ if (isLocalhost) {
+ // This is running on localhost. Let's check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl, config)
+
+ // Add some additional logging to localhost, pointing developers to the
+ // service worker/PWA documentation.
+ navigator.serviceWorker.ready.then(() => {
+ console.log(
+ 'This web app is being served cache-first by a service ' +
+ 'worker. To learn more, visit https://goo.gl/SC7cgQ'
+ )
+ })
+ } else {
+ // Is not local host. Just register service worker
+ registerValidSW(swUrl, config)
+ }
+ })
+ }
+}
+
+function registerValidSW(swUrl, config) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then(registration => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === 'installed') {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the old content will have been purged and
+ // the fresh content will have been added to the cache.
+ // It's the perfect time to display a "New content is
+ // available; please refresh." message in your web app.
+ console.log('New content is available; please refresh.')
+
+ // Execute callback
+ if (config.onUpdate) {
+ config.onUpdate(registration)
+ }
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log('Content is cached for offline use.')
+
+ // Execute callback
+ if (config.onSuccess) {
+ config.onSuccess(registration)
+ }
+ }
+ }
+ }
+ }
+ })
+ .catch(error => {
+ console.error('Error during service worker registration:', error)
+ })
+}
+
+function checkValidServiceWorker(swUrl, config) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl)
+ .then(response => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ if (
+ response.status === 404 ||
+ response.headers.get('content-type').indexOf('javascript') === -1
+ ) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister().then(() => {
+ window.location.reload()
+ })
+ })
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl, config)
+ }
+ })
+ .catch(() => {
+ console.log('No internet connection found. App is running in offline mode.')
+ })
+}
+
+export function unregister() {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister()
+ })
+ }
+}
diff --git a/packages/holmes/package.json b/packages/holmes/package.json
new file mode 100644
index 00000000000..a9e262b574a
--- /dev/null
+++ b/packages/holmes/package.json
@@ -0,0 +1,66 @@
+{
+ "name": "holmes",
+ "version": "0.0.1",
+ "description": "Sherlock Holmes hat",
+ "author": "AlfaLyr",
+ "license": "MIT",
+ "repository": "AlfaLyr/holmes",
+ "main": "dist/index.js",
+ "module": "dist/index.es.js",
+ "jsnext:main": "dist/index.es.js",
+ "engines": {
+ "node": ">=8",
+ "npm": ">=5"
+ },
+ "scripts": {
+ "test": "cross-env CI=1 react-scripts test --env=jsdom",
+ "test:watch": "react-scripts test --env=jsdom",
+ "build": "rollup -c",
+ "start": "rollup -c -w",
+ "prepare": "npm run build",
+ "predeploy": "cd example && npm install && npm run build",
+ "deploy": "gh-pages -d example/build"
+ },
+ "devDependencies": {
+ "react": "^16.8",
+ "react-dom": "^16.8",
+ "@babel/plugin-proposal-class-properties": "^7.0.0",
+ "babel-eslint": "10.0.1",
+ "eslint": "^5.16.0",
+ "babel-jest": "24.7.1",
+ "jest": "24.7.1",
+ "@freesewing/core": "latest",
+ "@freesewing/plugin-bundle": "latest",
+ "@freesewing/components": "latest",
+ "@freesewing/css-theme": "latest",
+ "@freesewing/i18n": "latest",
+ "@freesewing/mui-theme": "latest",
+ "@freesewing/patterns": "latest",
+ "@freesewing/plugin-bust": "latest",
+ "@freesewing/plugin-buttons": "latest",
+ "@freesewing/plugin-debug": "latest",
+ "@freesewing/plugin-designer": "latest",
+ "@freesewing/plugin-flip": "latest",
+ "@freesewing/utils": "latest",
+ "@svgr/rollup": "^2.4.1",
+ "cross-env": "^5.1.4",
+ "gh-pages": "^1.2.0",
+ "react-scripts": "^3.0.1",
+ "webpack": "4.29.6",
+ "rollup": "^0.64.1",
+ "rollup-plugin-babel": "^4.0.1",
+ "rollup-plugin-babel-minify": "^7.0.0",
+ "rollup-plugin-commonjs": "^9.1.3",
+ "rollup-plugin-json": "^3.1.0",
+ "rollup-plugin-node-resolve": "^3.3.0",
+ "rollup-plugin-peer-deps-external": "^2.2.0",
+ "rollup-plugin-postcss": "^1.6.2",
+ "rollup-plugin-url": "^1.4.0",
+ "@material-ui/core": "^4.0.1",
+ "@material-ui/icons": "^4.0.1",
+ "@material-ui/lab": "^v4.0.0-alpha.14",
+ "react-intl": "2.8.0",
+ "prop-types": "15.7.2",
+ "file-saver": "^2.0.2"
+ }
+}
diff --git a/packages/holmes/rollup.config.js b/packages/holmes/rollup.config.js
new file mode 100644
index 00000000000..70c428bd463
--- /dev/null
+++ b/packages/holmes/rollup.config.js
@@ -0,0 +1,47 @@
+import babel from "rollup-plugin-babel";
+import commonjs from "rollup-plugin-commonjs";
+import external from "rollup-plugin-peer-deps-external";
+import postcss from "rollup-plugin-postcss";
+import json from "rollup-plugin-json";
+import resolve from "rollup-plugin-node-resolve";
+import url from "rollup-plugin-url";
+import svgr from "@svgr/rollup";
+import minify from "rollup-plugin-babel-minify";
+import { name, version, description, author, license } from "./package.json";
+
+import pkg from "./package.json";
+
+export default {
+ input: "src/index.js",
+ output: [
+ {
+ file: pkg.main,
+ format: "cjs",
+ sourcemap: true
+ },
+ {
+ file: pkg.module,
+ format: "es",
+ sourcemap: true
+ }
+ ],
+ plugins: [
+ external(),
+ postcss({
+ modules: true
+ }),
+ url({ exclude: ["**/*.svg"] }),
+ svgr(),
+ babel({
+ exclude: "node_modules/**"
+ }),
+ resolve({ browser: true }),
+ json(),
+ commonjs(),
+ minify({
+ comments: false,
+ sourceMap: true,
+ banner: `/**\n * ${name} | v${version}\n * ${description}\n * (c) ${new Date().getFullYear()} ${author}\n * @license ${license}\n */`
+ })
+ ]
+};
diff --git a/packages/holmes/src/brim.js b/packages/holmes/src/brim.js
new file mode 100644
index 00000000000..9ff8b36c396
--- /dev/null
+++ b/packages/holmes/src/brim.js
@@ -0,0 +1,72 @@
+export default function(part) {
+ let {
+ Point,
+ points,
+ Path,
+ paths,
+ measurements,
+ options,
+ complete,
+ sa,
+ snippets,
+ Snippet,
+ paperless,
+ macro
+ } = part.shorthand();
+
+// Design pattern here
+
+let headRadius = measurements.headCircumference/2/Math.PI
+let brimRadius = headRadius/Math.sin(options.brimAngle*Math.PI/180)
+let sectorAngle = Math.PI/3
+let brimSectorAngle = sectorAngle * headRadius / brimRadius
+let cpDistance = 4/3*brimRadius*(1-Math.cos(brimSectorAngle/2))/Math.sin(brimSectorAngle/2)
+
+points.origin = new Point(0, 0)
+points.in1 = new Point(0, 0)
+points.in2 = points.in1.shift(90/Math.PI*brimSectorAngle, 2*brimRadius*Math.sin(brimSectorAngle/2))
+points.in1C = points.in1.shift(0, cpDistance)
+points.in2C = points.in2.shift(180+180/Math.PI*brimSectorAngle, cpDistance)
+points.in1CFlipped = points.in1C.flipX()
+points.in2Flipped = points.in2.flipX()
+points.in2CFlipped = points.in2C.flipX()
+
+points.ex1 = points.in1.shift(-90, options.brimWidth)
+points.ex1C = points.ex1.shift(0, 0.5*points.in2.x)
+points.ex2C = points.in2.shift(-90, (points.ex1.y-points.in2.y)*(2/(1+Math.exp(-options.brimWidth/15))-1))
+points.ex1CFlipped = points.ex1C.flipX()
+points.ex2CFlipped = points.ex2C.flipX()
+
+paths.seam = new Path()
+ .move(points.in2Flipped)
+ .curve(points.in2CFlipped, points.in1CFlipped, points.in1)
+ .curve(points.in1C, points.in2C, points.in2)
+ .curve(points.ex2C, points.ex1C, points.ex1)
+ .curve(points.ex1CFlipped, points.ex2CFlipped, points.in2Flipped)
+ .close()
+
+ // Complete?
+ if (complete) {
+ macro('grainline', {from: points.in1, to: points.ex1})
+ macro('title', { at: points.ex1.shift(45, 20), nr: 2, title: 'brim', scale: 0.4})
+
+ if (sa) {
+ paths.sa = paths.seam.offset(sa * -1).attr('class', 'fabric sa')
+ }
+
+ // Paperless?
+ if (paperless) {
+ macro('hd', {
+ from: points.in2Flipped,
+ to: points.in2,
+ y: points.ex1.y + 15 + sa
+ })
+ macro('vd', {
+ from: points.ex1,
+ to: points.in2Flipped,
+ x: points.in2Flipped.x - 15 - sa
+ })
+ }
+ }
+ return part;
+}
diff --git a/packages/holmes/src/ear.js b/packages/holmes/src/ear.js
new file mode 100644
index 00000000000..f95e437e86a
--- /dev/null
+++ b/packages/holmes/src/ear.js
@@ -0,0 +1,62 @@
+export default function(part) {
+ let {
+ Point,
+ points,
+ Path,
+ paths,
+ measurements,
+ options,
+ complete,
+ sa,
+ snippets,
+ Snippet,
+ paperless,
+ macro
+ } = part.shorthand();
+
+// Design pattern here
+
+points.top = new Point(0, 0)
+points.bottom = new Point(measurements.headCircumference/12, options.lengthRatio*measurements.headCircumference/2)
+points.topC = points.top.shift(0, points.bottom.x)
+points.bottomC = points.bottom.shift(90, points.bottom.y-points.bottom.x)
+points.topCFlipped = points.topC.flipX()
+points.bottomFlipped = points.bottom.flipX()
+points.bottomCFlipped = points.bottomC.flipX()
+
+paths.seam = new Path()
+ .move(points.top)
+ .curve(points.topCFlipped, points.bottomCFlipped, points.bottomFlipped)
+ .line(points.bottom)
+ .curve(points.bottomC, points.topC, points.top)
+ .close()
+
+ // Complete?
+ if (complete) {
+ macro('grainline', {from: points.top, to: new Point(0, points.bottom.y)})
+ points.logo = new Point(-0.5*points.bottom.x, 0.75*points.bottom.y)
+ snippets.logo = new Snippet("logo", points.logo)
+ .attr("data-scale", 0.7);
+ points.title = new Point(0.3*points.bottom.x, 0.75*points.bottom.y)
+ macro('title', { at: points.title, nr: 3, title: 'ear', scale: 0.5})
+
+ if (sa) {
+ paths.sa = paths.seam.offset(sa).attr('class', 'fabric sa')
+ }
+
+ // Paperless?
+ if (paperless) {
+ macro('hd', {
+ from: points.bottomFlipped,
+ to: points.bottom,
+ y: points.bottom.y + 15 + sa
+ })
+ macro('vd', {
+ from: points.bottomFlipped,
+ to: points.top,
+ x: points.bottomFlipped.x - 15 - sa
+ })
+ }
+ }
+ return part;
+}
diff --git a/packages/holmes/src/gore.js b/packages/holmes/src/gore.js
new file mode 100644
index 00000000000..8ef6ad09069
--- /dev/null
+++ b/packages/holmes/src/gore.js
@@ -0,0 +1,77 @@
+export default function(part) {
+ let {
+ Point,
+ points,
+ Path,
+ paths,
+ measurements,
+ options,
+ macro,
+ complete,
+ sa,
+ snippets,
+ Snippet,
+ paperless
+ } = part.shorthand();
+
+// Design pattern here
+
+//Radius of the head
+let headRadius = measurements.headCircumference/2/Math.PI
+
+points.p0 = new Point(0, 0);
+
+macro("gore", {
+ from: points.p0,
+ radius: headRadius,
+ goreNumber: options.goreNumber,
+ extraLength: (options.lengthRatio-0.5)*measurements.headCircumference/2,
+ prefix: "gore_",
+ render: true
+});
+
+ // Complete?
+ if (complete) {
+ points.title = new Point(points.gore_p1.x/10, points.gore_p2.y/1.8)
+ macro('title', { at: points.title, nr: 1, title: 'gore', scale: 0.5})
+
+ macro('cutonfold', {
+ from: points.p0,
+ to: points.gore_p1.shift(180, 20),
+ offset: -points.gore_p2.y/6,
+ grainline: true
+ })
+
+ if (sa) {
+ paths.saBase = new Path()
+ .move(points.gore_p1)
+ .curve(points.gore_Cp1, points.gore_Cp2, points.gore_p2)
+ .line(points.gore_p3)
+ .line(points.p0)
+ .offset(sa)
+ .setRender(false)
+ paths.sa = new Path()
+ .move(points.gore_p1)
+ .line(points.gore_p1.shift(0, sa))
+ .line(paths.saBase.start())
+ .join(paths.saBase)
+ .line(points.p0)
+ .attr('class', 'fabric sa')
+ }
+
+ // Paperless?
+ if (paperless) {
+ macro('hd', {
+ from: points.p0,
+ to: points.gore_p1,
+ y: -points.p0.x + 15
+ })
+ macro('vd', {
+ from: points.p0,
+ to: points.gore_p3,
+ x: points.p0.x - 15 - sa
+ })
+ }
+ }
+ return part;
+}
diff --git a/packages/holmes/src/index.js b/packages/holmes/src/index.js
new file mode 100644
index 00000000000..c1610596cd7
--- /dev/null
+++ b/packages/holmes/src/index.js
@@ -0,0 +1,17 @@
+import freesewing from '@freesewing/core'
+import plugins from '@freesewing/plugin-bundle'
+import gore from './plugin-gore'
+import config from '../config'
+import draftGore from './gore'
+import draftBrim from './brim'
+import draftEar from './ear'
+
+// Create new design
+const Pattern = new freesewing.Design(config, [plugins,gore])
+
+// Attach the draft methods to the prototype
+Pattern.prototype.draftGore = draftGore
+Pattern.prototype.draftBrim = draftBrim
+Pattern.prototype.draftEar = draftEar
+
+export default Pattern
diff --git a/packages/holmes/src/plugin-gore.js b/packages/holmes/src/plugin-gore.js
new file mode 100644
index 00000000000..1b0396709d2
--- /dev/null
+++ b/packages/holmes/src/plugin-gore.js
@@ -0,0 +1,63 @@
+import { name, version } from '../package.json'
+
+export default {
+ name: name,
+ version: version,
+ hooks: {
+ preRender: function(svg) {
+ if (svg.attributes.get('freesewing:plugin-gore') === false)
+ svg.attributes.set('freesewing:plugin-gore', version)
+ }
+ },
+ macros: {
+ gore: function(so) {
+ let from = so.from
+ let goreNumber = so.goreNumber //number of gores for the complete sphere
+ let radius = so.radius //radius of the sphere
+ let prefix = so.prefix
+ let extraLength = so.extraLength //the length of the straight section after a complete semisphere
+
+ this.points[prefix + 'p1'] = from.shift(0, radius*Math.PI/2 + extraLength)
+ this.points[prefix + 'Cp1'] = this.points[prefix + 'p1'].shift(180-180/goreNumber, radius/2/Math.cos(Math.PI/goreNumber))
+ this.points[prefix + 'p3'] = from.shift(90, radius*Math.PI/goreNumber)
+ this.points[prefix + 'p2'] = this.points[prefix + 'p3'].shift(0, extraLength)
+ this.points[prefix + 'Cp2'] = this.points[prefix + 'p2'].shift(0, radius*(Math.PI-2)/2)
+
+ if (extraLength < 0){
+ //top curve used to calculate the new points if extraLength < 0
+ this.paths.auxiliaryPath = new this.Path()
+ .move(this.points[prefix + "p1"])
+ .curve(
+ this.points[prefix + 'Cp1'],
+ this.points[prefix + 'Cp2'],
+ this.points[prefix + 'p2']
+ )
+ .setRender(false)
+
+ this.points[prefix + 'p2'] = this.paths.auxiliaryPath.intersectsX(0)[0] //the new point p2 is the one in which the auxiliary curve intersects x=0
+ this.paths.auxiliaryPath = this.paths.auxiliaryPath.split(this.points[prefix + 'p2'])[0] //the auxiliary curve is split
+ this.points[prefix + 'Cp1'] = this.paths.auxiliaryPath.ops[1].cp1 //the new control points are those of the new curve
+ this.points[prefix + 'Cp2'] = this.paths.auxiliaryPath.ops[1].cp2
+ this.points[prefix + 'p3'] = this.points[prefix + 'p2'].clone()
+ }
+
+ //the seam path is generated
+ this.paths[prefix + 'seam'] = new this.Path()
+ .move(from)
+ .line(this.points[prefix + 'p1'])
+ .curve(
+ this.points[prefix + 'Cp1'],
+ this.points[prefix + 'Cp2'],
+ this.points[prefix + 'p2']
+ )
+ .line(this.points[prefix + "p3"])
+ .line(from)
+ .close()
+ .attr('class', so.class ? so.class : '')
+
+ if (typeof so.render !== 'undefined' && so.render)
+ this.paths[prefix + 'seam'].render = true
+ else this.paths[prefix + 'seam'].render = false
+ }
+ }
+}