1
0
Fork 0

chore: Port FreeSewing.dev to docusaurus

The replaces the NextJS site powering FreeSewing.dev with a Docusaurus
setup. It's part of my efforts to simplify FreeSewing's setup so we can
focus on our core value proposition.
This commit is contained in:
Joost De Cock 2024-09-28 13:13:48 +02:00
parent 497633d1d3
commit ab3204f9f1
692 changed files with 11037 additions and 20674 deletions

View file

@ -0,0 +1,31 @@
---
title: Part.asRenderProps()
---
The `Part.asRenderProps()` method will return the data stored in the
part as a serializable JavaScript object. This method is typically
not invoked directly but rather called under the hood as a result of
calling [`Pattern.getRenderProps()`](/reference/api/pattern/getrenderprops).
## Signature
```js
Object part.asRenderProps()
```
## Returned object properties
This returns JavaScript object has the following properties:
| Name | Description |
| ----:| ----------- |
| `paths` | The part's paths as [Path.asRenderProps()](/reference/api/path/asrenderprops) |
| `points` | The part's points as [Point.asRenderProps()](/reference/api/point/asrenderprops) |
| `snippet` | The part's snippets as [Snippet.asRenderProps()](/reference/api/snippet/asrenderprops) |
| `attributes` | The result of [Part.attributes.asRenderProps()](/reference/api/attributes/asrenderprops) |
| `height` | A number indicating the part height in `mm` |
| `width` | A number indicating the part width in `mm` |
| `topLeft` | The [Point](/reference/api/point) at the top left of the part, or rather its [`Point.asRenderProps()`](/reference/api/point/asrenderprops) result |
| `bottomRight` | The [Point](/reference/api/point) at the bottom right of the part, or rather its [`Point.asRenderProps()`](/reference/api/point/asrenderprops) result |

View file

@ -0,0 +1,57 @@
---
title: Part.attr()
---
This `Part.attr()` method can be used to add attributes to the Part object.
It calls `this.attributes.add()` under the hood, and returns the Part object.
If the third parameter is set to `true` it will call `this.attributes.set()`
instead, thereby overwriting the value of the attribute.
## Signature
```js
Part Part.attr(
string name,
mixed value,
bool overwrite = false
)
```
:::tip
This method is chainable as it returns the `Part` object
:::
## Example
<Example caption=" Example of the Part.attr() method">
```js
({ part, points, Point, Path, paths }) => {
points.A = new Point(0,0)
points.B = new Point(0,40)
points.C = new Point(100,40)
paths.line = new Path()
.move(points.B)
.line(points.C)
.line(points.A)
.line(points.B)
.close()
.addText('I have been flipped!', 'left')
part.attr('transform', 'scale(1,-1) translate(0,-40)')
return part
}
```
</Example>

View file

@ -0,0 +1,76 @@
---
title: Part dependencies
---
Dependencies in a part's configuration object are controlled by the `from` and `after` properties.
:::note
In both cases, you should specify the actual configuration object of the dependency part,
not merely a string with its name.
:::
:::tip
Dependencies configured on parts do not need to be included in the `parts` property
passed to [the Design constructor](/reference/api/design). FreeSewing core will
recursively resolve all dependencies and add them to the design for you.
:::
## after
The `after` property holds an array of parts that should be drafted before the current part:
```js
import { exampleBack } from './back.mjs'
import { exampleFront } from './front.mjs'
const part = {
name: 'example.sleeve',
after: [ exampleBack, exampleFront ],
draft: ({ part }) => part
}
```
The effect of the `after` property is that drafting of this part will be deferred until all the parts listed
in the `after` property are drafted.
:::tip
If you only have one part for the `after` property, you do not have to specify an array:
```js
import { exampleBack } from './back.mjs'
const part = {
name: 'example.front',
after: exampleBack,
draft: ({ part }) => part
}
```
:::
## from
The `from` property holds a part that should be used as the base for the current part.
In other words, the current part will _extend_ the part listed in `front` and inherit all its content.
```js
import { exampleBack } from './back.mjs'
const part = {
name: 'example.front',
from: exampleBack,
draft: ({ part }) => part
}
```
:::warning
Unlike `after`, `from` only ever takes one part since you can only extend one part.
:::

View file

@ -0,0 +1,265 @@
---
title: Hiding parts
---
The `hide` option of a part's configuration controls how to hide it and/or its dependencies.
:::tipA hidden part will not be included in the output when it's rendered:::
:::tipThe `hide` configuration from parts that you include in your design will always override configuration from inherited parts.:::
## Settings
### hide.after
To hide the explicitly included `after` parts, set `hide.after` to a truthy value
```js
import exampleBase from './base.mjs'
const part = {
name: 'example.front',
after: [exampleBase],
// hide `exampleBase`
hide: {after: true},
draft: ({ part }) => part
}
```
### hide.always
To hide a specific part that would otherwise not be hidden by other configuration, add its name to the `hide.always` array
```js
import { exampleBase } from './base.mjs'
import { exampleBack } from './back.mjs'
const part = {
name: 'example.front',
after: [exampleBase, exampleBack],
// hide `exampleBack`
hide: {always: ['example.back']},
draft: ({ part }) => part
}
```
### hide.from
To hide the explicitly included `from` part, set `hide.from` to a truthy value.
```js
import exampleBase from './base.mjs'
const part = {
name: 'other.base',
from: exampleBase,
// hide exampleBase
hide: {from: true},
draft: ({ part }) => part
}
```
### hide.inherited
To hide parts that you have not explicitly included in this part that may be pulled in by the explicitly included `from` and `after` parts, set `hide.inherited` to a truthy value.
:::noteThis setting will hide any part included as `from` or `after` by your explicitly included `from` part or its dependency chain. It will also hide any part included as `from` by your explicitly included `after` part or its dependency chain. It will not hide the `after` parts of `after` parts:::
```js
// the "after" chain
const mainFrontParent = {
name: 'other.mainFront',
draft: ({part}) => part
}
const mainFrontBase = {
name: 'example.mainFrontBase',
draft: ({part}) => part
}
const mainFront = {
name: 'example.mainFront',
after: mainFrontBase,
from: mainFrontParent,
draft: ({part}) => part
}
// the "from" chain
const grandParentBase = {
name: 'other.grandParentBase',
draft: ({part}) => part
}
const grandParent = {
name: 'other.grandParent',
after: grandParentBase
draft: ({ part }) => part
}
const parent = {
name: 'other.parent',
from: grandParent
draft: ({ part }) => part
}
const mainBack = {
name: 'example.mainBack',
from: parent,
after: mainFront,
// hide grandParentBase, grandParent, mainFrontParent
// don't hide parent, mainFront, or mainFrontBase
hide: { inherited: true },
draft: ({part}) => part
}
```
:::tip
<details>
<summary>Need more clarity?</summary>
In the above example, the dependency tree for the part `example.mainBack` resolves to the following, with `from` dependencies in **bold** and `after` dependencies *italicized*.
| Part | Dependency Type | Hidden |
| :---------- | :---------- | :-----|
| example.mainBack | root | false |
| - **other.parent** | from | false |
| - - **other.grandParent** | inherited from | true |
| - - - *other.grandParentBase* | inherited after | true |
| - *example.mainFront* | after | false |
| - - *example.mainFrontBase* | after | false |
| - - **other.mainFront** | inherited from | true |
Dependencies are considered inherited if they have two or more dashes (-) next to them, and are either **bold** themselves, or underneath a **bold** part.
</details>
:::
### hide.never
To __not__ hide a specific part that would otherwise be hidden by other configuration, add its name to the `hide.never` array
```js
import { exampleBase } from './base.mjs'
import { exampleBack } from './back.mjs'
const part = {
name: 'example.front',
after: [exampleBase, exampleBack],
hide: {
// hide exampleBase and exampleBack
after: true,
// override hiding exampleBack so that it is shown
never: ['example.back']
},
draft: ({ part }) => part
}
```
### hide.self
To hide the current part, set `hide.self` to a truthy value:
```js
const part = {
name: 'example.front',
// hide `example.front`
hide: {self: true},
draft: (({ part }) => part)
}
```
## Presets
We provide two presets for common hiding configurations. For convenience, you can pass a preset to the `hide` configuration as a string like `hide: <preset name>`, or you can use `import { hidePresets } from '@freesewing.core` and pass `hide: hidePresets.<preset name>`
:::tip If you don't like to remember strings and you're working in development a environment that has code completion, importing the presets from `@freesewing/core` will help you be sure you're definitely using an available preset :::
### HIDE_ALL
For a shortcut to setting all `boolean` hiding options ([`after`](#hideafter), [`from`](#hidefrom), [`inherited`](#hideinherited), and [`self`](#hideself)) to true, use `HIDE_ALL`
:::note
This is equivalent to using
```js
{
self: true,
after: true,
from: true,
inherited: true
}
```
:::
To use it as an imported preset:
```js
import { hidePresets } from '@freesewing/core'
import { exampleBase } from './base.mjs'
import { exampleBack } from './back.mjs'
const part = {
name: 'example.front',
from: exampleBase
after: [exampleBack],
// hide `example.front`, `exmpleBase`, and `exampleBack`
// as well as any inherited parts
hide: hidePresets.HIDE_ALL,
draft: ({ part }) => part
}
```
To use it as a string
```js
import { exampleBase } from './base.mjs'
import { exampleBack } from './back.mjs'
const part = {
name: 'example.front',
from: exampleBase,
after: [exampleBack],
// hide `example.front`, `exmpleBase`, and `exampleBack`
// as well as any inherited parts
hide: 'HIDE_ALL',
draft: ({ part }) => part
}
```
### HIDE_TREE
For a shortcut to setting [`from: true`](#hidefrom) and [`inherited: true`](#hideinherited), use `HIDE_TREE`
:::note
This is equivalent to using
```js
{
from: true,
inherited: true
}
````
:::
:::note RELATED
See [`hide.inherited`](#hideinherited) for a full explanation of how that option works
:::
To use it as an imported preset:
```js
import { hidePresets } from '@freesewing/core'
import { exampleBase } from './base.mjs'
import { exampleBack } from './back.mjs'
const part = {
name: 'example.front',
from: exampleBase,
// hide `exmpleBase`, and all inherited parts
hide: hidePresets.HIDE_TREE,
draft: ({ part }) => part
}
```
To use it as a string
```js
import { exampleBase } from './base.mjs'
import { exampleBack } from './back.mjs'
const part = {
name: 'example.front',
from: exampleBase,
// hide `exmpleBase`, and all inherited parts
hide: 'HIDE_TREE',
draft: ({ part }) => part
}
```

View file

@ -0,0 +1,39 @@
---
title: Part measurements
---
The `measurements` and `optionalMeasurements` properties on the
part configuration object list the part's required and optional
measurements respectively.
:::tipYou should only include what's required by the part itself, not its dependencies:::
## measurements
The `measurements` property should hold the names of the measurements
that are required to draft the current part.
```js
const part = {
name: 'example.front',
measurements: [ 'head', 'chest' ],
draft: ({ part }) => part
}
```
## optionalMeasurements
The `optionalMeasurements` property should hold the names of the measurements
that are optional to draft the current part.
```js
import { pluginBust } from '@freesewing/plugin-bust'
const part = {
name: 'example.front',
plugins: [ pluginBust ],
measurements: [ 'head', 'chest' ],
optionalMeasurements: [ 'highBust' ],
draft: ({ part }) => part
}
```

View file

@ -0,0 +1,24 @@
---
title: Naming parts
---
The `name` property is -- together with [the `draft()`
method](/reference/api/part/draft) -- the
only mandatory property in a part's configuration object.
It should hold a string and be unique in the design:
```js
const part = {
name: 'example.front',
draft: ({ part }) => part
}
```
:::tip
We recommend to use a `design.part` format when naming your part.
This avoids naming clashes when people re-use your parts in other designs.
:::

View file

@ -0,0 +1,37 @@
---
title: Boolean options
---
For options where the choice is either `true` or `false`, **on** or **off**,
or **yes** or **no**, use a boolean option.
## Structure
A boolean option is a plain object with these properties:
- `bool` : Either `true` or `false` which will be the default
:::tip
Like all options that are configured through an object, you can
add more properties to the options' object to implement functionality on
top of what's provided by the core library.
Refer to [extending options](/reference/api/part/config/options/extend) for
more details.
:::
## Example
```js
const part = {
name: 'example.front',
options: {
withLining: {
bool: true
}
},
draft: ({ part }) => part
}
```

View file

@ -0,0 +1,40 @@
---
title: Constant options
---
If your option is a scalar value (like a string or a number),
it will be treated as a constant. Constant options are never
exposed in the frontend, but can still be set when using FreeSewing
via the API.
## Structure
Any option holding a scalar value is a constant option.
## Example
```js
const part = {
name: 'example.front',
options: {
collarFactor: 4.8,
fitCollar: false,
},
draft: ({ part }) => part
}
```
:::tip
##### Why would you use this?
There are typically two use-cases for constant options:
- Rather than define constants in your code, it's good practice to set
them in your configuration file. This way, people who use your
part as a dependency can override them if they would like to.
- A constant option can be used as a feature-flag. Enabling or disabling
parts of the code beyond the control of the end user, but accessible to
developers.
:::

View file

@ -0,0 +1,37 @@
---
title: Counter options
---
For a given number of things, use a counter option.
Counters are for integers only. Things like number of buttons and so on.
## Structure
Your counter option should be a plain object with these properties:
- `count` : The default integer value
- `min` : The minimum integer value that's allowed
- `max` : The maximum integer value that's allowed
:::tip
Like all options that are configured through an object, you can
add more properties to the options' object to implement functionality on
top of what's provided by the core library.
Refer to [extending options](/reference/api/part/config/options/extend) for
more details.
:::
## Example
```js
options: {
butttons: {
count: 7,
min: 4,
max: 12
}
}
```

View file

@ -0,0 +1,36 @@
---
title: Degree options
---
For angles, use a degree option.
## Structure
Your degree option should be a plain object with these properties:
- `deg` : The default value in degrees
- `min` : The minimum that's allowed
- `max` : The maximum that's allowed
:::tip
Like all options that are configured through an object, you can
add more properties to the options' object to implement functionality on
top of what's provided by the core library.
Refer to [extending options](/reference/api/part/config/options/extend) for
more details.
:::
## Example
```js
options: {
collarAngle: {
deg: 85,
min: 60,
max: 130,
}
}
```

View file

@ -0,0 +1,122 @@
---
title: Extending options
---
Additional, optional information can be added to options to extend
their use outside of core functionality.
This can be useful when using FreeSewing through a frontend UI.
The extended information can be used by the frontend to affect
how options are presented.
## Add menu structure
Because FreeSewing designs have been written with the expectation that
they will be used primarily through the freesewing.org website,
their options have been extended with menu information.
```js
options: {
// Fit
waistEase: { pct: 2, min: 0, max: 10, menu: 'fit', order: '100' },
seatEase: { pct: 5, min: 0, max: 15, menu: 'fit', order: '200' },
// Style
waistHeight: { pct: 5, min: 0, max: 100, menu: 'style', order: '400' },
lengthBonus: { pct: 0, min: -15, max: 10, menu: 'style', order: '300' },
elasticatedCuff: { bool: true, menu: 'style' },
buttons = { count: 7, min: 4, max: 12, menu: 'style.closure', order: '800' }
extraTopButton = { bool: true, menu: 'style.closure', order: '850' }
}
```
In the above example, the added `menu` attributes provide the
freesewing.org website UI with information about the options
should appear in menus.
- The `waistEase` and `seatEase` options should appear in the `fit`
menu while the other options go in the `style` menu.
- Additionally, the `buttons` and `extraTopButton` options should
appear in a `closure` submenu under the `style` menu.
The optional 'order' attributes provide the UI with information about
the order in which options and menus should appear.
- Within the `fit` menu, `waistEase` should come before `seatEase`.
- Within the `style` menu, options should be in the order
`lengthBonus`, `waistHeight`, `buttons`, `extraTopButton`, and
`elasticatedCuff`.
- The `elasticatedCuff` option does not have an `order` attribute,
so it should appear after the options that do.
- Because the `fit` menu has an option with an `order` value that comes
before any of the `order` values for options in the `style` menu,
the `fit` menu should appear before the `style` menu.
:::note
##### This is not a core feature
To be clear, setting this here does not do anything in core.
It's merely extra metadata you can add on the option to facilitate
frontend integration.
:::
:::note
freesewing.org UI behavior:
- Ordering is performed using an alphabetic, not numeric, sort.
For example, `order` value "99" will be placed _after_ "100" because
"1" comes before "9" alphabetically.
However, "099" will be placed before "100", so using leading zeros
can be helpful when using numbers as `order` values.
- After they have been ordered using `order` attribute, if present,
design options and menus are arranged in alphabetical order by
their names.
- However, the `advanced` menu, if present, is always ordered to
be the last menu, appearing after all the other menus.
:::
## Suppress translation
When using `list` options, we usually we want the different options
in the list to be translated.
But sometimes, there is no need for that, like in this example from Breanna:
```js
options: {
primaryBustDart: {
list: [
'06:00',
'07:00',
'08:00',
'09:00',
'10:00',
'11:00',
'11:30',
'12:00',
'12:30',
'13:00',
'13:30',
'14:00',
'15:00',
'16:00',
'17:00',
],
dflt: '06:00',
doNotTranslate: true,
},
// More here
}
```
As you can see above, you can set the `doNotTranslate` property to `true` and to indicate this.
:::note
##### This is not a core feature
To be clear, setting this here does not do anything in core. It's merely extra
metadata you can add on the option to facilitate frontend integration.
:::

View file

@ -0,0 +1,42 @@
---
title: List options
---
Use a list option when you want to offer an array of choices.
## Structure
Your list option should be a plain object with these properties:
- `dflt` : The default for this option
- `list` : An array of available values options
:::tip
Like all options that are configured through an object, you can
add more properties to the options' object to implement functionality on
top of what's provided by the core library.
Refer to [extending options](/reference/api/part/config/options/extend) for
more details.
:::
## Example
```js
options: {
cuffStyle: {
dflt: "angledBarrelCuff",
list: [
"roundedBarrelCuff",
"angledBarrelCuff",
"straightBarrelCuff",
"roundedFrenchCuff",
"angledFrenchCuff",
"straightFrenchCuff"
]
}
}
```

View file

@ -0,0 +1,53 @@
---
title: Millimeter options
---
:::warning
While FreeSewing supports millimeter options, we recommend
using [percentage options][1] and will not accept
contributions that use millimeter options.
:::
## Structure
A millimeter option should be a plain object with these properties:
- `mm` : The default value in millimeters
- `min` : The minimum that's allowed
- `max` : The maximum that's allowed
## Example
```js
options: {
elasticWidth: {
mm: 35,
min: 5,
max: 80
}
}
```
:::note COMMENT (by joost)
##### What's wrong with millimeter options?
Millimeter options do not scale.
Parametric design is the _raison d'être_ of FreeSewing and that core belief
that things should seamlessly adapt goes out the window when you use a `mm`
option because now you have a value that will not change based on the
input measurements.
You could argue that it's fine because _you can just lower the option_
but that breaks the principle of _sensible defaults_ (aka no surprises).
The fact that you can sidestep the bullet does not mean you're not creating
a footgun.
When you need a millimeter option, reach for a [snapped
percentage option][1] instead.
:::
[1]: /reference/api/part/config/options/pct

View file

@ -0,0 +1,94 @@
---
title: Setting a value in millimeter as a percentage option
---
Percentage options are great for parametric design, but not always
very intuitive for the user. For example: A user may desire 13
centimeters (5 inches) of chest ease. But what percentage should
they set the `chestEase` option to to accomplish this?
To address this common grievance, FreeSewing allows you to add a
`fromAbs` method that should take a value in millimeter and
return the percentage the option should be set to to result in this
value.
:::note
Note that this method will not change the percentage of the option.
It will merely return a percentage value. It is up to the
frontend designer to then either set this value, or suggest it to
the user.
:::
## Structure
The `fromAbs` property should hold a function with the following
signature:
```js
function fromAbs(millimeter, settings) {
// return a percentage here (0.5 is 50%)
}
```
The first parameter is the desired value in millimeter (for example
`130` for `13 cm`).
The second parameter is the pattern's [settings](/reference/settings) object
which holds -- among other things -- the measurements provided by the user.
## Example
In our example above, let's say that the `chestEase` option is
a simple percentage of the `chest` measurement. Our option
configuration could like like this:
```js
chestEase: {
pct: 8,
min: 0,
max: 20,
fromAbs: function(millimeter, settings) {
return millimeter / settings.measurements.chest
}
}
```
With object destructuring and fat-arrow notation,
you can write it a bit terser like this:
```js
fromAbs: (val, { measurements }) => val /measurements.chest
```
## Using pctBasedOn for simple measurement fractions
Many percentage options represent a simple fraction of a measurement
(chest circumference in the example above).
As this scenario is so common, `@freesewing/core` exports a `pctBasedOn` method
that will do the work for you:
```js
// First import the method
import { pctBasedOn } from '@freesewing/core'
options: {
chestEase: {
pct: 8,
min: 0,
max: 20,
// Pass the measurement name as parameter
// and spread the return value into your option
...pctBasedOn('chest')
}
}
```
This will not only add an `fromAbs()` method to your option --
one that will return the percentage of any millimeter value passed into it --
it will also add a `toAbs()` method that does the inverse: return the
value in millimeter of whatever percentage the option is set to.
See [Reporting a percentage option value in
millimeter](/reference/api/part/config/options/pct/toabs) for details.

View file

@ -0,0 +1,73 @@
---
title: Percentage options
---
Percentage options are the bread and butter of freesewing.
Almost all your options will most likely be percentage options as
they ensure that your part will scale regardless of size.
## Structure
Your percentage option should be a plain object with these properties:
- `pct` : The default percentage
- `min` : The minimum percentage that's allowed
- `max` : The maximum percentage that's allowed
:::note
###### Percentage options will be divided by 100 when loaded
You specify percentages in your config file. For example, `50` means 50%.
When your configuration is loaded, those percentages will be divided by 100.
So a percentage of `50` in your config file will be `0.5` when you read out that option in your part.
###### Percentage options are not limited to the range 0-100
The minimum and maximum (and default) percentages are not restricted to the range from `0%` to `100%`.
A percentage option that spans from `-25%` to `135%` is just as valid.
:::
:::tip
Like all options that are configured through an object, you can
add more properties to the options' object to implement functionality on
top of what's provided by the core library.
Refer to [extending options](/reference/api/part/config/options/extend) for
more details.
:::
## Example
Below is a simple example:
```js
options: {
acrossBackFactor: {
pct: 97,
min: 93,
max: 100
}
}
```
## Advanced use
Percentage options have more advanced features that are supported by the core library.
You can unlock those features by adding the following properties to your option:
- `fromAbs`: A method to [determine the percentage based on a value in millimeter][fromabs]
- `toAbs`: A method to [return the option value in millimeter][toabs]
- `snap`: The configuration to control [snapping of percentage options][snap]
[fromabs]: /reference/api/part/config/options/pct/fromabs
[toabs]: /reference/api/part/config/options/pct/toabs
[snap]: /reference/api/part/config/options/pct/snap
Refer to the relevant documentation for more details:
<ReadMore />

View file

@ -0,0 +1,249 @@
---
title: Snapped percentage options
---
Snapped percentage options are a hybrid between [list options][list] and
[percentage options][pct]. By combining traits of both, they create a
sort of _smart list option_ that will select the most appropriate value
from the list, and also allow a pure parametric value if no close match
is found.
## Structure
Your snapped percentage option should be a plain object with these properties:
- `pct` : The default percentage
- `min` : The minimum percentage that's allowed
- `max` : The maximum percentage that's allowed
- `snap`: Holds the snap configuration (see below)
- `toAbs`: a method returning the **millimeter value** of the option ([see `toAbs()`][toabs])
:::tip
##### Values for snapped percentage options are available through `absoluteOptions`
Your draft method can not only destructure the `options` property to get access to options,
it can also destructure the `absoluteOptions` property to get access to the values
for those options with snaps configured.
See [the part `draft()` method](/reference/api/part/draft) for more details.
:::
## Snap configuration
A snapped percentage option requires a `snap` property that will determine
what **value in millimeter** to snap to.
There are three different scenarios:
### snap holds a number
When `snap` holds a number, the option will be _snapped_ to a
multiple of this value.
In the example below, the absolute value of this option will be set to a multiple of `7`
(so one of `0 mm`, `7 mm`, `14 mm`, `21 mm`, `28 mm`, `35 mm`, `42 mm`, ...).
```js
myOption: {
pct:5,
min: 0
max: 25,
snap: 7,
toAbs: (pct, { measurements }) => measurements.waistToFloor * pct
}
```
:::note
In a case like this, the value will **always** be snapped,
because the snap points will be distributed equally across the entire range
of all possible inputs.
:::
### snap holds an array of numbers
When snap holds an array of numbers, the option will be _snapped_ to one of
the numbers unless it's further away than half the distance to its closest neighbor.
In the example below, if the absolute value returned by `toAbs()` is in the
region of influence -- in this example between `4.5 mm` and `69.5 mm` -- the nearest snap value
will be used. If instead it is outside the region of influence, the result of
`toAbs()` will be uses as-is.
```js
myOption: {
pct:5,
min: 0
max: 35,
snap: [7, 12, 21, 34, 53, 64 ]
toAbs: (pct, { measurements }) => measurements.waistToFloor * pct
}
```
### snap is a plain object with `metric` and `imperial` properties that each hold either a number or an array of numbers
In this case, the behavior is similar to either when `snap` holds a number or when it holds an array
of numbers.
The difference is that this allows you to supply a different multiple value or list of snap values
for users using metric or imperial units.
In the first example below, the value of [settings.units](/reference/settings/units) will
determine which list of snap values gets used.
Then, if the absolute value returned by `toAbs()` is in the
region of influence -- in this example between `4.5 mm` and `69.5 mm` for metric
and between `12.7 mm` and `88.9 mm` for imperial -- the nearest snap value
will be used. If instead it is outside the region of influence, the result of
`toAbs()` will be used as-is.
```js
myOption: {
pct:5,
min: 0
max: 35,
snap: {
metric: [7, 12, 21, 34, 53, 64 ],
imperial: [25.4, 50.8, 76.2 ],
}
}
```
In this second example, the value of [settings.units](/reference/settings/units) will
determine which multiple value gets used.
If set to `metric`, the absolute value of this option will be set to a multiple of `7`
(so one of `0 mm`, `7 mm`, `14 mm`, `21 mm`, `28 mm`, `35 mm`, `42 mm`, ...).
If set to `imperial`, the absolute value of this option will be set to a
multiple of `25.4` (1 in.)
(so one of `0 mm` (0 in.), `25.4 mm` (1 in.), `50.8 mm` (2 in.), `76.2 mm` (3 in.), ...).
```js
myOption: {
pct:5,
min: 0
max: 35,
snap: {
metric: 7,
imperial: 25.4,
}
}
```
(Again, similar to when `snap` is set to a single number, the snap points
will be distributed equally across the entire range, and
the value will **always** be snapped,)
:::note
##### Read on for an in-depth look at snapped percentage options
While this information above tells you how to use snapped percentage options,
it does not explain why or when you should use them, or how they work.
Read on if you'd like to learn more about that.
:::
## Example use-case
To understand the need that snapped percentage options are addressing,
we'll use an example use-case: We'll be designing a pajama pants pattern
with an elasticated waist.
In our design, the `waistbandWidth` option should match the width of the
elastic we're going to use so we can construct the waistband accordingly.
We have a few different ways we can approach this:
### Approach A: We use a percentage option
We use a percentage option based on a vertical measurement, like
`waistToFloor`.
The elastic width people end up with is something like `34.12 mm` for
user A and `27.83 mm` for user B.
Elastic of that width is not for sale in the store, so that's not great.
### Approach B: We use a list option
We use a list option with a selection of standard elastic
widths to choose from: from half and inch to 3 inches
in 0.5 inch increments.
User A is a doll enthusiasts and 0.5 inch is too big.
User B is working on a giant to go on a float in a parade, and 3 inch
is way too small.
While it would probably work for most people somewhat in the middle,
our solution does not scale.
### Approach C: We use a snapped percentage option
We combine approaches A and B and configure a snapped percentage option
with:
- A percentage based on `waistToFloor`
- Our list of standard elastic widths as _snaps_
For typical humans, our options will _snap_ to the closest match in our
list and behave just like Approach B (with a list option).
For dolls and giants, the option will revert to the parametric value and
behave just like Approach A (with a percentage option).
## How snapped percentage options work
Before we wade into the details of how snapped percentage options are handled
under the hood, let's first agree on terminology:
- The **percentage value** is the value passed by the user for the option.
Its value always represents a percentage. For example `0.5` for 50%.
- The **millimeter value** is the result of feeding the **percentage value** to
the `toAbs()` method. Its value always represents millimeters. For example `12 mm`.
- The **snap values** are the values provided by the snap configuration.
Each of the values always represents millimeters (even for imperial users).
Under the hood, and snapped percentage option will:
- Use `toAbs()` to calculate the **millimeter value** from the **percentage value**
- See whether the **millimeter value** approaches one of the **snap values**
- If so, use the snap value (in millimeter) as provided by one of the **snap values**
- If not, use the **millimeter value** as-is
If you're head's spinning, here's an image that will hopefully clarify things a bit:
![A visual guide to how snapped percentage options work](snap.png)
The gradient box represents the range of any given measurement,
from dolls all the way on the left, to giants all the way on the right.
The sort of middle green-colored region is what the designer had in mind
when designing the pattern, and they have set up snap values -- marked by
a red dot -- for values that they feel make sense.
The region of influence of any given snap point will extend 50% towards its
neighbor on both sides (indicated by the dashed lines).This means that the
region of snap points is continuous, once you're in, you're going to be
snapped to one of the snap points.
However, when you venture out into the area where the designer did not
configure any snap points, the absolute value will be used as-is, without
snapping, just as it would in a normal percentage option.
This system results in the best of both worlds:
- Things like elastic widths and so on can be configured to be fixed values,
of common elastic widths for example
- The absolute value will still scale up and down, but will snap to the closest
fixed value when appropriate.
- When the input measurements go somewhere the designer did not anticipate,
the option will just behave as a regular percentage option
[toabs]: /reference/api/part/config/options/pct/toabs
[pct]: /reference/api/part/config/options/pct
[list]: /reference/api/part/config/options/list

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View file

@ -0,0 +1,91 @@
---
title: Reporting a percentage option value in millimeter
---
Percentage options are great for parametric design, but not always
very intuitive for the user. For example: Setting the `chestEase`
option to `9%` is not very meaningful unless you happen to know
what that percentage is based on.
To address this common grievance, FreeSewing allows you to add a
`toAbs` method that should return the value of the option in
millimeter.
## Structure
The `toAbs` property should hold a function with the following
signature:
```js
function toAbs(percentage, settings, mergeOptions) {
// return value in millimeter here
}
```
The first parameter is the percentage value provided by the user (for example
`0.5` for `50%`).
The second parameter holds the pattern's [settings](/reference/settings) object
which holds -- among other things -- the measurements provided by the user.
The third parameter should be the return value of
[utils.mergeOptions()](/reference/api/utils/mergeoptions), which provides an
object with all option values populated. Although this parameter is not
required for simple values based on measurements, it is often required when the
result depends on several options.
## Example
In our example above, let's say that the `chestEase` option is
a simple percentage of the `chest` measurement. Our option
configuration could like like this:
```js
chestEase: {
pct: 8,
min: 0,
max: 20,
toAbs: function(value, settings) {
return settings.measurements.chest * value
}
}
```
With object destructuring and fat-arrow notation,
you can write it a bit terser like this:
```js
toAbs: (val, { measurements }) => measurements.chest * val
```
## Using pctBasedOn for simple measurement fractions
Many percentage options represent a simple fraction of a measurement
(chest circumference in the example above).
As this scenario is so common, `@freesewing/core` exports a `pctBasedOn` method
that will do the work for you:
```js
// First import the method
import { pctBasedOn } from '@freesewing/core'
options: {
chestEase: {
pct: 8,
min: 0,
max: 20,
// Pass the measurement name as parameter
// and spread the return value into your option
...pctBasedOn('chest')
}
}
```
This will not only add an `toAbs()` method to your option -- one that will return
the value in millimeter of whatever percentage the option is set to -- it will
also add a `fromAbs()` method that does the inverse: return the percentage of
any millimeter value passed into it.
See [Setting a value in millimeter as a
percentage option](/reference/api/part/config/options/pct/fromabs) for details.

View file

@ -0,0 +1,75 @@
---
title: Part options
---
The `options` property on the part configuration object
list the part's options:
```js
const part = {
name: 'example.front',
options: {
chestEase: { pct: 12, min: 0, max: 25 },
},
draft: ({ part }) => part
}
```
## The use case for options
One of the things that sets FreeSewing apart is that sewing patterns are not
static. Each pattern is generated on the spot to accommodate the input
provided by the user. Input that typically includes their measurements.
This _bespoke_ approach is sort of _our thing_ at FreeSewing,
but why stop there?
There's a lot of things that can be left up to the user and taken into
consideration when drafting the pattern. Things like how many buttons to use,
whether or not to include pockets, shape of the collar, and so on. The only
limit really is the creativity of the designer.
The `options` section in a part's configuration is what makes this
possible.
## Types of options
These are the types of options supported by the FreeSewing core library:
1. [**boolean** options][bool] are for yes/no choices
2. [**constant** options][const] are used as [feature flags](https://en.wikipedia.org/wiki/Feature_toggle) or to hard-code certain values yet still allow them to be changed when the part is extended
3. [**counter** options][count] are for integer values
4. [**degree** options][deg] are for degrees
5. [**list** options][list] are for a list of possible choices
6. [**millimeter** options][mm] are supported but not recommended (see warning below)
7. [**percentage** options][pct] are for percentages (and can optionally be [**snapped percentage** options][snapped])
These options can be [extended][extend] with additional, optional
information to help with their use outside of the core.
:::tip
In parametric design, percentage options are by far the most common.
They also have the most features and flexibility.
:::
:::warning
While our core library supports millimeter (`mm`) options,
we do not allow them in designs contributed to FreeSewing.org
as they are a _red flag_ for poor parametric design.
If you believe you need `mm` options, look into [snapped
percentage options][snapped] instead.
:::
[bool]: /reference/api/part/config/options/bool
[const]: /reference/api/part/config/options/const
[count]: /reference/api/part/config/options/counter
[deg]: /reference/api/part/config/options/deg
[list]: /reference/api/part/config/options/list
[pct]: /reference/api/part/config/options/pct
[snapped]: /reference/api/part/config/options/pct/snap
[mm]: /reference/api/part/config/options/mm
[extend]: /reference/api/part/config/options/extend

View file

@ -0,0 +1,68 @@
---
title: Part plugins
---
The `plugins` property on the part configuration object
list the plugins that are used in/required by the part:
```js
import { pluginBundle } from '@freesewing/plugin-bundle'
import { pluginBust } from '@freesewing/plugin-bust'
const part = {
name: 'example.front',
plugins: [ pluginBundle, pluginBust ],
draft: ({ part }) => part
}
```
:::tip
You should only list the plugins that are required by the part itself,
not those required by its dependencies
:::
## Passing data to a plugin
Some plugins require you to pass data to the plugin.
For these, pass an `[plugin, data]` array:
```js
import { pluginBundle } from '@freesewing/plugin-bundle'
import { myDataPlugin } from 'myDataPlugin'
const myData = {
some: 'data'
}
const part = {
name: 'example.front',
plugins: [ pluginBundle, [ myDataPlugin, data ] ],
draft: ({ part }) => part
}
```
## Conditional plugins
A conditional plugin is loaded only when a condition is met.
The plugin and condition should be provided as an `Object`
with the following structure:
```js
import myPlugin from './my-plugin.mjs'
const myConditionalPlugin = {
myPlugin,
condition,
}
```
Where `myPlugin` is the plugin itself, and `condition` is a method
that returns `true` if the plugin should be loaded.
:::note RELATED
Refer to [the plugin guide](/guides/plugins) to learn
about conditional plugins
:::

View file

@ -0,0 +1,14 @@
---
title: "Parts: Configuration"
---
Apart from [the `draft()` method](/reference/api/part/draft) a part
can provide the following configuration properties:
- [`name`](/reference/api/part/config/name) __is mandatory__ and holds the part's name
- [`from` and `after`](/reference/api/part/config/dependencies) list the part's dependencies
- [`hide`, `hideDependencies`, or `hideAll`](/reference/api/part/config/hide) hide the part, its dependencies, or both
- [`measurements` and `optionalMeasurements`](/reference/api/part/config/measurements) lists the part's required or optional measurements
- [`options`](/reference/api/part/config/options) lists the part's options
- [`plugins`](/reference/api/part/config/plugins) lists the part's required plugins
- [`stack`](/reference/api/part/config/stack) assigns the part to a stack

View file

@ -0,0 +1,51 @@
---
title: Assigning parts to stacks
---
The optional `stack` property assigns the part to a specific
[Stack](/reference/api/stack).
It holds either a string with the stack name or a function which
returns the stack name:
```js
const part = {
name: 'example.front',
stack: 'example.combined',
}
```
```js
const part = {
name: 'example.back',
stack: ({options}) => options.stack ? 'example.combined' : 'example.back',
}
```
## Notes
If `stack` is present, the part is assigned to the stack with the
specified name.
If multiple parts are assigned to the same stack, they will overlap
in drafting and printing layouts.
This is because parts in the stack are drafted within the same stack
space.
Otherwise, if the `stack` property is not present, the default behavior
is to use the part's name as its stack name.
- In a draft with only one set, this will result in each part having its
own stack.
With a default layout, the part will not overlap other parts because it is
the only part drafted within its stack and stacks do not overlap.
- In a draft with multiple sets, this will result in parts of the same name
using the same stack.
This is how we achieve the layered look of parts in sample drafts.
:::note RELATED
Please see [Stacks](/guides/designs/stacks) in the Design Guide for
more information about how stacks can be used.
:::

View file

@ -0,0 +1,52 @@
---
title: "The part's draft method"
---
Each part **must** have a `draft` property that holds a method that will draft the part.
In other words, this method is where the actual work happens. The method's signature
is as follows:
```js
function draft(props)
```
The draft method receives a single parameter, an object which you can _destructure_ to
access the following properties:
| Property | Description |
| --------:|:----------- |
|| **_Content constructors_** |
| `Path` | A [Path constructor](/reference/api/path) to create new paths |
| `Point` | A [Point constructor](/reference/api/point) to create new points |
| `Snippet` | A [Snippet constructor](/reference/api/snippet) to create new snippets |
|| **_Content containers_** |
| `paths` | Add a Path to your part by adding it to this object |
| `points` | Add a Points to your part by adding it to this object |
| `snippets` | Add a Snippet to your part by adding it to this object |
|| **_Access to settings_** |
| `absoluteOptions` | Access to `settings.absoluteOptions` |
| `complete` | Access to `settings.complete` |
| `measurements` | Access to `settings.measurements` |
| `options` | Access to `settings.options` |
| `paperless` | Access to `settings.paperless` |
| `sa` | Access to `settings.sa` |
| `scale` | Access to `settings.scale` |
|| **_Access to utilities_** |
| `context` | Allows access to the pattern object and other things higher in the tree |
| `getId` | See [the getId documentation](/reference/api/part/getid) |
| `log` | See [the Store Methods documentation](/reference/store-methods#store-methods-we-maintain) |
| `macro` | See [the macros documentation](/reference/macros/) |
| `store` | See [the store documentation](/reference/api/store) |
| `units` | A version of [`utils.units()`](/reference/api/utils/units) that is preconfigured with the user's chosen units |
| `utils` | See [the utils documentation](/reference/api/utils) |
| `Bezier` | The [bezier-js](https://pomax.github.io/bezierjs/) library's `Bezier` named export |
|| **_Return value_** |
| `part` | Your draft method **must** return this |
:::tip
Please note that there is no `optionalMeasurements` property.
Instead, optional measurements are accessed via the 'measurements'
property.
:::

View file

@ -0,0 +1,37 @@
---
title: Part.getId()
---
The `Part.getId()` method will return an integer the can be used as an
for ID Points/Paths/Snippets. This method will ensure the ID is unique by
keeping an internal incremental counter of the IDs that have been used.
It is typically used when programatically adding points, paths, or snippets.
:::tip
This method can be destructured as `getID`
in [a part's draft method](/reference/api/part/draft).
:::
## Part.getId() signature
```js
int|string getId(prefix='')
```
This method takes an optional parameter that will be used as a prefix for the ID.
## Part.getId() example
```js
cont part = {
name: 'examples.getid',
draft: ({ Point, points, getId, part }) => {
for (let i=0;i<10;i++) {
points[getId()] = new Point(i*10, i*10)
}
return part
}
}
```

View file

@ -0,0 +1,28 @@
---
title: Part.hide()
---
The `Part.hide()` method will mark the part as hidden.
This method returns the `part` object, so it's chainable.
:::tip
This method can be destructured as `hidden`
in [a part's draft method](/reference/api/part/draft).
:::
:::note RELATED
The [unhide](/reference/api/part/unhide) and
[setHidden](/reference/api/part/sethidden) methods also control a
part's visibility
:::
## Part.hide() example
```js
cont part = {
name: 'examples.hide',
draft: ({ hide, part }) => part.hide()
}
```

View file

@ -0,0 +1,73 @@
---
title: Part
---
A `Part` in FreeSewing holds all data, logic, and configuration of a Design.
Parts truly are the building blocks of FreeSewing as they not only provide
the configuration, but also a `draft()` method that does the actual work
of drafting a parametric design.
## Properties
A Part object comes with the following properties:
- `attributes` : An [Attributes](/reference/api/attributes) instance holding
the part's attributes
- `hidden` : When this is `true` the part will be hidden (excluding it from the
output). See [Part.hide()](/reference/api/part/hide),
[Part.unhide()](/reference/api/part/unhide), and
[Part.setHidden()](/reference/api/part/sethidden) for various methods that
allow setting this in a chainable way.
- `name` : The name of the part
- `paths` : Holds the paths used in the part
- `points` : Holds the points used in the part
- `snippets` : Holds the snippets used in the part
:::note RELATED
See [Using Attributes](/howtos/code/attributes)
for information about custom Attributes that can be used with Parts.
:::
## Example
```js
const part = {
name: 'example.part',
from: otherPart,
after: [ yetAnotherPart, oneMorePart ],
measurements: ['head', 'chest' ],
optionalMeasurements: ['neck'],
options: {
headEase: { pct: 12, min: 5, max: 20 }
}
hide: false,
hideAll: false,
hideDependencies: true,
plugins: [
plugin1,
plugin1,
[ plugin3, dataForPlugin3 ],
],
draft: ({ part }) => part
}
```
## Methods
A Part object exposes the following methods:
- [Part.asRenderProps()](/reference/api/part/asrenderprops)
- [Part.attr()](/reference/api/part/attr)
- [Part.getId()](/reference/api/part/getid)
- [Part.hide()](/reference/api/part/hide)
- [Part.setHidden()](/reference/api/part/sethidden)
- [Part.shorthand()](/reference/api/part/shorthand)
- [Part.unhide()](/reference/api/part/unhide)
- [Part.units()](/reference/api/part/units)
## More information
Click below to learn more about:
- [A part's configuration](/reference/api/part/config)
- [A part's `draft()` method](/reference/api/part/draft)

View file

@ -0,0 +1,32 @@
---
title: Part.setHidden()
---
The `Part.setHidden()` method will mark the part either hidden
or not, depending on the value you pass it.
This method returns the `part` object, so it's chainable
- Pass a *truthy* value: The part will be hidden
- Pass a *falsy* value: The part will be unhidden/revealed
:::tip
This method can be destructured as `setHidden`
in [a part's draft method](/reference/api/part/draft).
:::
:::note RELATED
The [hide](/reference/api/part/hide) and
[unhide](/reference/api/part/unhide) also control a
part's visibility
:::
## Part.setHidden() example
```js
cont part = {
name: 'examples.hide',
draft: ({ setHidden, part }) => part.setHidden(true)
}
```

View file

@ -0,0 +1,8 @@
---
title: Part.shorthand()
---
The `Part.shorthand()` method is what is called under the hood to provide the
object you can destructure in [a part's draft method](/reference/api/part/draft).
You probably do not want to call it directly.

View file

@ -0,0 +1,28 @@
---
title: Part.unhide()
---
The `Part.unhide()` method will mark the part as not hidden.
This method returns the `part` object, so it's chainable.
:::tip
This method can be destructured as `inhide`
in [a part's draft method](/reference/api/part/draft).
:::
:::note RELATED
The [hide](/reference/api/part/hide) and
[setHidden](/reference/api/part/sethidden) methods also control a
part's visibility
:::
## Part.unhide() example
```js
cont part = {
name: 'examples.hide',
draft: ({ unhide, part }) => part.unhide()
}
```

View file

@ -0,0 +1,13 @@
---
title: Part.units()
---
The `Part.units()` method returns a value formatted according to the
units set in settings.
:::note
This method is used internally.
You will probably want to the use `units()` method you can destructure
in [a part's draft method](/reference/api/part/draft) instead.
:::