diff --git a/markdown/dev/guides/patterns/config/en.md b/markdown/dev/guides/patterns/config/en.md deleted file mode 100644 index 92d843770e1..00000000000 --- a/markdown/dev/guides/patterns/config/en.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Configuration -order: 10 ---- - - - -The pattern configuration holds important information about the pattern - - - -A pattern's [configuration](/reference/api/config/) is created by the pattern designer -and details a number of important things about the pattern, like: - -- The **measurements** that are required to draft the pattern -- The different **parts** in the pattern and how they depend on each other -- The different **options** that are available to tweak the pattern - -The configuration is part of the pattern's code. It is created by the designer and -it is the same for everybody using the pattern. -In other words, you cannot change the configuration. Instead, the configuration -specifies what kind of settings the pattern accepts. diff --git a/markdown/dev/guides/patterns/en.md b/markdown/dev/guides/patterns/en.md index 0882327ec5d..68c8baf2361 100644 --- a/markdown/dev/guides/patterns/en.md +++ b/markdown/dev/guides/patterns/en.md @@ -1,5 +1,5 @@ --- -title: How patterns work +title: Pattern guide --- This short guide will illustrate and explain how patterns work in FreeSewing. @@ -11,19 +11,188 @@ pattern is generated by FreeSewing. This illustration is a good starting point to gain a better understanding of the structure of a FreeSewing pattern: - -A schematic overview of FreeSewing + +```mjs +({ Point, points, Path, paths, options, part }) => { + + // Draws a w*h box, returns a Path object + const box = (name, origin, width, height, classes='fabric') => { + let base = height + if (width < height) base = width + let t = base + points[name + 'TopLeft'] = new Point(origin.x, origin.y) + points[name + 'BottomLeft'] = new Point(origin.x, origin.y + height) + points[name + 'BottomRight'] = new Point( + origin.x + width, + origin.y + height + ) + points[name + 'TopRight'] = new Point(origin.x + width, origin.y) + points[name + 'Mid'] = points[name + 'TopLeft'].shiftFractionTowards( + points[name + 'BottomRight'], + 0.5 + ) + points[name + 'Mid'].y += 3 + + return new Path() + .move(points[name + 'TopLeft']) + .line(points[name + 'BottomLeft']) + .line(points[name + 'BottomRight']) + .line(points[name + 'TopRight']) + .line(points[name + 'TopLeft']) + .close() + .addClass(classes) + } + + // Draws and arrow from to and places text + const arrow = (name, text = '') => { + let from = points[name + 'From'] + let to = points[name + 'To'] + from = from.shiftTowards(to, 2) + to = to.shiftTowards(from, 2) + const base = from.dist(to) + const r = 3 + points[name + 'Tip1'] = to.shiftTowards(from, 3.5).rotate(r, from) + points[name + 'Tip2'] = to.shiftTowards(from, 3.5).rotate(r * -1, from) + const path = new Path() + .move(from) + .line(to) + .move(points[name + 'Tip1']) + .line(to) + .line(points[name + 'Tip2']) + .addClass('lining stroke-lg') + if (options.focus === name) path.addClass('note') + + return text + ? path.addText(' ' + text, options.focus === name ? 'fill-note center' : 'center') + : path + } + + // Draws a box and handled text placement + const drawBox = (name, x, y, width, height, text=true, classes, textClasses='') => { + points[name + 'Origin'] = new Point(x, y) + paths[name] = box(name, points[name + 'Origin'], width, height, classes) + if (text === 'b') { + paths[name+'_label'] = new Path() + .move(points[name+'BottomLeft']) + .line(points[name+'BottomRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dy', -1) + } + else if (text === 't') { + paths[name+'_label'] = new Path() + .move(points[name+'TopLeft']) + .line(points[name+'TopRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dy', 11) + } + else if (text === 'r') { + paths[name+'_label'] = new Path() + .move(points[name+'BottomRight']) + .line(points[name+'TopRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dx', -5) + } + else if (text) points[name + 'Mid'].addText(name, 'center') + } + + // Settings + drawBox('Settings Set 0', -140, -18, 50, 20, 'Settings 0', 'fabric fill-bg') + drawBox('Settings Set 1', -140, 6, 50, 20, 'Settings 0', 'fabric fill-bg') + const sname = 'Settings' + drawBox(sname, -145, -24, 61, 65, 'b', 'fabric', 'text-lg bold') + points.arrowDraftFrom = new Point(-84,15) + points.arrowDraftTo = new Point(-45, 15) + paths.arrowDraft = arrow('arrowDraft', 'draft()') + + // Stacks + drawBox('Stack 0', -30, -33, 50, 169, 'b', 'mark fill-mark', 'fill-mark') + paths['Stack 0'].attr('fill-opacity', 0.2) + drawBox('Stack 1', 23, -33, 50, 169, 'b', 'mark fill-mark', 'fill-mark') + paths['Stack 1'].attr('fill-opacity', 0.2) + drawBox('Stack 2', 76, -33, 50, 169, 'b', 'mark fill-mark', 'fill-mark') + paths['Stack 2'].attr('fill-opacity', 0.2) + + // Sets + drawBox('Set 0', -33, -30, 174, 76, 'r', 'contrast fill-contrast', 'fill-contrast') + paths['Set 0'].attr('fill-opacity', 0.2) + drawBox('Set 1', -33, 50, 174, 76, 'r', 'contrast fill-contrast', 'fill-contrast') + paths['Set 1'].attr('fill-opacity', 0.2) + + // Parts set 0 + drawBox('Part A (set 0)', -27, -27, 44, 70, 'b', 'note fill-bg') + drawBox('points', -24, -24, 38, 12, true, 'note') + drawBox('paths', -24, -9, 38, 12, true, 'note') + drawBox('snippets', -24, 6, 38, 12, true, 'note') + + drawBox('Part B (set 0)', 26, -27, 44, 70, 'b', 'note fill-bg') + drawBox(' points ', 29, -24, 38, 12, true, 'note') + drawBox(' paths ', 29, -9, 38, 12, true, 'note') + drawBox(' snippets ', 29, 6, 38, 12, true, 'note') + + drawBox('Part C (set 0)', 79, -27, 44, 70, 'b', 'note fill-bg') + drawBox(' points ', 82, -24, 38, 12, true, 'note') + drawBox(' paths ', 82, -9, 38, 12, true, 'note') + drawBox(' snippets ', 82, 6, 38, 12, true, 'note') + + drawBox('setStore 0', -24, 21, 144, 12, true, 'lining fill-lining dashed', 'fill-various') + paths['setStore 0'].attr('fill-opacity', 0.2) + + // Parts set 1 + drawBox('Part A (set 1)', -27, 53, 44, 70, 'b', 'note fill-bg') + drawBox(' points', -24, 56, 38, 12, true, 'note') + drawBox(' paths', -24, 71, 38, 12, true, 'note') + drawBox(' snippets', -24, 86, 38, 12, true, 'note') + + drawBox('Part B (set 1)', 26, 53, 44, 70, 'b', 'note fill-bg') + drawBox(' points ', 29, 56, 38, 12, true, 'note') + drawBox(' paths ', 29, 71, 38, 12, true, 'note') + drawBox(' snippets ', 29, 86, 38, 12, true, 'note') + + drawBox('Part C (set 1)', 79, 53, 44, 70, 'b', 'note fill-bg') + drawBox(' points ', 82, 56, 38, 12, true, 'note') + drawBox(' paths ', 82, 71, 38, 12, true, 'note') + drawBox(' snippets ', 82, 86, 38, 12, true, 'note') + + drawBox('setStore 1', -24, 101, 147, 12, true, 'lining fill-lining dashed', 'fill-various') + paths['setStore 1'].attr('fill-opacity', 0.2) + + // Pattern + drawBox('Pattern Store', -30, -52, 155, 15, true, 'lining fill-lining') + paths['Pattern Store'].attr('fill-opacity', 0.2) + drawBox('Pattern', -43, -59, 195, 216, 'b', 'fabric stroke-lg', 'text-lg bold') + + // Render + points.arrowSvgFrom = new Point(154,15) + points.arrowSvgTo = new Point(225, -15) + paths.arrowSvg = arrow('arrowSvg', 'render()') + points.arrowReactFrom = new Point(154,15) + points.arrowReactTo = new Point(225, 45) + paths.arrowReact = arrow('arrowReact', 'getRenderProps()') + drawBox('Render stage', 190, -52, 105, 175, 'b', 'interfacing lashed', 'text-lg bold') + + // Render logos + points.svg = new Point(225, -15).addText('SVG', 'text-lg bold') + points.js = new Point(225, 45).addText("React\nSvelte\nVue\nJS\n...", 'text-lg') + + // Prevent clipping + paths.unclip = new Path().move(new Point(260, -80)) + + return part +} +``` +If it looks like _a lot_ don't despair. There's a lot of repetition, and we'll +work throug the building blocks step by step. + If we look at our image, it can be divided into three areas: -- The left area with the **settings** box -- The middle area with the **Pattern** box and everything in it -- The right area with the **draft** box and the _SVG_ and _React_ logos - -Let's take a closer look at everything that is contained within our central **Pattern** box: - - +- [The **Settings**](#the-settings) on the left. +- [The **Render stage**](#rendering-your-pattern) on the right +- [The **Pattern**](#anatomy-of-a-pattern) @@ -31,6 +200,74 @@ The left and right parts are all about how to integrate FreeSewing in your _fron In other words, how you'll plug it into your website, or online store, or a mobile application. -That part is outside the scope of this guide. +We'll briefly cover those areas in this page. But a deep-dive in those topics +is outside the scope of this guide. + +## The settings + +On the left, we have the **settings** box. It represents the +[settings](/reference/settings) that are provided by the user. + +Most of the settings are the same for all designs generated with FreeSewing as they are provided by the core library. + +Noteworthy exceptions are the [measurements](/reference/settings/measurements) and [options](/reference/settings/options) keys as they are defined by the pattern design. + +In other words, different patterns will required different measurements, and provide different options. +But all patterns will allow users to set the [units](reference/settings/units) (for example). + + +Settings are provided by passing them to the Pattern contructor + + + +##### Multiset support + +Since version 3 of FreeSewing, you can pass multiple sets of settings to `Pattern.draft()`. +We call this _multset support_. + +In 99% of the cases, you will only have a single set of settings, and you can mostly forget about this feature. +It is typically used to compare different drafts, or to draft for different sets of measurements (for example). + + + +## Rendering your pattern + +By rendering our pattern, we mean to generate the output to show to the user. + +This can be done in two different ways: + +### Render to SVG + +The core library ships with a renderer that will render your pattern to SVG. + +To use it, call [`Pattern.render()`](/reference/api/pattern/render) which will return an SVG string. + +### BYOR (bring your own renderer) + +If you'd like more fine-grained control over the output, you can use your own renderer. + +To do so, call [`Pattern.getRenderProps()`](/reference/api/pattern/getrenderprops) which will return all the data you need to render your pattern in the way you prefer. + + +This is what we use on these pages to render with React + + +## The pattern + +Last but not least, we've arrived at the central item: the pattern itself. +The pattern is a container that holds all your parts, along with a +(pattern-wide) store. + +In reality, your pattern is a _constructor_ that takes the user's settings as +input and will return an instantiated pattern. + +That pattern instance will have a `draft()` method which will do the actual work of +drafting the pattern. Once drafted, the pattern can be rendered. + +While the pattern does a lot of heavy lifting behind the scenes, we can content +ourselves by understanding its different building blocks: + + + diff --git a/markdown/dev/guides/patterns/parts/en.md b/markdown/dev/guides/patterns/parts/en.md index 0de62b5c381..7961c24fedf 100644 --- a/markdown/dev/guides/patterns/parts/en.md +++ b/markdown/dev/guides/patterns/parts/en.md @@ -1,18 +1,97 @@ --- title: Parts -order: 20 +order: 120 --- - -Parts divide your pattern into re-usable components - +A pattern is a container for a bunch of parts. And parts are in turn a +container for the the points, paths, and snippets of (a part of) your pattern. +Parts can be re-used and mixed and matched to create other patterns, a powerful +concept to build a pattern library. -Parts are a container for the points, paths, and snippets of (a part of) your pattern. -They are also re-usable by other patterns, which makes them a powerful tool to build -a pattern library. - -If you design a T-shirt pattern with a `front`, `back`, and `sleeve`, each of those would be a part. -If you then wanted to make a long-sleeved version of your T-shirt pattern, you only need to design -a new sleeve part. You can re-use the `front` and `back` parts of your short-sleeved T-shirt pattern, as they did not change. +If you design a T-shirt pattern with a `front`, `back`, and `sleeve`, each of +those would be a part. If you then wanted to make a long-sleeved version of +your T-shirt pattern, you only need to design a new sleeve part. You can re-use +the `front` and `back` parts of your short-sleeved T-shirt pattern, as they did +not change. When developing a FreeSewing pattern, you will spend most of your time designing the individual parts. + + +```mjs +({ Point, points, Path, paths, options, part }) => { + + // Draws a w*h box, returns a Path object + const box = (name, origin, width, height, classes='fabric') => { + let base = height + if (width < height) base = width + let t = base + points[name + 'TopLeft'] = new Point(origin.x, origin.y) + points[name + 'BottomLeft'] = new Point(origin.x, origin.y + height) + points[name + 'BottomRight'] = new Point( + origin.x + width, + origin.y + height + ) + points[name + 'TopRight'] = new Point(origin.x + width, origin.y) + points[name + 'Mid'] = points[name + 'TopLeft'].shiftFractionTowards( + points[name + 'BottomRight'], + 0.5 + ) + points[name + 'Mid'].y += 3 + + return new Path() + .move(points[name + 'TopLeft']) + .line(points[name + 'BottomLeft']) + .line(points[name + 'BottomRight']) + .line(points[name + 'TopRight']) + .line(points[name + 'TopLeft']) + .close() + .addClass(classes) + } + + // Draws a box and handled text placement + const drawBox = (name, x, y, width, height, text=true, classes, textClasses='') => { + points[name + 'Origin'] = new Point(x, y) + paths[name] = box(name, points[name + 'Origin'], width, height, classes) + if (text === 'b') { + paths[name+'_label'] = new Path() + .move(points[name+'BottomLeft']) + .line(points[name+'BottomRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dy', -1) + } + else if (text) points[name + 'Mid'].addText(name, 'center') + } + + // Parts set 0 + drawBox('Part A (set 0)', -27, -27, 44, 70, 'b', 'note fill-note', 'bold') + paths['Part A (set 0)'].attr('fill-opacity', 0.2) + drawBox('points', -24, -24, 38, 12, true, 'note') + drawBox('paths', -24, -9, 38, 12, true, 'note') + drawBox('snippets', -24, 6, 38, 12, true, 'note') + + drawBox('Part B (set 0)', 26, -27, 44, 70, 'b', 'note fill-note', 'bold') + paths['Part B (set 0)'].attr('fill-opacity', 0.2) + drawBox(' points ', 29, -24, 38, 12, true, 'note') + drawBox(' paths ', 29, -9, 38, 12, true, 'note') + drawBox(' snippets ', 29, 6, 38, 12, true, 'note') + + drawBox('Part C (set 0)', 79, -27, 44, 70, 'b', 'note fill-note', 'bold') + paths['Part C (set 0)'].attr('fill-opacity', 0.2) + drawBox(' points ', 82, -24, 38, 12, true, 'note') + drawBox(' paths ', 82, -9, 38, 12, true, 'note') + drawBox(' snippets ', 82, 6, 38, 12, true, 'note') + + drawBox('setStore 0', -24, 21, 144, 12, true, 'lining dashed') + paths['setStore 0'].attr('fill-opacity', 0.2) + + // Pattern + drawBox('Pattern Store', -30, -45, 155, 15, true, 'lining') + paths['Pattern Store'].attr('fill-opacity', 0.2) + drawBox('Pattern', -34, -49, 163, 106, 'b', 'fabric stroke-lg', 'text-lg bold') + + return part +} +``` + + diff --git a/markdown/dev/guides/patterns/paths/en.md b/markdown/dev/guides/patterns/paths/en.md index 71ddcf6e7d6..501b368fd13 100644 --- a/markdown/dev/guides/patterns/paths/en.md +++ b/markdown/dev/guides/patterns/paths/en.md @@ -3,13 +3,9 @@ title: Paths order: 40 --- - -Paths are the lines and curves of your pattern - - Paths are the lines and curves that make up your pattern. +They are made up of individual drawing operations that together make up the path. -They are made up of a set of drawing operations that together make up the path. FreeSewing supports the following types of drawing operations: - The **move** operation moves our virtual pen but does not draw anything. @@ -33,9 +29,92 @@ Understanding that each drawing operation builds upon the next one is an importa + +```mjs +({ Point, points, Path, paths, options, part }) => { + + // Draws a w*h box, returns a Path object + const box = (name, origin, width, height, classes='fabric') => { + let base = height + if (width < height) base = width + let t = base + points[name + 'TopLeft'] = new Point(origin.x, origin.y) + points[name + 'BottomLeft'] = new Point(origin.x, origin.y + height) + points[name + 'BottomRight'] = new Point( + origin.x + width, + origin.y + height + ) + points[name + 'TopRight'] = new Point(origin.x + width, origin.y) + points[name + 'Mid'] = points[name + 'TopLeft'].shiftFractionTowards( + points[name + 'BottomRight'], + 0.5 + ) + points[name + 'Mid'].y += 3 + + return new Path() + .move(points[name + 'TopLeft']) + .line(points[name + 'BottomLeft']) + .line(points[name + 'BottomRight']) + .line(points[name + 'TopRight']) + .line(points[name + 'TopLeft']) + .close() + .addClass(classes) + } + + // Draws a box and handled text placement + const drawBox = (name, x, y, width, height, text=true, classes, textClasses='') => { + points[name + 'Origin'] = new Point(x, y) + paths[name] = box(name, points[name + 'Origin'], width, height, classes) + if (text === 'b') { + paths[name+'_label'] = new Path() + .move(points[name+'BottomLeft']) + .line(points[name+'BottomRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dy', -1) + } + else if (text) points[name + 'Mid'].addText(name, 'center') + } + + // Parts set 0 + drawBox('Part A (set 0)', -27, -27, 44, 70, 'b', 'note', 'bold') + drawBox('points', -24, -24, 38, 12, true, 'note') + drawBox('paths', -24, -9, 38, 12, true, 'note fill-note') + paths['paths'].attr('fill-opacity', 0.2) + drawBox('snippets', -24, 6, 38, 12, true, 'note') + + drawBox('Part B (set 0)', 26, -27, 44, 70, 'b', 'note', 'bold') + drawBox(' points ', 29, -24, 38, 12, true, 'note') + drawBox(' paths ', 29, -9, 38, 12, true, 'note fill-note') + paths[' paths '].attr('fill-opacity', 0.2) + drawBox(' snippets ', 29, 6, 38, 12, true, 'note') + + drawBox('Part C (set 0)', 79, -27, 44, 70, 'b', 'note', 'bold') + drawBox(' points ', 82, -24, 38, 12, true, 'note') + drawBox(' paths ', 82, -9, 38, 12, true, 'note fill-note') + paths[' paths '].attr('fill-opacity', 0.2) + drawBox(' snippets ', 82, 6, 38, 12, true, 'note') + + drawBox('setStore 0', -24, 21, 144, 12, true, 'lining dashed') + paths['setStore 0'].attr('fill-opacity', 0.2) + + // Pattern + drawBox('Pattern Store', -30, -45, 155, 15, true, 'lining') + paths['Pattern Store'].attr('fill-opacity', 0.2) + drawBox('Pattern', -34, -49, 163, 106, 'b', 'fabric stroke-lg', 'text-lg bold') + + return part +} +``` + + Our example image (which, if you hadn't realized was created with FreeSewing) has a lot of paths in it. Each box, the arrows, the lines in the React logo, and so on. +Click the **X-Ray** tab to reveal them. + + + diff --git a/markdown/dev/guides/patterns/points/en.md b/markdown/dev/guides/patterns/points/en.md index 4b87f4d3761..767d33fba68 100644 --- a/markdown/dev/guides/patterns/points/en.md +++ b/markdown/dev/guides/patterns/points/en.md @@ -3,10 +3,6 @@ title: Points order: 30 --- - -Points store coordinates - - Developing a pattern with FreeSewing is similar to doing it on paper. But instead of using a pencil and paper, you'll be writing code. @@ -29,9 +25,92 @@ may intuitively expect. + +```mjs +({ Point, points, Path, paths, options, part }) => { + + // Draws a w*h box, returns a Path object + const box = (name, origin, width, height, classes='fabric') => { + let base = height + if (width < height) base = width + let t = base + points[name + 'TopLeft'] = new Point(origin.x, origin.y) + points[name + 'BottomLeft'] = new Point(origin.x, origin.y + height) + points[name + 'BottomRight'] = new Point( + origin.x + width, + origin.y + height + ) + points[name + 'TopRight'] = new Point(origin.x + width, origin.y) + points[name + 'Mid'] = points[name + 'TopLeft'].shiftFractionTowards( + points[name + 'BottomRight'], + 0.5 + ) + points[name + 'Mid'].y += 3 + + return new Path() + .move(points[name + 'TopLeft']) + .line(points[name + 'BottomLeft']) + .line(points[name + 'BottomRight']) + .line(points[name + 'TopRight']) + .line(points[name + 'TopLeft']) + .close() + .addClass(classes) + } + + // Draws a box and handled text placement + const drawBox = (name, x, y, width, height, text=true, classes, textClasses='') => { + points[name + 'Origin'] = new Point(x, y) + paths[name] = box(name, points[name + 'Origin'], width, height, classes) + if (text === 'b') { + paths[name+'_label'] = new Path() + .move(points[name+'BottomLeft']) + .line(points[name+'BottomRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dy', -1) + } + else if (text) points[name + 'Mid'].addText(name, 'center') + } + + // Parts set 0 + drawBox('Part A (set 0)', -27, -27, 44, 70, 'b', 'note', 'bold') + drawBox('points', -24, -24, 38, 12, true, 'note fill-note') + paths['points'].attr('fill-opacity', 0.2) + drawBox('paths', -24, -9, 38, 12, true, 'note') + drawBox('snippets', -24, 6, 38, 12, true, 'note') + + drawBox('Part B (set 0)', 26, -27, 44, 70, 'b', 'note', 'bold') + drawBox(' points ', 29, -24, 38, 12, true, 'fill-note note') + paths[' points '].attr('fill-opacity', 0.2) + drawBox(' paths ', 29, -9, 38, 12, true, 'note') + drawBox(' snippets ', 29, 6, 38, 12, true, 'note') + + drawBox('Part C (set 0)', 79, -27, 44, 70, 'b', 'note', 'bold') + drawBox(' points ', 82, -24, 38, 12, true, 'note fill-note') + paths[' points '].attr('fill-opacity', 0.2) + drawBox(' paths ', 82, -9, 38, 12, true, 'note') + drawBox(' snippets ', 82, 6, 38, 12, true, 'note') + + drawBox('setStore 0', -24, 21, 144, 12, true, 'lining dashed') + paths['setStore 0'].attr('fill-opacity', 0.2) + + // Pattern + drawBox('Pattern Store', -30, -45, 155, 15, true, 'lining') + paths['Pattern Store'].attr('fill-opacity', 0.2) + drawBox('Pattern', -34, -49, 163, 106, 'b', 'fabric stroke-lg', 'text-lg bold') + + return part +} +``` + + Our example image (which, if you hadn't realized was created with FreeSewing) has a lot of points in it. The corners of the boxes, the location where the text goes, and so on. +Click the **X-Ray** tab to reveal them. + + + diff --git a/markdown/dev/guides/patterns/sets/en.md b/markdown/dev/guides/patterns/sets/en.md new file mode 100644 index 00000000000..3261e0306d3 --- /dev/null +++ b/markdown/dev/guides/patterns/sets/en.md @@ -0,0 +1,340 @@ +--- +title: Sets +order: 100 +--- + +When we refer to a _set_ we mean a _set of settings_. +In the vast majority of cases, there is only one set of settings. So we just +refer to them as _the settings_. But FreeSewing supports instantiating a +pattern with multiple sets of settings. We refer to this as **multiset +support**. + +## Multiset support + +Multiset support underpins features such as +[sampling](/reference/api/pattern/sample) and in general can be used to +_combine_ multiple drafted variants into a single pattern container. + +Here's a simple example: + + +```js +({ + Point, + points, + Path, + paths, + Snippet, + snippets, + measurements, + part +}) => { + + const size = measurements.head + paths.box = new Path() + .move(new Point(0,0)) + .line(new Point(0, size/3)) + .line(new Point(size, size/3)) + .line(new Point(size, 0)) + .addClass('fabric') + .close() + points.logo = new Point(size/2, size/5) + snippets.logo = new Snippet('logo', points.logo) + + return part +} +``` + + +When drafting for multiple sets of settings, it's important to keep the different draft variants from cross-contaminating each other. +To ensure this, the core library will: + +- Set up each pattern part per set +- Provide a per-set store that is shared between parts in the set + +This is illustrated below: + + + + +```mjs +({ Point, points, Path, paths, options, part }) => { + + // Draws a w*h box, returns a Path object + const box = (name, origin, width, height, classes='fabric') => { + let base = height + if (width < height) base = width + let t = base + points[name + 'TopLeft'] = new Point(origin.x, origin.y) + points[name + 'BottomLeft'] = new Point(origin.x, origin.y + height) + points[name + 'BottomRight'] = new Point( + origin.x + width, + origin.y + height + ) + points[name + 'TopRight'] = new Point(origin.x + width, origin.y) + points[name + 'Mid'] = points[name + 'TopLeft'].shiftFractionTowards( + points[name + 'BottomRight'], + 0.5 + ) + points[name + 'Mid'].y += 3 + + return new Path() + .move(points[name + 'TopLeft']) + .line(points[name + 'BottomLeft']) + .line(points[name + 'BottomRight']) + .line(points[name + 'TopRight']) + .line(points[name + 'TopLeft']) + .close() + .addClass(classes) + } + + // Draws and arrow from to and places text + const arrow = (name, text = '') => { + let from = points[name + 'From'] + let to = points[name + 'To'] + from = from.shiftTowards(to, 2) + to = to.shiftTowards(from, 2) + const base = from.dist(to) + const r = 3 + points[name + 'Tip1'] = to.shiftTowards(from, 3.5).rotate(r, from) + points[name + 'Tip2'] = to.shiftTowards(from, 3.5).rotate(r * -1, from) + const path = new Path() + .move(from) + .line(to) + .move(points[name + 'Tip1']) + .line(to) + .line(points[name + 'Tip2']) + .addClass('lining stroke-lg') + if (options.focus === name) path.addClass('note') + + return text + ? path.addText(' ' + text, options.focus === name ? 'fill-note center' : 'center') + : path + } + + // Draws a box and handled text placement + const drawBox = (name, x, y, width, height, text=true, classes, textClasses='') => { + points[name + 'Origin'] = new Point(x, y) + paths[name] = box(name, points[name + 'Origin'], width, height, classes) + if (text === 'b') { + paths[name+'_label'] = new Path() + .move(points[name+'BottomLeft']) + .line(points[name+'BottomRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dy', -1) + } + else if (text === 't') { + paths[name+'_label'] = new Path() + .move(points[name+'TopLeft']) + .line(points[name+'TopRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dy', 11) + } + else if (text === 'r') { + paths[name+'_label'] = new Path() + .move(points[name+'BottomRight']) + .line(points[name+'TopRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dx', -5) + } + else if (text) points[name + 'Mid'].addText(name, 'center') + } + + // Stacks + drawBox('Stack 0', -30, -33, 50, 169, 'b', 'mark', 'fill-mark') + paths['Stack 0'].attr('fill-opacity', 0.2) + drawBox('Stack 1', 23, -33, 50, 169, 'b', 'mark', 'fill-mark') + paths['Stack 1'].attr('fill-opacity', 0.2) + drawBox('Stack 2', 76, -33, 50, 169, 'b', 'mark', 'fill-mark') + paths['Stack 2'].attr('fill-opacity', 0.2) + + // Sets + drawBox('Set 0', -33, -30, 174, 76, 'r', 'contrast fill-contrast', 'bold text-lg fill-contrast') + paths['Set 0'].attr('fill-opacity', 0.2) + drawBox('Set 1', -33, 50, 174, 76, 'r', 'contrast fill-contrast', 'bold text-lg fill-contrast') + paths['Set 1'].attr('fill-opacity', 0.2) + + // Parts set 0 + drawBox('Part A (set 0)', -27, -27, 44, 70, 'b', 'note') + drawBox('points', -24, -24, 38, 12, true, 'note') + drawBox('paths', -24, -9, 38, 12, true, 'note') + drawBox('snippets', -24, 6, 38, 12, true, 'note') + + drawBox('Part B (set 0)', 26, -27, 44, 70, 'b', 'note') + drawBox(' points ', 29, -24, 38, 12, true, 'note') + drawBox(' paths ', 29, -9, 38, 12, true, 'note') + drawBox(' snippets ', 29, 6, 38, 12, true, 'note') + + drawBox('Part C (set 0)', 79, -27, 44, 70, 'b', 'note') + drawBox(' points ', 82, -24, 38, 12, true, 'note') + drawBox(' paths ', 82, -9, 38, 12, true, 'note') + drawBox(' snippets ', 82, 6, 38, 12, true, 'note') + + drawBox('setStore 0', -24, 21, 144, 12, true, 'lining', 'fill-various') + paths['setStore 0'].attr('fill-opacity', 0.2) + + // Parts set 1 + drawBox('Part A (set 1)', -27, 53, 44, 70, 'b', 'note') + drawBox(' points', -24, 56, 38, 12, true, 'note') + drawBox(' paths', -24, 71, 38, 12, true, 'note') + drawBox(' snippets', -24, 86, 38, 12, true, 'note') + + drawBox('Part B (set 1)', 26, 53, 44, 70, 'b', 'note') + drawBox(' points ', 29, 56, 38, 12, true, 'note') + drawBox(' paths ', 29, 71, 38, 12, true, 'note') + drawBox(' snippets ', 29, 86, 38, 12, true, 'note') + + drawBox('Part C (set 1)', 79, 53, 44, 70, 'b', 'note') + drawBox(' points ', 82, 56, 38, 12, true, 'note') + drawBox(' paths ', 82, 71, 38, 12, true, 'note') + drawBox(' snippets ', 82, 86, 38, 12, true, 'note') + + drawBox('setStore 1', -24, 101, 147, 12, true, 'lining', 'fill-various') + paths['setStore 1'].attr('fill-opacity', 0.2) + + // Pattern + drawBox('Pattern Store', -30, -52, 155, 15, true, 'lining', 'fill-lining') + paths['Pattern Store'].attr('fill-opacity', 0.2) + drawBox('Pattern', -43, -59, 195, 216, 'b', 'fabric stroke-lg', 'text-lg bold') + + return part +} +``` + + +## One set is plenty + +In the vast majority of cases, a pattern will only have one set of settings. As such, multiset support is not something you need to be intimately familiar with. +But it is good to know it exists, and explain certain things that might seem _odd_ if you are unaware of multiset support, such as the fact that there's a pattern-wide store, and a different store per set, the so-called setStore(s). + +Below is an illustration of a pattern with a single set of settings which, once again, is the vast majority of use cases: + + +```mjs +({ Point, points, Path, paths, options, part }) => { + + // Draws a w*h box, returns a Path object + const box = (name, origin, width, height, classes='fabric') => { + let base = height + if (width < height) base = width + let t = base + points[name + 'TopLeft'] = new Point(origin.x, origin.y) + points[name + 'BottomLeft'] = new Point(origin.x, origin.y + height) + points[name + 'BottomRight'] = new Point( + origin.x + width, + origin.y + height + ) + points[name + 'TopRight'] = new Point(origin.x + width, origin.y) + points[name + 'Mid'] = points[name + 'TopLeft'].shiftFractionTowards( + points[name + 'BottomRight'], + 0.5 + ) + points[name + 'Mid'].y += 3 + + return new Path() + .move(points[name + 'TopLeft']) + .line(points[name + 'BottomLeft']) + .line(points[name + 'BottomRight']) + .line(points[name + 'TopRight']) + .line(points[name + 'TopLeft']) + .close() + .addClass(classes) + } + + // Draws and arrow from to and places text + const arrow = (name, text = '') => { + let from = points[name + 'From'] + let to = points[name + 'To'] + from = from.shiftTowards(to, 2) + to = to.shiftTowards(from, 2) + const base = from.dist(to) + const r = 3 + points[name + 'Tip1'] = to.shiftTowards(from, 3.5).rotate(r, from) + points[name + 'Tip2'] = to.shiftTowards(from, 3.5).rotate(r * -1, from) + const path = new Path() + .move(from) + .line(to) + .move(points[name + 'Tip1']) + .line(to) + .line(points[name + 'Tip2']) + .addClass('lining stroke-lg') + if (options.focus === name) path.addClass('note') + + return text + ? path.addText(' ' + text, options.focus === name ? 'fill-note center' : 'center') + : path + } + + // Draws a box and handled text placement + const drawBox = (name, x, y, width, height, text=true, classes, textClasses='') => { + points[name + 'Origin'] = new Point(x, y) + paths[name] = box(name, points[name + 'Origin'], width, height, classes) + if (text === 'b') { + paths[name+'_label'] = new Path() + .move(points[name+'BottomLeft']) + .line(points[name+'BottomRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dy', -1) + } + else if (text === 't') { + paths[name+'_label'] = new Path() + .move(points[name+'TopLeft']) + .line(points[name+'TopRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dy', 11) + } + else if (text === 'r') { + paths[name+'_label'] = new Path() + .move(points[name+'BottomRight']) + .line(points[name+'TopRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dx', -5) + } + else if (text) points[name + 'Mid'].addText(name, 'center') + } + + // Stacks + drawBox(' Stack 0', -30, -33, 50, 89, 'b', 'mark', 'fill-mark') + drawBox(' Stack 1', 23, -33, 50, 89, 'b', 'mark', 'fill-mark') + drawBox(' Stack 2', 76, -33, 50, 89, 'b', 'mark', 'fill-mark') + + // Sets + drawBox('Set 0', -33, -30, 174, 76, 'r', 'contrast fill-contrast', 'bold text-lg fill-contrast') + paths['Set 0'].attr('fill-opacity', 0.2) + + // Parts set 0 + drawBox('Part A (set 0)', -27, -27, 44, 70, 'b', 'note') + drawBox('points', -24, -24, 38, 12, true, 'note') + drawBox('paths', -24, -9, 38, 12, true, 'note') + drawBox('snippets', -24, 6, 38, 12, true, 'note') + + drawBox('Part B (set 0)', 26, -27, 44, 70, 'b', 'note') + drawBox(' points ', 29, -24, 38, 12, true, 'note') + drawBox(' paths ', 29, -9, 38, 12, true, 'note') + drawBox(' snippets ', 29, 6, 38, 12, true, 'note') + + drawBox('Part C (set 0)', 79, -27, 44, 70, 'b', 'note') + drawBox(' points ', 82, -24, 38, 12, true, 'note') + drawBox(' paths ', 82, -9, 38, 12, true, 'note') + drawBox(' snippets ', 82, 6, 38, 12, true, 'note') + + drawBox('setStore 0', -24, 21, 144, 12, true, 'lining', 'fill-various') + paths['setStore 0'].attr('fill-opacity', 0.2) + + // Pattern + drawBox('Pattern Store', -30, -52, 155, 15, true, 'lining', 'fill-lining') + paths['Pattern Store'].attr('fill-opacity', 0.2) + drawBox(' Pattern', -43, -59, 195, 128, 'b', 'fabric stroke-lg', 'text-lg bold') + + return part +} +``` + + + diff --git a/markdown/dev/guides/patterns/snippets/en.md b/markdown/dev/guides/patterns/snippets/en.md index eb79ec409dc..1891490a306 100644 --- a/markdown/dev/guides/patterns/snippets/en.md +++ b/markdown/dev/guides/patterns/snippets/en.md @@ -3,10 +3,6 @@ title: Snippets order: 50 --- - -Snippets are little embelishments that go on your pattern - - Snippets are little embellishments you can use and re-use on your pattern. They are typically used for things like logos or buttons. @@ -15,9 +11,110 @@ Each snippet must have: - An anchor point that determine where the snippet will be located - The name of the snippet to insert -Since our example image does not have any snippets in it, here's another example -of a `button`, `buttonhole`, and `logo` snippet added to a FreeSewing pattern: + +```mjs +({ Point, points, Path, paths, options, part }) => { - -An example of the use of snippets + // Draws a w*h box, returns a Path object + const box = (name, origin, width, height, classes='fabric') => { + let base = height + if (width < height) base = width + let t = base + points[name + 'TopLeft'] = new Point(origin.x, origin.y) + points[name + 'BottomLeft'] = new Point(origin.x, origin.y + height) + points[name + 'BottomRight'] = new Point( + origin.x + width, + origin.y + height + ) + points[name + 'TopRight'] = new Point(origin.x + width, origin.y) + points[name + 'Mid'] = points[name + 'TopLeft'].shiftFractionTowards( + points[name + 'BottomRight'], + 0.5 + ) + points[name + 'Mid'].y += 3 + + return new Path() + .move(points[name + 'TopLeft']) + .line(points[name + 'BottomLeft']) + .line(points[name + 'BottomRight']) + .line(points[name + 'TopRight']) + .line(points[name + 'TopLeft']) + .close() + .addClass(classes) + } + + // Draws a box and handled text placement + const drawBox = (name, x, y, width, height, text=true, classes, textClasses='') => { + points[name + 'Origin'] = new Point(x, y) + paths[name] = box(name, points[name + 'Origin'], width, height, classes) + if (text === 'b') { + paths[name+'_label'] = new Path() + .move(points[name+'BottomLeft']) + .line(points[name+'BottomRight']) + .addClass('hidden') + .addText(name, 'center ' + textClasses) + .attr('data-text-dy', -1) + } + else if (text) points[name + 'Mid'].addText(name, 'center') + } + + // Parts set 0 + drawBox('Part A (set 0)', -27, -27, 44, 70, 'b', 'note', 'bold') + drawBox('points', -24, -24, 38, 12, true, 'note') + drawBox('paths', -24, -9, 38, 12, true, 'note') + drawBox('snippets', -24, 6, 38, 12, true, 'note fill-note') + paths['snippets'].attr('fill-opacity', 0.2) + + drawBox('Part B (set 0)', 26, -27, 44, 70, 'b', 'note', 'bold') + drawBox(' points ', 29, -24, 38, 12, true, 'note') + drawBox(' paths ', 29, -9, 38, 12, true, 'note') + drawBox(' snippets ', 29, 6, 38, 12, true, 'note fill-note') + paths[' snippets '].attr('fill-opacity', 0.2) + + drawBox('Part C (set 0)', 79, -27, 44, 70, 'b', 'note', 'bold') + drawBox(' points ', 82, -24, 38, 12, true, 'note') + drawBox(' paths ', 82, -9, 38, 12, true, 'note') + drawBox(' snippets ', 82, 6, 38, 12, true, 'note fill-note') + paths[' snippets '].attr('fill-opacity', 0.2) + + drawBox('setStore 0', -24, 21, 144, 12, true, 'lining dashed') + paths['setStore 0'].attr('fill-opacity', 0.2) + + // Pattern + drawBox('Pattern Store', -30, -45, 155, 15, true, 'lining') + paths['Pattern Store'].attr('fill-opacity', 0.2) + drawBox('Pattern', -34, -49, 163, 106, 'b', 'fabric stroke-lg', 'text-lg bold') + + return part +} +``` + + + +Our example image (which, if you hadn't realized was created with FreeSewing) has a lot of +paths in it. Each box, the arrows, the lines in the React logo, and so on. + +Click the **X-Ray** tab to reveal them. + + + +Since our example image does not have any snippets in it, here's an example +of a `button` snippet: + + +```js +({ Point, Path, paths, Snippet, snippets, part }) => { + + snippets.demo = new Snippet('button', new Point(0,0)) + + // Prevent clipping + paths.diag = new Path() + .move(new Point(-50,-4)) + .move(new Point(50,4)) + + return part +} +``` + + diff --git a/markdown/dev/guides/patterns/stacks/en.md b/markdown/dev/guides/patterns/stacks/en.md new file mode 100644 index 00000000000..36c34d026f6 --- /dev/null +++ b/markdown/dev/guides/patterns/stacks/en.md @@ -0,0 +1,57 @@ +--- +title: Stacks +order: 110 +--- + +Stacks come into play when layouting a pattern. +The FreeSewing core library, by default, will handle the layout of a pattern +for you by placing all parts next to each other in as small a space as +possible. + +That is _typically_ what you want, but not always. For example, when sampling, +you want parts to be stacked on top of each other: + + +```js +({ + Point, + points, + Path, + paths, + Snippet, + snippets, + measurements, + part +}) => { + + const size = measurements.head + paths.box = new Path() + .move(new Point(0,0)) + .line(new Point(0, size/3)) + .line(new Point(size, size/3)) + .line(new Point(size, 0)) + .addClass('fabric') + .close() + points.logo = new Point(size/2, size/5) + snippets.logo = new Snippet('logo', points.logo) + + return part +} +``` + + +Under the hood, sampling uses multiple sets of settings, and then uses stacks +to place them on top of each other. But this functionality is also available +to patterns designers who want to use it. + +Essentially, stacks behave as layers. Parts that are on the same _stack_ will be stacked on top of each other in the layout. + +You can stack parts from the same set, or from different sets. + +Include code example + + +In the vast majority of cases you won't be using any stacks, or the stacking +will be handled for you by the core library (like in the sampling example +above). + diff --git a/markdown/dev/guides/patterns/store/en.md b/markdown/dev/guides/patterns/store/en.md index d8fa98ede6c..0234f1e2185 100644 --- a/markdown/dev/guides/patterns/store/en.md +++ b/markdown/dev/guides/patterns/store/en.md @@ -3,25 +3,21 @@ title: Store order: 60 --- - -The store provides pattern-wide key/value storage - - -The store provides key-value storage that is shared across your pattern. - +The store in a FreeSewing pattern provides shared key-value storage. If you have some information in one part that you want to make available outside that part (in another part) you can save it to the store. -A **Store** object holds a simple key/value store with methods for storing and retrieving information. +There are two types of stores: +- A pattern-wide store that +- A store per set that is shared across parts in that set -## Note +When you interact with a store in your part code, it is almost certainly the +so-called setStore, the store that is shared accross parts in the set. +The pattern-wide store is used for pattern initialization and storing logs and +other data in the early stages of the pattern lifecycle. -A store is typically used to share information between parts. For example -the length of the neck opening in one part can be used to calculate the -length for the collar in another part. -All patterns have access to ```js