1
0
Fork 0

feat(markdown): Ported pattern guide to v3

This commit is contained in:
Joost De Cock 2022-10-12 00:15:33 +02:00
parent ff8774463e
commit 3fcf229c3c
9 changed files with 1015 additions and 73 deletions

View file

@ -1,22 +0,0 @@
---
title: Configuration
order: 10
---
<Example part="docs_overview" options_focus="config">
The pattern configuration holds important information about the pattern
</Example>
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.

View file

@ -1,5 +1,5 @@
--- ---
title: How patterns work title: Pattern guide
--- ---
This short guide will illustrate and explain how patterns work in FreeSewing. 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 This illustration is a good starting point to gain a better
understanding of the structure of a FreeSewing pattern: understanding of the structure of a FreeSewing pattern:
<Example part="docs_overview"> <Example caption="A schematic overview of what goes on inside 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
}
```
</Example> </Example>
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: If we look at our image, it can be divided into three areas:
- The left area with the **settings** box - [The **Settings**](#the-settings) on the left.
- The middle area with the **Pattern** box and everything in it - [The **Render stage**](#rendering-your-pattern) on the right
- The right area with the **draft** box and the _SVG_ and _React_ logos - [The **Pattern**](#anatomy-of-a-pattern)
Let's take a closer look at everything that is contained within our central **Pattern** box:
<ReadMore />
<Note> <Note>
@ -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 In other words, how you'll plug it into your website, or online store, or a mobile
application. 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.
</Note> </Note>
## 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).
<Tip>
Settings are provided by passing them to the Pattern contructor
</Tip>
<Note>
##### 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).
</Note>
## 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.
<Note compact>
This is what we use on these pages to render with React
</Note>
## 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:
<ReadMore />

View file

@ -1,18 +1,97 @@
--- ---
title: Parts title: Parts
order: 20 order: 120
--- ---
<Example part="docs_overview" options_focus="Part"> A pattern is a container for a bunch of parts. And parts are in turn a
Parts divide your pattern into re-usable components container for the the points, paths, and snippets of (a part of) your pattern.
</Example> 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. If you design a T-shirt pattern with a `front`, `back`, and `sleeve`, each of
They are also re-usable by other patterns, which makes them a powerful tool to build those would be a part. If you then wanted to make a long-sleeved version of
a pattern library. 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
If you design a T-shirt pattern with a `front`, `back`, and `sleeve`, each of those would be a part. not change.
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. When developing a FreeSewing pattern, you will spend most of your time designing the individual parts.
<Example caption="A schematic overview of parts inside a FreeSewing pattern">
```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
}
```
</Example>

View file

@ -3,13 +3,9 @@ title: Paths
order: 40 order: 40
--- ---
<Example part="docs_overview" options_focus="Paths">
Paths are the lines and curves of your pattern
</Example>
Paths are the lines and curves that make up 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: FreeSewing supports the following types of drawing operations:
- The **move** operation moves our virtual pen but does not draw anything. - 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
</Note> </Note>
<Example caption="A schematic overview of where paths are kept inside a FreeSewing pattern">
```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
}
```
</Example>
<Tip> <Tip>
Our example image (which, if you hadn't realized was created with FreeSewing) has a lot of 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. paths in it. Each box, the arrows, the lines in the React logo, and so on.
Click the **X-Ray** tab to reveal them.
</Tip> </Tip>

View file

@ -3,10 +3,6 @@ title: Points
order: 30 order: 30
--- ---
<Example part="docs_overview" options_focus="Points">
Points store coordinates
</Example>
Developing a pattern with FreeSewing is similar to doing it on paper. 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. But instead of using a pencil and paper, you'll be writing code.
@ -29,9 +25,92 @@ may intuitively expect.
</Note> </Note>
<Example caption="A schematic overview of where points are kept inside a FreeSewing pattern">
```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
}
```
</Example>
<Tip> <Tip>
Our example image (which, if you hadn't realized was created with FreeSewing) has a lot of 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. 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.
</Tip> </Tip>

View file

@ -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:
<Example settings="sample: { type: measurement, measurement: head }" withHead caption="A simple example of sampling the `head` measurement">
```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
}
```
</Example>
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:
<Example caption="A schematic overview of what goes on inside a FreeSewing pattern">
```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
}
```
</Example>
## 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:
<Example caption="A schematic overview of what goes on inside a FreeSewing pattern in a typical use-case: a single set of settings">
```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
}
```
</Example>

View file

@ -3,10 +3,6 @@ title: Snippets
order: 50 order: 50
--- ---
<Example part="docs_overview" options_focus="Snippets">
Snippets are little embelishments that go on your pattern
</Example>
Snippets are little embellishments you can use and re-use 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. 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 - An anchor point that determine where the snippet will be located
- The name of the snippet to insert - The name of the snippet to insert
Since our example image does not have any snippets in it, here's another example <Example caption="A schematic overview of where snippets are kept inside a FreeSewing pattern">
of a `button`, `buttonhole`, and `logo` snippet added to a FreeSewing pattern: ```mjs
({ Point, points, Path, paths, options, part }) => {
<Example part="snippet"> // Draws a w*h box, returns a Path object
An example of the use of snippets 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
}
```
</Example> </Example>
<Tip>
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.
</Tip>
Since our example image does not have any snippets in it, here's an example
of a `button` snippet:
<Example caption="An example of the 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
}
```
</Example>

View file

@ -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:
<Example settings="sample: { type: measurement, measurement: head }" withHead caption="A simple example of sampling the `head` measurement">
```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
}
```
</Example>
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.
<Fixme>Include code example</Fixme>
<Note>
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).
</Note>

View file

@ -3,25 +3,21 @@ title: Store
order: 60 order: 60
--- ---
<Example part="docs_overview" options_focus="Store"> The store in a FreeSewing pattern provides shared key-value storage.
The store provides pattern-wide key/value storage
</Example>
The store provides key-value storage that is shared across your pattern.
If you have some information in one part that you want to make available 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. 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
<Example caption="An overview of different stores within a pattern"> <Example caption="An overview of different stores within a pattern">
```js ```js