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,25 @@
---
title: Attributes.add()
---
The `Attributes.add()` method adds `value` to the attribute identified by
`key`.
## Signature
```js
Attributes attributes.add(string key, string value)
```
## Example
```js
const attr = new Attributes()
.add('class', 'classA')
.add('class', 'classB')
```
## Notes
Adding multiple values to the same key will result in them being joined together
(with a space) when rendering.

View file

@ -0,0 +1,30 @@
---
title: Attributes.asPropsIfPrefixIs()
---
The `Attributes.asPropsIfPrefixIs()` method will return attribute values as a
props object (a plain JavaScript object) but only for those keys who start with
`prefix`.
## Signature
```js
Object attributes.asPropsIfPrefixIs(string prefix)
```
## Example
```js
const attr = new Attributes()
.add('class', 'various')
.add('stroke', 'red')
.add('stroke-width', 2)
const props = attr.asPropsIfPrefixIs('stroke')
/* Props holds:
{
stroke: 'red',
stroke-width: 2
}
*/
```

View file

@ -0,0 +1,87 @@
---
title: Attributes.asRenderProps()
---
The `Attributes.asRenderProps()` method will return the data stored in the
attributes as a serializable JavaScript object. This method is typically
note invoked directly but rather called under the hood as a result of
calling [`Pattern.getRenderProps()`](/reference/api/pattern/getrenderprops).
## Signature
```js
Object attributes.asRenderProps()
```
## Returned object properties
This returns JavaScript object has the following properties:
| Name | Description |
| ----:| ----------- |
| `list`| A plain object representation of all attributes |
| `forSvg`| All attributes rendered as a string for inclusion in an SVG (or HTML) tag |
| `forCss`| All attributes rendered as a string for inclusion in CSS |
| `circle`| An array of circles radii to render (set as the `data-circle` attribute) |
| `circleProps`| A plain object representation of all circle-specific attributes (set as the `data-circle-*` attribute) |
| `text`| An array of texts to render (set as the `data-text` attribute) |
| `textProps`| A plain object representation of all text-specific attributes (set as the `data-text-*` attribute) |
## Example
```js
const attr = new Attributes()
.add('class', 'various')
.add('stroke', 'red')
.add('stroke-width', 2)
.add('data-circle', 20)
.add('data-circle-class', 'lining')
.add('data-text', 'test')
.add('data-text-dx', 3)
const json = JSON.stringify(
attr.asRenderProps(), null ,2
)
/* json holds:
{
"list": {
"class": [
"various"
],
"stroke": [
"red"
],
"stroke-width": [
2
],
"data-circle": [
20
],
"data-circle-class": [
"lining"
],
"data-text": [
"test"
],
"data-text-dx": [
3
]
},
"forSvg": " class=\"various\" stroke=\"red\" stroke-width=\"2\" data-circle=\"20\" data-circle-class=\"lining\" data-text=\"test\" data-text-dx=\"3\"",
"forCss": " class:various; stroke:red; stroke-width:2; data-circle:20; data-circle-class:lining; data-text:test; data-text-dx:3;",
"circle": [
20
],
"circleProps": {
"className": "lining"
},
"text": [
"test"
],
"textProps": {
"dx": "3"
}
}
*/
```

View file

@ -0,0 +1,23 @@
---
title: Attributes.clone()
---
The `Attributes.clone()` method returns a new Attributes object that is a deep
copy of this one.
## Signature
```js
Attributes attributes.clone()
```
## Example
```js
const attr = new Attributes()
.add('class', 'classA')
.add('class', 'classB')
const cloned = attr.clone()
```

View file

@ -0,0 +1,25 @@
---
title: Attributes.get()
---
The `Attributes.get()` method will return the value of attribute stored under
`key`, or `false` if it's not set.
## Signature
```js
string attributes.get(string key)
```
If key has multiple values, they will be joined together in a string, separated by spaces.
## Example
```js
const attr = new Attributes()
.add('class', 'classA')
.add('class', 'classB')
const class = attr.get('class')
// class now holds: "classA classB"
```

View file

@ -0,0 +1,23 @@
---
title: Attributes.getAsArray()
---
The `Attributes.getAsArray()` method will return an array with the value of
attribute stored under `key`, or `false` if it's not set.
## Signature
```js
array attributes.getAsArray(string key)
```
## Example
```js
const attr = new Attributes()
.add('class', 'classA')
.add('class', 'classB')
const class = attr.getAsArray('class')
// class now holds: [ "classA", "classB" ]
```

View file

@ -0,0 +1,35 @@
---
title: Attributes
---
An Attributes object holds attributes for a variety of other objects.
## Signature
```js
Attributes new Attributes()
```
The Attributes constructor takes no arguments.
## Properties
An Attributes object comes with the following property:
- `list` : Holds the internal object in which attributes are stored
## Methods
An Attributes object exposes the following methods:
<ReadMore />
## Notes
Attributes are attached to [`Point`](/reference/api/point),
[`Path`](/reference/api/path), and [`Snippet`](/reference/api/snippet) objects,
as well as the internal [`Svg`](/reference/api/svg) object.
All of these have an instantiated Attributes object in their `attributes`
property, as well as a number of (chainable) helper methods to manipulate the
attributes. You will typically use these higher-level method and thus are
unlikely to interact with an Attributes object directly.

View file

@ -0,0 +1,23 @@
---
title: Attributes.remove()
---
The `Attributes.remove()` method removes the attribute values under key and
returns the Attributes object.
## Signature
```js
Attributes attributes.remove(string key)
```
## Example
```js
const attr = new Attributes()
.add('class', 'classA')
.add('class', 'classB')
.remove('class')
// attr holds no data
```

View file

@ -0,0 +1,23 @@
---
title: Attributes.render()
---
The `Attributes.render()` method will render attributes as a string suitable
for inclusion in an SVG tag.
## Signature
```js
string attributes.render()
```
## Example
```js
const attr = new Attributes()
.set('id', 'example')
.add('class', 'classA')
.add('class', 'classb')
const paragraph = `<p ${attr.render()}>Hello</p>`
```

View file

@ -0,0 +1,27 @@
---
title: Attributes.renderAsCss()
---
The `Attributes.renderAsCss()` method will render attributes as a string
suitable for inclusion in a CSS definition.
## Signature
```js
string attributes.renderAsCss()
```
## Example
```js
const attr = new Attributes()
.add('stroke', 'red')
.add('stroke-width', 2)
.add('stroke-dasharray', '3 1')
const css = `
path {
${attr.renderAsCss()}
}
`
```

View file

@ -0,0 +1,24 @@
---
title: Attributes.renderIfPrefixIs()
---
The `Attributes.renderIfPrefixIs()` method will render attributes as a string
suitable for inclusion in an SVG tag, but only those attribute keys who start
with `prefix`.
## Signature
```js
string attributes.renderIfPrefixIs(string prefix)
```
## Example
```js
const attr = new Attributes()
.add('class', 'various')
.add('stroke', 'red')
.add('stroke-width', 2)
const line = `<path ${attr.renderIfPrefixIs('stroke')} d="M 0,0 L 100.0" />`
```

View file

@ -0,0 +1,29 @@
---
title: Attributes.set()
---
The `Attributes.set()` method sets the attribute identified by `key` to value
`value`.
## Signature
```js
Attributes attributes.set(string key, string value)
```
## Example
```js
const attr = new Attributes()
.add('class', 'classA')
.add('class', 'classB')
const class = attr.get('class')
// class now holds: "classA classB"
```
## Notes
This will overwrite any value that's currently set on the attribute identified
by `key`.

View file

@ -0,0 +1,27 @@
---
title: Attributes.setIfUnset()
---
The `Attributes.setIfUnset()` method sets the attribute identified by `key` to
value `value` but only if it's currently unset (`undefined`).
## Signature
```js
Attributes attributes.setIfUnset(string key, string value)
```
## Example
```js
const attr = new Attributes()
.setIfUnset('class', 'classA')
.setIfUnset('class', 'classB')
const class = attr.get('class')
// class now holds: "classA"
```
## Notes
This will never overwrite any value and thus is a safe way to set attributes

View file

@ -0,0 +1,44 @@
---
title: Defs.asRenderProps()
---
Returns the Defs as render props, suitable for front-end rendering.
## Signature
```js
Object defs.asRenderProps()
```
The object returned by this method will have a `list` property that
holds all the defs, as well as a `asSvg` property that holds the result
of [Defs.render()](/reference/api/defs/render):
```js
{
list: {
button: `<g id="button" transform="scale(1)">
<circle cx="0" cy="0" r="3.4" class="mark"></circle>
<circle cx="-1" cy="-1" r="0.5" class="no-stroke fill-mark"></circle>
<circle cx="1" cy="-1" r="0.5" class="no-stroke fill-mark"></circle>
<circle cx="1" cy="1" r="0.5" class="no-stroke fill-mark"></circle>
<circle cx="-1" cy="1" r="0.5" class="no-stroke fill-mark"></circle>
</g>`,
buttonhole: `<g id="buttonhole" transform="scale(1)">
<path class="mark" d="M -1,-5 L 1,-5 L 1,5 L -1,5 z"></path>
</g>`,
},
forSvg: `
<g id="button" transform="scale(1)">
<circle cx="0" cy="0" r="3.4" class="mark"></circle>
<circle cx="-1" cy="-1" r="0.5" class="no-stroke fill-mark"></circle>
<circle cx="1" cy="-1" r="0.5" class="no-stroke fill-mark"></circle>
<circle cx="1" cy="1" r="0.5" class="no-stroke fill-mark"></circle>
<circle cx="-1" cy="1" r="0.5" class="no-stroke fill-mark"></circle>
</g>
<g id="buttonhole" transform="scale(1)">
<path class="mark" d="M -1,-5 L 1,-5 L 1,5 L -1,5 z"></path>
</g>
`
}
```

View file

@ -0,0 +1,11 @@
---
title: Defs.clone()
---
Returns a Defs object that is a deep copy of this one.
## Signature
```js
Defs defs.clone()
```

View file

@ -0,0 +1,12 @@
---
title: Defs.get()
---
Returns the value of a defs entry stored under _name_.
## Signature
```js
String defs.get(string name)
```

View file

@ -0,0 +1,22 @@
---
title: Defs
---
The `Defs` object in FreeSewing's core library represents an SVG document's
[`defs` element](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs).
It is not directly exposed, but it is available as the `defs` attribute
of an [Svg object](/reference/api/svg/defs).
While the properties and methods exposed by this object are typically only used internally,
they are documented here to facilitate development of plugins.
## Properties
A Defs object comes with the following property:
- `list` : Holds the internal object in which entries of the `defs` element are stored
## Defs methods
<ReadMore />

View file

@ -0,0 +1,15 @@
---
title: Defs.remove()
---
Removes the defs entry stored under _name_.
## Signature
```js
Defs defs.remove(string name)
```
This object is chainable as it returns the `Defs` object.

View file

@ -0,0 +1,27 @@
---
title: Defs.render()
---
Renders the added Defs as a string suitable for inclusion in an SVG document `defs` element.
## Signature
```js
String defs.render()
```
This function is called by [svg.render()](/reference/api/svg/render) internally.
Its output will look something like this:
```()
<g id="button" transform="scale(1)">
<circle cx="0" cy="0" r="3.4" class="mark"></circle>
<circle cx="-1" cy="-1" r="0.5" class="no-stroke fill-mark"></circle>
<circle cx="1" cy="-1" r="0.5" class="no-stroke fill-mark"></circle>
<circle cx="1" cy="1" r="0.5" class="no-stroke fill-mark"></circle>
<circle cx="-1" cy="1" r="0.5" class="no-stroke fill-mark"></circle>
</g>
<g id="buttonhole" transform="scale(1)">
<path class="mark" d="M -1,-5 L 1,-5 L 1,5 L -1,5 z"></path>
</g>
```

View file

@ -0,0 +1,17 @@
---
title: Defs.set()
---
Sets the defs entry stored under _name_ to _value_.
## Signature
```js
Defs defs.set(
String name,
String value
)
```
This object is chainable as it returns the `Defs` object.

View file

@ -0,0 +1,17 @@
---
title: Defs.setIfUnset()
---
Sets the defs entry stored under _name_ to _value_, but only if it
is not currently set to any value (if it is undefined).
## Signature
```js
Defs defs.setIfUnset(
String name,
String value
)
```
This object is chainable as it returns the `Defs` object.

View file

@ -0,0 +1,77 @@
---
title: Design
---
The `Design` named export in FreeSewing's core library is a constructor that
creates new pattern designs.
## Signature
```js
Pattern Design({
array parts,
object data
})
```
## Example
```js
const Sorcha = new Design({
// design configuration here
})
```
This constructor creates a new pattern design.
It takes a single argument, an object holding the design's configuration.
## Design configuration
Since a design's configuration is managed at the part level,
the Design configuration object only requires a `parts` property that should
hold an array of parts to include in the Design.
```js
const Sorcha = new Design({
parts: [ front, back, sleeve ],
})
```
:::tipA Design in FreeSewing is little more than a container for various Parts:::
Optionally, you can also pass it a `data` attribute
to hold any custom data you'd like to add to your Design.
Any `data` you add to the Design constructor will be added
to [the Store](/reference/api/store).
```js
const Sorcha = new Design({
parts: [ front, back, sleeve ],
data: {
version: 3,
price: 12,
currency: 'euro'
}
})
```
## Notes
The Design constructor is a _super-constructor_.
It will return a constructor method that will a pattern based on your design.
## Properties
In addition to the returned constructor method, an instantiated Design object
also provides the following properties:
### Design.designConfig
This holds the design configuration as passed to the Design constructor.
### Design.patternConfig
Holds the resolved pattern configuration based on the configuration passed to the Design constructor.

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.
:::

View file

@ -0,0 +1,48 @@
---
title: "Path._curve()"
---
The `Path._curve()` method draws a cubic Bézier curve
from the current position via two control points to a given endpoint.
However, the start control point is identical to the current position,
so you do not need to provide it.
## Signature
```js
Path path._curve(Point cp2, Point to)
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.\_curve() method">
```js
({ Point, points, Path, paths, part }) => {
points.from = new Point(5, 20)
points.cp2 = new Point(60, 50)
points.to = new Point(90, 20)
paths.line = new Path()
.move(points.from)
._curve(points.cp2, points.to)
.setText("Path._curve()", "text-sm center fill-note")
.attr("data-text-dy", -1)
return part
}
```
</Example>
## Notes
The main purpose of this method is to save your some typing,
as the two following calls yield the same result:
```js
path.curve(point1, point1, point2)
path._curve(point1, point2)
```

View file

@ -0,0 +1,42 @@
---
title: Path.addClass()
---
The `Path.addClass()` method adds a CSS class to the path.
## Signature
```js
Path path.addClass(string className)
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.addClass() method">
```js
({ Point, points, Path, paths, part }) => {
points.from = new Point(5, 10)
points.to = new Point(95, 10)
paths.line = new Path()
.move(points.from)
.line(points.to)
.addClass('note dashed')
return part
}
```
</Example>
## Notes
The main purpose of this method is to save your some typing,
as the two following calls yield the same result:
```js
path.attr('class', 'fabric')
path.addClass('fabric')
```

View file

@ -0,0 +1,47 @@
---
title: Path.addText()
---
The `Path.addText()` method adds text to the path.
## Signature
```js
Path path.addText(string text, string className = '')
```
The second argument will optionally be used to set the CSS class for the text.
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.addText() method">
```js
({ Point, points, Path, paths, part }) => {
points.from = new Point(5, 10)
points.to = new Point(95, 10)
paths.line = new Path()
.move(points.from)
.line(points.to)
.addText('FreeSewing rocks')
return part
}
```
</Example>
## Notes
The main purpose of this method is to save your some typing,
as the two following calls yield the same result:
```js
path.attr('data-text', 'Hello')
path.addText('Hello')
```
The difference with [Path.setText()](/reference/api/path/addtext) is that this
method will add to the existing text whereas `Path.setText()` will overwrite
existing text on the path,

View file

@ -0,0 +1,54 @@
---
title: Path.angleAt()
---
The `Path.angleAt()` method returns the (tangent) angle of a path at a specific point.
If the given point is a sharp corner, this method prefers returning the angle directly before the corner.
If the given point does not lie (approximately) on the path, this method returns `false`.
## Signature
```js
number|false path.angleAt(Point point)
```
## Example
<Example caption="Example of the Path.angleAt() method">
```js
({ Point, points, Path, paths, snippets, Snippet, part }) => {
points.A = new Point(45, 60)
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
points.D = new Point(50, 80)
points.DCp1 = new Point(70, 30)
paths.demo = new Path()
.move(points.D)
.curve(points.DCp1, points.DCp1, points.C)
.curve(points.CCp1, points.BCp2, points.B)
.line(points.A)
points.testPoint = paths.demo.shiftFractionAlong(0.55)
snippets.point = new Snippet("notch", points.testPoint)
let angle = paths.demo.angleAt(points.testPoint)
//draw a tangent path
paths.tangent = new Path()
.move(points.testPoint.shift(angle, -30))
.line(points.testPoint.shift(angle, 30))
.attr("class", "lining dashed")
return part
}
```
</Example>
## Notes
Keep in mind that calculations with Bézier curves are often approximations.

View file

@ -0,0 +1,17 @@
---
title: Path.asPathString()
---
This `Path.asPathString()` returns the path as a string that can be used
as the `d` attribute for an SVG `path` element.
## Signature
```js
string path.asPathString()
```
## Notes
This method is mostly aimed at people looking to implement their own rendering
methods, or integrate with SVG processing tools.

View file

@ -0,0 +1,31 @@
---
title: Path.asRenderProps()
---
The `Path.asRenderProps()` method will return the data stored in the
path 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 path.asRenderProps()
```
## Returned object properties
This returns JavaScript object has the following properties:
| Name | Description |
| ----:| ----------- |
| `attributes` | The result of [Path.attributes.asRenderProps()](/reference/api/attributes/asrenderprops) |
| `hidden` | A boolean indicating whether the path is hidden or not |
| `name` | The path name |
| `ops` | An array of drawing operations |
| `height` | A number indicating the path height in `mm` |
| `width` | A number indicating the path 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 |
| `d` | The path's pathstring for rendering as SVG |

View file

@ -0,0 +1,56 @@
---
title: Path.attr()
---
This `Path.attr()` method can be used to add attributes to the Path object.
It calls `this.attributes.add()` under the hood, and returns the Path object.
If the optional third parameter is set to `true` it will call `this.attributes.set()`
instead, thereby overwriting the value of the attribute.
## Signature
```js
Path path.attr(
string name,
mixed value,
bool overwrite = false
)
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption=" Example of the Path.attr() method">
```js
({ Point, points, Path, paths, part }) => {
points.from = new Point(10, 20)
points.cp1 = new Point(20, -10)
points.cp2 = new Point(50, 50)
points.to = new Point(70, 20)
paths.example = new Path()
.move(points.from)
.curve(points.cp1, points.cp2, points.to)
.attr("class", "canvas")
.setText("FreeSewing rocks", "text-xs center")
return part
}
```
</Example>
## Notes
Methods like
[`Path.addClass`](/reference/api/path/addclass),
[`Path.setClass`](/reference/api/path/setclass),
[`Path.addText`](/reference/api/path/addtext), and
[`Path.setText`](/reference/api/path/settext)
all call this method under the hood.
See [Using Attributes](/howtos/code/attributes)
for information about custom Attributes that can be used with Paths.

View file

@ -0,0 +1,25 @@
---
title: Path.bbox()
---
The `Path.bbox()` method returns an object describing the bounding box of a path.
In other words, it gives you a rectangle the Path fits in.
## Signature
```js
object path.bbox()
```
It returns an object with a signature like this:
```js
{
topLeft: Point,
bottomRight: Point
}
```
## Notes
This method is mostly aimed at people looking to implement their own layout solution.

View file

@ -0,0 +1,52 @@
---
title: Path.circleSegment()
---
The `Path.circleSegment()` method draws a circle segment
starting from the current endpoint of the path around the given origin with a given angle.
A positive angle results in a counter-clockwise arc.
A negative angle results in a clockwise arc.
:::tip
The new endpoint of this path is the same point
that
```js
path.end().rotate(deg, origin)
```
would return.
:::
## Signature
```js
Path path.circleSegment(deg, origin)
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.circleSegment() method">
```js
({ Point, points, Path, paths, part }) => {
points.from = new Point(10, 20)
points.origin = new Point(40, 0)
paths.line = new Path()
.move(points.from)
.circleSegment(90, points.origin)
.setText("→ Path.circleSegment() →", "text-sm center fill-note")
paths.helper = new Path()
.move(paths.line.start())
.line(points.origin)
.line(paths.line.end())
.setClass('dotted stroke-sm')
return part
}
```
</Example>

View file

@ -0,0 +1,60 @@
---
title: Path.clean()
---
The `Path.clean()` method removes spurious drawing operations from a path.
A _spurious_ drawing operation is one that has no effect, but can still cause
problems if left in place. For example, a line from a given point to the same
given point will not cause any problems as such, but can trip up things like
path offset and other methods. For this reason, such drawing operations can be
cleaned up with the `Path.clean()` method.
As this method is called under the hood to guard against various scenarios
where spurious segments could cause an issue, you should have no need to call
this method yourself explicitly, but it's there if you need it. path that you
pass it.
## Signature
```js
Path path.clean()
```
## Example
<Example caption="Example of the Path.clean() method">
```js
({ Point, points, Path, paths, snippets, Snippet, part }) => {
points.A = new Point(10, 10)
points.B = new Point(10, 20)
points.C = new Point(10, 30)
points.D = new Point(90, 10)
points.E = new Point(90, 20)
points.F = new Point(90, 30)
paths.a = new Path()
.move(points.A)
.line(points.C)
.line(points.B)
.line(points.B) // spurious op
.line(points.E)
.line(points.F)
.curve_(points.F, points.F) // another spurious op
.line(points.D)
.addClass('lining')
paths.b = paths.a
.clone()
.clean()
.addClass('interfacing')
paths.a.addText(`${paths.a.ops.length} ops in a`, 'center fill-lining')
paths.b.addText(`${paths.b.ops.length} ops in b`, 'center fill-note')
.attr('data-text-dy', 7)
return part
}
```
</Example>

View file

@ -0,0 +1,38 @@
---
title: Path.clone()
---
The `Path.clone()` method returns a new `Path` object that is a deep copy of this path.
## Signature
```js
Path path.clone()
```
## Example
<Example caption="Example of the Path.clone() method">
```js
({ Point, points, Path, paths, part }) => {
points.A = new Point(45, 60)
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
paths.example = new Path()
.move(points.A)
.line(points.B)
.curve(points.BCp2, points.CCp1, points.C)
paths.clone = paths.example
.clone()
.setClass("note lashed stroke-xl")
.attr("style", "stroke-opacity: 0.5")
return part
}
```
</Example>

View file

@ -0,0 +1,35 @@
---
title: Path.close()
---
The `Path.close()` method closes a path by drawing a straight line from the current position to the path's start.
## Signature
```js
Path path.close()
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.close() method">
```js
({ Point, points, Path, paths, part }) => {
points.from = new Point(10, 20)
points.cp2 = new Point(60, 30)
points.to = new Point(90, 20)
paths.line = new Path()
.move(points.from)
._curve(points.cp2, points.to)
.close()
.reverse() // To keep text from being upside-down
.setText('Path._close()', 'text-sm right fill-note')
return part
}
```
</Example>

View file

@ -0,0 +1,58 @@
---
title: Path.combine()
---
The `Path.combine()` method combines this path with one or more other paths
into a single Path instance.
Any gaps in the path (caused by move operations) will be left as-is, rather
than joined with a line. If that's not what you want, you should use
[`Path.join()`](/reference/api/path/join) instead.
## Signature
```js
Path path.combine(path other)
```
## Examples
<Example caption="Example of the Path.combine() method">
```js
({ Point, points, Path, paths, part }) => {
points.A1 = new Point(0, 0)
points.A2 = new Point(60, 0)
points.B1 = new Point(0, 10)
points.B2 = new Point(60, 10)
points.C1 = new Point(0, 20)
points.C2 = new Point(60, 20)
paths.path1 = new Path()
.move(points.A1)
.line(points.A2)
.setClass("various")
paths.path2 = new Path()
.move(points.B1)
.line(points.B2)
.setClass("note")
paths.path3 = new Path()
.move(points.C1)
.line(points.C2)
.setClass("canvas")
paths.combo = paths.path1
.combine(paths.path2, paths.path3)
.setClass("lining dotted")
return part
}
```
</Example>
## Notes
`Path.combine()` method is _variadic_, so you can pass multiple paths to join

View file

@ -0,0 +1,35 @@
---
title: Path.curve()
---
The `Path.curve()` method draws a cubic Bézier curve from the current position
via two control points to a given endpoint.
## Signature
```js
Path path.curve(Point cp1, Point cp2, Point to)
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.curve() method">
```js
({ Point, points, Path, paths, part }) => {
points.from = new Point(10, 20)
points.cp1 = new Point(40, 0)
points.cp2 = new Point(60, 40)
points.to = new Point(90, 20)
paths.line = new Path()
.move(points.from)
.curve(points.cp1, points.cp2, points.to)
.setText("Path.curve()", "text-sm center fill-note")
return part
}
```
</Example>

View file

@ -0,0 +1,48 @@
---
title: Path.curve_()
---
The `Path.curve_()` method draws a cubic Bézier curve from the current position
via two control points to a given endpoint. However, the end control point is
identical to the end point.
## Signature
```js
Path path.curve_(Point cp1, Point to)
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.curve\_() method">
```js
({ Point, points, Path, paths, part }) => {
points.from = new Point(10, 20)
points.cp1 = new Point(40, 0)
points.to = new Point(90, 20)
paths.line = new Path()
.move(points.from)
.curve_(points.cp1, points.to)
.setText("Path.curve_()", "text-sm center fill-note")
.attr("data-text-dy", -1)
return part
}
```
</Example>
## Notes
The main purpose of this method is to save your some typing,
as the two following calls yield the same result:
```js
.curve(point1, point2, point2)
.curve_(point1, point2)
```

View file

@ -0,0 +1,48 @@
---
title: Path.divide()
---
The `Path.divide()` method breaks a path apart in an array of atomic paths. An
atomic path is a path that can't be divided further and is always made up of
one move + one drawing operation.
## Signature
```js
array path.divide()
```
## Example
<Example caption="Example of the Path.divide() method">
```js
({ Point, points, Path, paths, part }) => {
points.A = new Point(55, 40)
points.B = new Point(10, 70)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 60)
points.CCp1 = new Point(50, -30)
points.D = new Point(50, 80)
points.DCp1 = new Point(140, 50)
paths.example = new Path()
.move(points.A)
.line(points.B)
.curve(points.BCp2, points.CCp1, points.C)
.curve(points.DCp1, points.DCp1, points.D)
.close()
let style = "stroke-width: 4; stroke-opacity: 0.5;"
let i = 0
for (let p of paths.example.divide()) {
i++
paths[i] = p
.attr("style", style)
.attr('style', `stroke: hsl(${i * 70}, 100%, 50%)`)
}
return part
}
```
</Example>

View file

@ -0,0 +1,53 @@
---
title: Path.edge()
---
```js
Point path.edge(string side)
```
Returns the Point object at the edge of the path you specify. Edge must be one of:
- `top`
- `bottom`
- `left`
- `right`
- `topLeft`
- `topRight`
- `bottomLeft`
- `bottomRight`
<Example caption="Example of the Path.edge() method">
```js
({ Point, points, Path, paths,snippets, Snippet, part }) => {
points.A = new Point(45, 60)
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
points.D = new Point(-60, 90)
points.E = new Point(90, 190)
paths.demo = new Path()
.move(points.A)
.line(points.B)
.curve(points.BCp2, points.CCp1, points.C)
.curve(points.E, points.D, points.A)
.close()
for (let i of [
"topLeft",
"topRight",
"bottomLeft",
"bottomRight",
"top",
"left",
"bottom",
"right"
]) snippets[i] = new Snippet("notch", paths.demo.edge(i))
return part
}
```
</Example>

View file

@ -0,0 +1,37 @@
---
title: Path.end()
---
The `Path.end()` method returns the Point object at the end of the path.
## Signature
```js
Point path.end()
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.end() method">
```js
({ Point, points, Path, paths, snippets, Snippet, part }) => {
points.A = new Point(45, 60)
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
paths.demo = new Path()
.move(points.A)
.line(points.B)
.curve(points.BCp2, points.CCp1, points.C)
snippets.end = new Snippet("notch", paths.demo.end())
return part
}
```
</Example>

View file

@ -0,0 +1,32 @@
---
title: Path.hide()
---
The `Path.hide()` hides the path so it does not appear in the output.
## Signature
```js
Path path.hide()
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.hide() method">
```js
({ Point, points, Path, paths, part }) => {
points.top = new Point(50, 0)
points.left = new Point (20,50)
points.right = new Point (80,50)
paths.a = new Path().move(points.top).line(points.right).setText('a')
paths.b = new Path().move(points.right).line(points.left).setText('b').hide()
paths.c = new Path().move(points.left).line(points.top).setText('c')
return part
}
```
</Example>

View file

@ -0,0 +1,50 @@
---
title: Path.insop()
---
The `Path.insop()` method injects a Path into the [`noop`
operation](/reference/api/path/noop) with id `id`.
## Signature
```js
Path path.insop(string id, Path path)
```
:::tipThis method is chainable as it returns the `Path` object:::
<Example caption="Example of the Path.insop() method">
```js
({ Point, points, Path, paths, part }) => {
points.left = new Point(10,10)
points.dartLeft = new Point(40, 10)
points.dartTip = new Point(50, 50)
points.dartRight = new Point(60, 10)
points.right = new Point(90, 10)
paths.withoutDart = new Path()
.move(points.left)
.line(points.dartLeft)
.noop('dart')
.line(points.right)
paths.withDart = paths.withoutDart
.clone()
.insop(
'dart',
new Path()
.line(points.dartTip)
.line(points.dartRight)
)
.attr('style', 'stroke-width: 2px; stroke-opacity: 0.5; stroke: orange;')
return part
}
```
</Example>
## Notes
This is often used to insert darts into a path.

View file

@ -0,0 +1,70 @@
---
title: Path.intersects()
---
The `Path.intersects()` method returns the Point object(s) where the path
intersects with a path you pass it.
:::warning
This method can sometimes fail to find intersections in some curves
due to a limitation in an underlying Bézier library.
Please see [Bug #3367](https://github.com/freesewing/freesewing/issues/3367)
for more information.
:::
## Signature
```
array|false path.intersects(Path path)
```
<Example caption="Example of the Path.intersects() method">
```js
({ Point, points, Path, paths, snippets, Snippet, getId, part }) => {
points.A = new Point(45, 60)
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
points.D = new Point(50, 130)
points.DCp1 = new Point(150, 30)
points._A = new Point(55, 40)
points._B = new Point(0, 55)
points._BCp2 = new Point(40, -20)
points._C = new Point(90, 40)
points._CCp1 = new Point(50, -30)
points._D = new Point(40, 120)
points._DCp1 = new Point(180, 40)
paths.demo1 = new Path()
.move(points.A)
.line(points.B)
.curve(points.BCp2, points.CCp1, points.C)
.curve(points.DCp1, points.DCp1, points.D)
paths.demo2 = new Path()
.move(points._A)
.line(points._B)
.curve(points._BCp2, points._CCp1, points._C)
.curve(points._DCp1, points._DCp1, points._D)
for (let p of paths.demo1.intersects(paths.demo2)) {
snippets[getId()] = new Snippet('notch', p)
}
return part
}
```
</Example>
## Notes
This is an expensive (read: slow) method that you should only use when you don't know
in advance in what segment of your path the intersection will occur.
If you do know, use one of the intersection methods in [Utils](/reference/api/utils).

View file

@ -0,0 +1,58 @@
---
title: Path.intersectsX()
---
The `Path.intersectsX()` method returns the Point object(s) where the path
intersects with a given X-value.
:::warning
This method can sometimes fail to find intersections in some curves
due to a limitation in an underlying Bézier library.
Please see [Bug #3367](https://github.com/freesewing/freesewing/issues/3367)
for more information.
:::
## Signature
```js
array|false path.intersectsX(float x)
```
## Example
<Example caption="Example of the Path.intersectsX() method">
```js
({ Point, points, Path, paths, snippets, Snippet, getId, part }) => {
points.A = new Point(95, 50)
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
points.D = new Point(50, 130)
points.DCp1 = new Point(150, 30)
points.top = new Point(60, -10)
points.bot = new Point(60, 140)
paths.line = new Path()
.move(points.top)
.line(points.bot)
.attr("class", "lining dashed")
paths.demo = new Path()
.move(points.A)
.line(points.B)
.curve(points.BCp2, points.CCp1, points.C)
.curve(points.DCp1, points.DCp1, points.D)
for (let p of paths.demo.intersectsX(60)) {
snippets[getId()] = new Snippet("notch", p)
}
return part
}
```
</Example>

View file

@ -0,0 +1,58 @@
---
title: Path.intersectsY()
---
The `Path.intersectsY()` method returns the Point object(s) where the path
intersects with a given Y-value.
:::warning
This method can sometimes fail to find intersections in some curves
due to a limitation in an underlying Bézier library.
Please see [Bug #3367](https://github.com/freesewing/freesewing/issues/3367)
for more information.
:::
## Signature
```js
array|false path.intersectsY(float y)
```
## Example
<Example caption="Example of the Path.intersectsY() method">
```js
({ Point, points, Path, paths, snippets, Snippet, getId, part }) => {
points.A = new Point(55, 40)
points.B = new Point(10, 70)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 60)
points.CCp1 = new Point(50, -30)
points.D = new Point(50, 80)
points.DCp1 = new Point(140, 50)
points.top = new Point(10, 58)
points.bot = new Point(130, 58)
paths.line = new Path()
.move(points.top)
.line(points.bot)
.attr("class", "lining dashed")
paths.demo = new Path()
.move(points.A)
.line(points.B)
.curve(points.BCp2, points.CCp1, points.C)
.curve(points.DCp1, points.DCp1, points.D)
for (let p of paths.demo.intersectsY(58)) {
snippets[getId()] = new Snippet("notch", p)
}
return part
}
```
</Example>

View file

@ -0,0 +1,59 @@
---
title: Path.join()
---
The `Path.join()` method joins this path with one or more other paths.
Any gaps in the path (caused by move operations) will be filled-in with a line.
If that's not what you want, you should use
[`Path.combine()`](/reference/api/path/combine) instead.
## Signature
```js
Path path.join(path other)
```
## Examples
<Example caption="Example of the Path.join() method">
```js
({ Point, points, Path, paths, part }) => {
points.A1 = new Point(0, 0)
points.A2 = new Point(60, 0)
points.B1 = new Point(0, 10)
points.B2 = new Point(60, 10)
points.C1 = new Point(0, 20)
points.C2 = new Point(60, 20)
paths.path1 = new Path()
.move(points.A1)
.line(points.A2)
.setClass("various")
paths.path2 = new Path()
.move(points.B1)
.line(points.B2)
.setClass("note")
paths.path3 = new Path()
.move(points.C1)
.line(points.C2)
.setClass("canvas")
paths.joint = paths.path1
.join(paths.path2, paths.path3)
.setClass("lining dotted")
return part
}
```
</Example>
## Notes
- `Path.join()` is _variadic_, so you can pass multiple paths to join
- You cannot join a closed path to another path

View file

@ -0,0 +1,49 @@
---
title: Path.length()
---
The `Path.length()` method returns the length of the path.
## Signature
```js
float path.length(bool withMoves = false)
```
## Example
<Example caption="Example of the Path.length() method">
```js
({ Point, points, Path, paths, units, part }) => {
points.A1 = new Point(0, 0)
points.A2 = new Point(160, 0)
points.B1 = new Point(0, 10)
points.B2 = new Point(160, 10)
points.C1 = new Point(0, 20)
points.C2 = new Point(160, 20)
paths.path1 = new Path()
.move(points.A1)
.line(points.A2)
.move(points.B1)
.line(points.B2)
.move(points.C1)
.line(points.C2)
.setClass("various")
points.label1 = new Point(25, 8).addText('Total length = ' + units(paths.path1.length()))
points.label2 = new Point(25, 18).addText('Total length with moves = ' + units(paths.path1.length(true)))
return part
}
```
</Example>
## Notes
By default, `Path.length()` will measure the combined length of all drawing operations in the Path, but skip
over gaps in the path (caused by move operations).
If you want the full length of the Path, including move operations, pass `true` to `Path.length()`.

View file

@ -0,0 +1,33 @@
---
title: Path.line()
---
The `Path.line()` method draws a straight line from the current position to a
given point.
## Signature
```js
Path path.line(Point to)
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.line() method">
```js
({ Point, points, Path, paths, part }) => {
points.from = new Point(10, 10)
points.to = new Point(90, 10)
paths.line = new Path()
.move(points.from)
.line(points.to)
.setText("Path.line()", "text-sm center fill-note")
return part
}
```
</Example>

View file

@ -0,0 +1,50 @@
---
title: Path.move()
---
The `Path.move()` method moves to a given point without drawing a line.
## Signature
```js
Path path.move(Point to)
```
## Example
<Example caption="Example of the Path.move() method">
```js
({ Point, points, Path, paths, part }) => {
points.to = new Point(50, 20)
.setText("Path.move()", "text-xs fill-note center")
paths.noline = new Path().move(points.to)
// Prevents clipping
paths.diag = new Path()
.move(new Point(40,19))
.move(new Point(70,21))
return part
}
```
</Example>
## Notes
When drawing a path, **you must always start with a `move()` call**,
followed by your `line()` and/or `curve()` calls
and an optional `close()` call.
These calls are chainable, making your code easier to read:
```js
paths.example = new Path()
.move(points.a)
.curve(points.b, points.c, points.d)
.line(points.e)
.close()
```

View file

@ -0,0 +1,48 @@
---
title: Path.noop()
---
The `Path.noop()` method adds a placeholder path operation.
A `noop` operation does nothing, but is intended to be replaced later
with [`Path.insop()`](/reference/api/path/insop).
## Signature
```js
Path path.noop(string id)
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.noop() method">
```js
({ Point, points, Path, paths, part }) => {
points.left = new Point(10,10)
points.dartLeft = new Point(40, 10)
points.dartTip = new Point(50, 50)
points.dartRight = new Point(60, 10)
points.right = new Point(90, 10)
paths.withoutDart = new Path()
.move(points.left)
.line(points.dartLeft)
.noop('dart')
.line(points.right)
paths.withDart = paths.withoutDart
.clone()
.insop(
'dart',
new Path()
.line(points.dartTip)
.line(points.dartRight)
)
.attr('style', 'stroke-width: 2px; stroke-opacity: 0.5; stroke: orange;')
return part
}
```
</Example>

View file

@ -0,0 +1,52 @@
---
title: Path.offset()
---
The `Path.offset()` method returns a new Path that is offset by distance from
the original path.
## Signature
```js
Path path.offset(float distance)
```
## Example
<Example caption="Example of the Path.offset() method">
```js
({ Point, points, Path, paths, part }) => {
points.A = new Point(45, 60)
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
paths.example = new Path()
.move(points.A)
.line(points.B)
.curve(points.BCp2, points.CCp1, points.C)
.line(points.A)
.close()
paths.offset = paths.example
.offset(-10)
.attr("class", "interfacing")
paths.lineOffset = new Path()
.move(points.A)
.line(points.B)
.offset(-5)
.attr("class", "various")
paths.curveOffset = new Path()
.move(points.B)
.curve(points.BCp2, points.CCp1, points.C)
.offset(-5)
.attr("class", "canvas")
return part
}
```
</Example>

View file

@ -0,0 +1,50 @@
---
title: Path
---
A path represents an SVG path, which are the lines and curves on our pattern.
## Signature
```js
Path new Path()
```
The Path constructor takes no arguments.
## Properties
A Path object comes with the following properties:
- `attributes` : An [Attributes](/reference/api/attributes) instance holding
the path's attributes
- `hidden` : When this is `true` the path will be hidden (excluding it from the
output). See [Path.hide()](/reference/api/path/hide),
[Path.unhide()](/reference/api/path/unhide), and
[Path.setHidden()](/reference/api/path/sethidden) for various methods that
allow setting this in a chainable way.
:::note RELATED
See [Using Attributes](/howtos/code/attributes)
for information about custom Attributes that can be used with Paths.
:::
## Example
<Example caption="Example of the Path contructor">
```js
({ Point, points, Path, paths, part }) => {
paths.example = new Path()
.move(new Point(0,0))
.line(new Point(100,0))
return part
}
```
</Example>
## Methods
A Path object exposes the following methods:
<ReadMore />

View file

@ -0,0 +1,47 @@
---
title: Path.reverse()
---
The `Path.reverse()` returns a path that is the reversed version of this path.
As in, start becomes end, and end becomes start.
## Signature
```js
Path path.reverse(bool cloneAttributes=false)
```
If you pass a truthy value to this method, it will return a deep clone of the
path, including its attributes. By default, it will return a shallow
copy, without the attributes.
## Example
<Example caption="Example of the Path.reverse() method">
```js
({ Point, points, Path, paths, part }) => {
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
paths.example = new Path()
.move(points.B)
.curve(points.BCp2, points.CCp1, points.C)
.setText("FreeSewing rocks", "text-xs fill-note center")
paths.reverse = paths.example.reverse(true)
return part
}
```
</Example>
## Notes
The reversed path is a shallow copy.
It will in other words not inherit the attributes of the original path.
If you want a deep copy, including the attributes, use `Path.reverse(true)`.

View file

@ -0,0 +1,53 @@
---
title: Path.rotate()
---
The `Path.rotate()` returns a path that is a rotated copy of this path.
This method behaves like calling [Point.rotate](/reference/api/point/rotate) on all nodes of this path.
## Signature
```js
Path path.rotate(number deg, Point rotationOrigin, cloneAttributes = false)
```
If you pass a truthy value to the cloneAttributes parameter, it will return a deep clone of the
path, including its attributes. By default, it will return a shallow
copy, without the attributes.
## Example
<Example caption="Example of the Path.rotate() method">
```js
({ Point, points, Path, Snippet, paths, snippets, part }) => {
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
points.origin = new Point(6, 34)
snippets.origin = new Snippet('notch', points.origin)
paths.example = new Path()
.move(points.B)
.curve(points.BCp2, points.CCp1, points.C)
.setText("FreeSewing rocks", "text-xs fill-note center")
paths.rotated = paths.example
.rotate(180, points.origin, true)
.attr("class", "dotted")
return part
}
```
</Example>
## Notes
The rotated path is a shallow copy.
It will in other words not inherit the attributes of the original path.
If you want a deep copy, including the attributes, set the third parameter to true:
`Path.rotate(deg, origin, true)`

View file

@ -0,0 +1,50 @@
---
title: Path.roughLength()
---
The `Path.roughLength()` method returns a (very) rough estimate of the path's length.
## Signature
```js
Number path.roughLength()
```
## Example
<Example caption="Example of the Path.attr() method">
```js
({ Point, points, Path, paths, macro, units, part }) => {
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(120, 30)
points.CCp1 = new Point(50, -30)
paths.example = new Path()
.move(points.B)
.curve(points.BCp2, points.CCp1, points.C)
macro("pd", {
path: paths.example,
d: -10,
text: `Path.roughLength() = ${units(paths.example.roughLength())}`
})
macro("pd", {
path: paths.example,
d: 10,
text: `Path.length() = ${units(paths.example.length())}`
})
return part
}
```
</Example>
## Notes
The `Path.roughLength()` is not intended to give an estimate that is accurate, but rather differentiates between paths that are a few millimeter long, or meters long.
It calculates the length without *walking the (cubic) Bézier curve* making it very fast and very inaccurate (for curves).
It is typically used to determine how much precision to apply when walking a curve.

View file

@ -0,0 +1,42 @@
---
title: Path.setClass()
---
The `Path.setClass()` method sets the CSS class(es) of the path.
## Signature
```js
Path path.setClass(string className)
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.setClass() method">
```js
({ Point, points, Path, paths, part }) => {
points.from = new Point(5, 10)
points.to = new Point(95, 10)
paths.line = new Path()
.move(points.from)
.line(points.to)
.setClass('note dashed')
return part
}
```
</Example>
## Notes
The main purpose of this method is to save your some typing,
as the two following calls yield the same result:
```js
path.attr('class', 'fabric', true)
path.setClass('fabric')
```

View file

@ -0,0 +1,33 @@
---
title: Path.setHidden()
---
The `Path.setHidden()` method either hides or unhides the path depending on the
value you pass it.
## Signature
```js
Path path.setHidden(bool hidden = false)
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.setHidden() method">
```js
({ Point, points, Path, paths, part }) => {
points.top = new Point(50, 0)
points.left = new Point (20,50)
points.right = new Point (80,50)
paths.a = new Path().move(points.top).line(points.right).setText('a')
paths.b = new Path().move(points.right).line(points.left).setText('b').setHidden(true)
paths.c = new Path().move(points.left).line(points.top).setText('c')
return part
}
```
</Example>

View file

@ -0,0 +1,47 @@
---
title: Path.setText()
---
The `Path.setText()` method sets text on the path.
## Signature
```js
Path path.setText(string text, string className = '')
```
The second argument will optionally be used to set the CSS class for the text.
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.setText() method">
```js
({ Point, points, Path, paths, part }) => {
points.from = new Point(5, 10)
points.to = new Point(95, 10)
paths.line = new Path()
.move(points.from)
.line(points.to)
.setText('FreeSewing rocks')
return part
}
```
</Example>
## Notes
The main purpose of this method is to save your some typing,
as the two following calls yield the same result:
```js
path.attr('data-text', 'Hello')
path.setText('Hello')
```
The difference with [Path.addText()](/reference/api/path/addtext) is that this
method will overwrite existing text on the path, whereas `Path.addText()` will
add to the existing text.

View file

@ -0,0 +1,55 @@
---
title: Path.shiftAlong()
---
The `Path.shiftAlong()` method returns a point that lies at distance travelled
along the path.
## Signature
```js
Point path.shiftAlong(float distance, int stepsPerMm = 10)
```
The second parameter controls the precision by which the path will be _walked_.
By default, we'll divide it into 10 steps per mm.
If you don't need that precision, you can pass a lower number.
If you need more precision, you can pass a higher number.
For most cases, the default will be fine.
## Example
<Example caption="Example of the Path.shiftAlong() method">
```js
({ Point, points, Path, paths, Snippet, snippets, part }) => {
points.A = new Point(45, 60)
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
paths.example = new Path()
.move(points.A)
.line(points.B)
.curve(points.BCp2, points.CCp1, points.C)
points.x1 = paths.example
.shiftAlong(20)
.attr("data-text", "2 cm")
.attr("data-text-class", "center fill-note")
.attr("data-text-lineheight", 6)
points.x2 = paths.example
.shiftAlong(90)
.attr("data-text", "9 cm")
.attr("data-text-class", "center fill-note")
.attr("data-text-lineheight", 6)
snippets.x1 = new Snippet("notch", points.x1)
snippets.x2 = new Snippet("notch", points.x2)
return part
}
```
</Example>

View file

@ -0,0 +1,51 @@
---
title: Path.shiftFractionAlong()
---
The `Path.shiftFractionAlong()` returns a point that lies at fraction of the
length of the path travelled along the path.
## Signature
```js
Point path.shiftFractionAlong(float fraction, int stepsPerMm = 25)
```
The second parameter controls the precision by which the path will be _walked_.
By default, we'll divide it into 10 steps per mm.
If you don't need that precision, you can pass a lower number.
If you need more precision, you can pass a higher number.
For most cases, the default will be fine.
## Example
<Example caption="Example of the Path.shiftFractionAlong() method">
```js
({ Point, points, Path, paths, Snippet, snippets, part }) => {
points.A = new Point(45, 60)
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
paths.example = new Path()
.move(points.A)
.line(points.B)
.curve(points.BCp2, points.CCp1, points.C)
points.x1 = paths.example
.shiftFractionAlong(0.2)
.setText("0.2", "center")
points.x2 = paths.example
.shiftFractionAlong(0.9)
.setText("0.9", "center")
snippets.xl = new Snippet("notch", points.x1)
snippets.x2 = new Snippet("notch", points.x2)
return part
}
```
</Example>

View file

@ -0,0 +1,40 @@
---
title: Path.smurve()
---
The `Path.smurve()` method draws a smooth curve from the current point via a control point to an endpoint.
A smooth curve means it will use the reflection of the end control point of the previous curve.
## Signature
```js
Path path.smurve(Point cp2, Point end)
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.smurve() method">
```js
({ Point, points, Path, paths, part }) => {
points.aFrom = new Point(10, 10)
points.aCp1 = new Point(40, 40)
points.aCp2 = new Point(70, -20)
points.aTo = new Point(100, 10)
points.bCp2 = new Point(50,50)
points.bTo = new Point(10,50)
paths.smurve = new Path()
.move(points.aFrom)
.curve(points.aCp1, points.aCp2,points.aTo)
.smurve(points.bCp2, points.bTo)
.reverse() // Puts text at the end
.setText('Path.smurve()')
return part
}
```
</Example>

View file

@ -0,0 +1,41 @@
---
title: Path.smurve_()
---
The `Path.smurve_()` method draws a smooth curve from the current point an endpoint.
In addition, the end point's control point lies on top of the end point.
A smooth curve means it will use the reflection of the end control point of the previous curve.
## Signature
```js
Path path.smurve_(Point cp2, Point end)
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.smurve_() method">
```js
({ Point, points, Path, paths, part }) => {
points.aFrom = new Point(10, 10)
points.aCp1 = new Point(40, 40)
points.aCp2 = new Point(70, -20)
points.aTo = new Point(100, 10)
points.bTo = new Point(10,50)
paths.smurve = new Path()
.move(points.aFrom)
.curve(points.aCp1, points.aCp2,points.aTo)
.smurve_(points.bTo)
.reverse() // Puts text at the end
.setText('Path.smurve()')
return part
}
```
</Example>

View file

@ -0,0 +1,69 @@
---
title: Path.split()
---
The `Path.split()` method splits a path in two halves, on a point along that
path that you pass it.
## Signature
```js
array path.split(Point splitPoint)
```
## Example
<Example caption="Example of the Path.split() method">
```js
({ Point, points, Path, paths, snippets, Snippet, part }) => {
points.A = new Point(45, 60)
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
points.D = new Point(50, 130)
points.DCp1 = new Point(150, 30)
paths.demo = new Path()
.move(points.D)
.curve(points.DCp1, points.DCp1, points.C)
.curve(points.CCp1, points.BCp2, points.B)
.line(points.A)
points.split = paths.demo.shiftFractionAlong(0.75)
snippets.split = new Snippet("notch", points.split)
let halves = paths.demo.split(points.split)
for (let i in halves) {
paths[i] = halves[i]
.attr("style", "stroke-width: 3; stroke-opacity: 0.5;")
.attr("style", `stroke: hsl(${i * 70}, 100%, 50%)`)
}
return part
}
```
</Example>
## Notes
### The returned array will hold null for edge cases
Typically, the returned array will hold a `Path` object for each half.
But in some cases, one of the array entries can hold `null` if the split failed to find a path.
For example because you are splitting a `Path` on its start or end point.
```mjs
// Return value for a normal case
[Path, Path]
// Return value when calling Path.split() on/near the path's start point
[null, Path]
// Return value when calling Path.split() on/near the path's end point
[Path, null]
```
### This method will snap the split point to start or end points
This method will also _snap_ to the start or end point if you are splitting a path
(very) close to it, as it checks with [`Point.sitsRoughlyOn()`](/reference/api/point/sitsroughlyon).

View file

@ -0,0 +1,37 @@
---
title: Path.start()
---
The `Path.start()` method returns the Point object at the start of the path.
## Signature
```js
Point path.start()
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.start() method">
```js
({ Point, points, Path, paths, snippets, Snippet, part }) => {
points.A = new Point(45, 60)
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
paths.demo = new Path()
.move(points.A)
.line(points.B)
.curve(points.BCp2, points.CCp1, points.C)
snippets.end = new Snippet("notch", paths.demo.start())
return part
}
```
</Example>

View file

@ -0,0 +1,50 @@
---
title: Path.translate()
---
The `Path.translate()` method returns a path with
[a translate transform](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#Translate)
applied.
## Example
```js
Path path.translate(float deltaX, float deltaY)
```
## Example
<Example caption="Example of the Path.translate() method">
```js
({ Point, points, Path, paths, part, macro }) => {
points.A = new Point(45, 60)
points.B = new Point(10, 30)
points.BCp2 = new Point(40, 20)
points.C = new Point(90, 30)
points.CCp1 = new Point(50, -30)
paths.A = new Path()
.move(points.A)
.line(points.B)
.curve(points.BCp2, points.CCp1, points.C)
paths.B = paths.A.translate(60, 30)
points.step1 = points.B.shift(0, 60)
points.step2 = points.step1.shift(-90, 30)
macro("ld", {
from: points.B,
to: points.step1,
noStartMarker: true
})
macro("ld", {
from: points.step1,
to: points.step2,
noStartMarker: true
})
return part
}
```
</Example>

View file

@ -0,0 +1,74 @@
---
title: Path.trim()
---
The `Path.trim()` method Returns a new Path that is this path with overlapping
parts removed.
```js
Path path.trim()
```
<Example caption="Example of the Path.trim() method">
```js
({ Point, points, Path, paths, part }) => {
points.center = new Point(0, 0)
points.base = new Point(0, 10)
points.tip = new Point(0, 50)
points.tipCpRight = new Point(30, 50)
points.tipCpLeft = new Point(-30, 50)
paths.example = new Path().move(points.base)
for (let i = 0; i < 4; i++) {
points["base" + i] = points.base.rotate(60 * i, points.center)
points["tip" + i] = points.tip.rotate(60 * i, points.center)
points["tipCpRight" + i] = points.tipCpRight.rotate(60 * i, points.center)
points["tipCpLeft" + i] = points.tipCpLeft.rotate(60 * i, points.center)
if (i < 2) {
paths.example
.line(points["base" + i])
.curve(points["base" + i], points["tipCpLeft" + i], points["tip" + i])
.curve(
points["tipCpRight" + i],
points["base" + i],
points["base" + i]
)
} else {
paths.example
.line(points["base" + i])
.line(points["tip" + i])
.line(points["tipCpRight" + i])
.line(points["base" + i])
}
}
paths.offset = paths.example
.offset(10)
.setClass("lining dotted stroke-sm")
paths.trimmed = paths.offset
.trim()
.setClass("various stroke-xl")
.attr("style", "stroke-opacity: 0.5")
return part
}
```
</Example>
## Notes
This method is typically used when [Path.offset()](/reference/api/path/offset) caused some overlap.
However, use this sparsely or performance will suffer.
This method is recursive and complex, and the performance penalty for using
it on a long/complex path will be significant.
To limit the impact of path.trim(), follow this approach:
- construct a minimal path that contains the overlap
- trim it
- now join it to the rest of your path

View file

@ -0,0 +1,34 @@
---
title: Path.unhide()
---
The `Path.unhide()` method unhides the path so it appears in the output. By
default, paths are not hidden. So you should only call this on path previously
hidden via `Path.hide()`.
## Signature
```js
Path path.unhide()
```
:::tipThis method is chainable as it returns the `Path` object:::
## Example
<Example caption="Example of the Path.unhide() method">
```js
({ Point, points, Path, paths, part }) => {
points.top = new Point(50, 0)
points.left = new Point (20,50)
points.right = new Point (80,50)
paths.a = new Path().move(points.top).line(points.right).setText('a')
paths.b = new Path().move(points.right).line(points.left).setText('b').hide().unhide()
paths.c = new Path().move(points.left).line(points.top).setText('c')
return part
}
```
</Example>

View file

@ -0,0 +1,48 @@
---
title: Pattern.addPart()
---
The `Pattern.addPart()` method allows you to add a part to a pattern.
It has the same effect as passing a part to the Design constructor.
:::noteThis method is chainable as it returns the Pattern object:::
## Pattern.addPart() signature
```js
Pattern pattern.addPart(object part)
```
## Pattern.addPart() example
```js
import { Aaron } from "@freesewing/aaron"
const extra = {
name: 'aaron.extra',
draft: ({ points, Point, paths, Path, part }) => {
points.msg = new Point(50,15)
.attr('data-text', "I am an extra part")
paths.box = new Path()
.move(new Point(0,0))
.line(new Point(0,30))
.line(new Point(100,30))
.line(new Point(100,0))
.close(new Point(100,0))
.addClass('note')
return part
}
}
// Load some public test measurements from the FreeSewing backend
const measurements = (
await (
await fetch("https://backend3.freesewing.org/curated-sets/1.json")
).json()
).measurements
const pattern = new Aaron({ measurements }).addPart(extra)
const svg = pattern.draft().render()
```

View file

@ -0,0 +1,33 @@
---
title: Pattern.draft()
---
A pattern's `draft()` method will draft the different pattern parts
making sure to do so in the right order, handle dependencies, resolve
options to their absolute values and a number of other housekeeping things
that are required for the pattern to be drafted.
:::noteThis method is chainable as it returns the Pattern object:::
## Pattern.draft() signature
```js
Pattern pattern.draft()
```
## Pattern.draft() example
```js
import { Aaron } from "@freesewing/aaron"
// Load some public test measurements from the FreeSewing backend
const measurements = (
await (
await fetch("https://backend3.freesewing.org/curated-sets/1.json")
).json()
).measurements
const pattern = new Aaron({ measurements })
const svg = pattern.draft().render()
```

View file

@ -0,0 +1,33 @@
---
title: Pattern.draftPartForSet()
---
A pattern's `draftPartForSet()` method will draft a part using a
given set of settings.
:::noteThis method is chainable as it returns the Pattern object:::
## Pattern.draftPartForSet() signature
```js
Pattern pattern.draftPartForSet(part, set)
```
## Pattern.draftPartForSet() example
```js
import { Aaron } from "@freesewing/aaron"
// Load a public test settings set from the FreeSewing backend
const set = (
await (
await fetch("https://backend3.freesewing.org/curated-sets/1.json")
).json()
)
const pattern = new Aaron()
for (const part in pattern.parts) {
const svg = pattern.draftPartForSet(part, set).render()
}
```

Some files were not shown because too many files have changed in this diff Show more