1
0
Fork 0

feat(plugin-ringsector): New plugin to draft a ring sector

This plugin provides the `ringsector` and `rmringsector` plugins.

The Sandy design was adapted to use the `ringsector` plugin instead of
its own method for doing this (that method was the base for this
plugin).

The plugin is also added to the tabbed-example component.

This closes #3865
This commit is contained in:
Joost De Cock 2023-10-16 16:19:27 +02:00
parent 5d03d146f7
commit 075cb34184
17 changed files with 727 additions and 166 deletions

View file

@ -87,6 +87,7 @@
#### Changed
- Rephrased flag message when expand is off to avoid confusion about included seam allowance. Fixes
- The skirt and curved waistband are now constructed with the ringsector macro
### shin
@ -124,6 +125,12 @@
- First release of the plugin providing the default packing implementation
### plugin-ringsector
#### Added
- First release of the plugin facilitating drafting a ring sector
### core
#### Added

View file

@ -4,6 +4,8 @@ Unreleased:
- Allow plugins to provide their own packing implementation
plugin-bin-pack:
- First release of the plugin providing the default packing implementation
plugin-ringsector:
- First release of the plugin facilitating drafting a ring sector
Changed:
aaron:
@ -33,6 +35,7 @@ Unreleased:
- Rephrased flag message when expand is off to avoid confusion about included seam allowance. Fixes #5057
sandy:
- Rephrased flag message when expand is off to avoid confusion about included seam allowance. Fixes #5057
- The skirt and curved waistband are now constructed with the ringsector macro
shin:
- Rephrased flag message when expand is off to avoid confusion about included seam allowance. Fixes #5057
sven:

View file

@ -8,6 +8,7 @@
"plugin-i18n": "A FreeSewing plugin for pattern translation",
"plugin-measurements": "A FreeSewing plugin that adds additional measurements that can be calculated from existing ones",
"plugin-mirror": "A FreeSewing plugin to mirror points or paths",
"plugin-ringsector": "A FreeSewing plugin to draft a ring sector",
"plugin-round": "A FreeSewing plugin to round corners",
"plugin-sprinkle": "A FreeSewing plugin to bulk-add snippets to your pattern",
"plugin-svgattr": "A FreeSewing plugin to set SVG attributes",

View file

@ -1,5 +1,3 @@
import { draftRingSector } from './shared.mjs'
export function draftCurvedWaistband({
utils,
store,
@ -32,18 +30,35 @@ export function draftCurvedWaistband({
store.get('waistbandOverlap') / (rad + absoluteOptions.waistbandWidth)
)
// The curved waistband is shown with no rotation
const rot = 0
// Call draftRingSector to draft the part
paths.seam = draftRingSector(
part,
rot,
an + anExtra,
rad,
rad + absoluteOptions.waistbandWidth
).attr('class', 'fabric')
// Call the RingSector macro to draft the waistband
macro('ringsector', {
angle: an + anExtra,
insideRadius: rad,
outsideRadius: rad + absoluteOptions.waistbandWidth,
})
const storeRoot = [
'parts',
part.name,
'macros',
'@freesewing/plugin-ringsector',
'ids',
'ringsector',
]
const pathId = store.get([...storeRoot, 'paths', 'path'])
paths.seam = paths[pathId].clone().addClass('fabric')
paths[pathId].hide()
if (sa) paths.sa = paths.seam.offset(sa * -1).attr('class', 'fabric sa')
/*
* Macros ensure they can be used more than once in a part, and will generate unique (and complex)
* point names. Since we're only calling the macro once here, we will simplify these names
*/
for (const [shortId, uid] of Object.entries(store.get([...storeRoot, 'points']))) {
points[shortId] = points[uid].copy()
// Some points are rotated, we need those too
if (points[uid + 'Rotated']) points[shortId + 'Rotated'] = points[uid + 'Rotated'].copy()
}
if (sa) paths.sa = paths.seam.offset(sa * -1).addClass('fabric sa')
/*
* Annotations
@ -55,7 +70,7 @@ export function draftCurvedWaistband({
points.gridAnchor = points.in1.clone()
// Title
points.title = points.in1Rotated.shiftFractionTowards(points.ex1Rotated, 0.5).shift(0, 25)
points.title = points.ex2Flipped.shiftFractionTowards(points.ex2, 0.5)
macro('title', {
at: points.title,
nr: 2,
@ -64,42 +79,33 @@ export function draftCurvedWaistband({
})
// Grainline
points.grainlineFrom = utils.curveIntersectsY(
points.ex2FlippedRotated,
points.ex2CFlippedRotated,
points.ex1CFlippedRotated,
points.ex1Rotated,
points.title.y
)
points.grainlineTo = points.grainlineFrom.flipX()
macro('grainline', {
from: points.grainlineFrom,
to: points.grainlineTo,
from: points.ex2Flipped,
to: points.ex2,
})
// Buttons / Notches
if (store.get('waistbandOverlap') >= options.minimumOverlap) {
points.pivot = points.in2Rotated.shiftFractionTowards(points.ex2Rotated, 0.5)
points.pivot = points.in2.shiftFractionTowards(points.ex2, 0.5)
points.button = points.pivot
.shiftTowards(points.ex2Rotated, store.get('waistbandOverlap') / 2)
.shiftTowards(points.ex2, store.get('waistbandOverlap') / 2)
.rotate(-90, points.pivot)
points.buttonhole = points.button.flipX()
snippets.button = new Snippet('button', points.button)
snippets.buttonhole = new Snippet('buttonhole', points.buttonhole).attr(
'data-rotate',
-1 * points.ex2FlippedRotated.angle(points.in2FlippedRotated)
snippets.buttonhole = new Snippet('buttonhole', points.buttonhole).rotate(
points.in2.angle(points.ex2)
)
points.centerNotch = new Path()
.move(points.ex1Rotated)
.curve(points.ex1CFlippedRotated, points.ex2CFlippedRotated, points.ex2FlippedRotated)
.curve(points.ex1cFlippedRotated, points.ex2cFlippedRotated, points.ex2FlippedRotated)
.shiftAlong(store.get('waistbandOverlap') / 2)
points.buttonNotch = new Path()
.move(points.ex2Rotated)
.curve(points.ex2CRotated, points.ex1CRotated, points.ex1Rotated)
.curve(points.ex2cRotated, points.ex1cRotated, points.ex1Rotated)
.shiftAlong(store.get('waistbandOverlap'))
macro('sprinkle', {
snippet: 'notch',
on: ['centerNotch', 'buttonNotch', 'ex2FlippedRotated'],
on: ['centerNotch', 'buttonNotch', 'ex2Flipped'],
})
}
@ -107,25 +113,25 @@ export function draftCurvedWaistband({
macro('hd', {
id: 'wTop',
from: points.in2FlippedRotated,
to: points.in2Rotated,
y: points.in2Rotated.y - sa - 15,
to: points.in2,
y: points.in2.y - sa - 15,
})
macro('hd', {
from: points.ex2FlippedRotated,
id: 'wFull',
to: points.ex2Rotated,
y: points.in2Rotated.y - sa - 30,
to: points.ex2,
y: points.in2.y - sa - 30,
})
macro('vd', {
id: 'hFull',
from: points.ex1Rotated,
to: points.in2Rotated,
x: points.in2Rotated.x + sa + 30,
from: points.ex1,
to: points.in2,
x: points.in2.x + sa + 40,
})
macro('ld', {
id: 'lWidth',
from: points.ex2Rotated,
to: points.in2Rotated,
from: points.ex2,
to: points.in2,
d: -1 * sa - 15,
})

View file

@ -1,105 +0,0 @@
export const draftRingSector = (part, rot, an, radIn, radEx, rotate = false) => {
const { utils, Point, points, Path } = part.shorthand()
const roundExtended = (radius, angle = 90) => {
const arg = utils.deg2rad(angle / 2)
return (radius * 4 * (1 - Math.cos(arg))) / Math.sin(arg) / 3
}
/**
* Calculates the distance of the control point for the internal
* and external arcs using bezierCircleExtended
*/
const distIn = roundExtended(radIn, an / 2)
const distEx = roundExtended(radEx, an / 2)
// The centre of the circles
points.center = new Point(0, 0)
/**
* This function is expected to draft ring sectors for
* angles up to 180%. Since roundExtended works
* best for angles until 90º, we generate the ring
* sector using the half angle and then duplicate it
*/
/**
* The first point of the internal arc, situated at
* a radIn distance below the centre
*/
points.in1 = points.center.shift(-90, radIn)
/**
* The control point for 'in1'. It's situated at a
* distance $distIn calculated with bezierCircleExtended
* and the line between it and 'in1' is perpendicular to
* the line between 'in1' and the centre, so it's
* shifted in the direction 0º
*/
points.in1C = points.in1.shift(0, distIn)
/**
* The second point of the internal arc, situated at
* a $radIn distance of the centre in the direction
* $an/2 - 90º
*/
points.in2 = points.center.shift(an / 2 - 90, radIn)
/**
* The control point for 'in2'. It's situated at a
* distance $distIn calculated with bezierCircleExtended
* and the line between it and 'in2' is perpendicular to
* the line between 'in2' and the centre, so it's
* shifted in the direction $an/2 + 180º
*/
points.in2C = points.in2.shift(an / 2 + 180, distIn)
/**
* The points for the external arc are generated in the
* same way, using $radEx and $distEx instead
*/
points.ex1 = points.center.shift(-90, radEx)
points.ex1C = points.ex1.shift(0, distEx)
points.ex2 = points.center.shift(an / 2 - 90, radEx)
points.ex2C = points.ex2.shift(an / 2 + 180, distEx)
// Flip all the points to generate the full ring sector
for (const id of ['in2', 'in2C', 'in1C', 'ex1C', 'ex2C', 'ex2'])
points[id + 'Flipped'] = points[id].flipX()
// Rotate all the points an angle rot
for (const id of [
'in1',
'in1C',
'in2',
'in2C',
'ex1',
'ex1C',
'ex2',
'ex2C',
'in2Flipped',
'in2CFlipped',
'in1CFlipped',
'ex1CFlipped',
'ex2CFlipped',
'ex2Flipped',
])
points[id + 'Rotated'] = points[id].rotate(rot, points.center)
if (rotate) {
// Rotate all points so the line from in1Rotated to ex1Rotated is vertical
const deg = 270 - points.in2Flipped.angle(points.ex2Flipped)
for (const id in points) {
points[id] = points[id].rotate(deg, points.in2Flipped)
}
}
// Return the path of the full ring sector
return new Path()
.move(points.in2Flipped)
.curve(points.in2CFlipped, points.in1CFlipped, points.in1)
.curve(points.in1C, points.in2C, points.in2)
.line(points.ex2)
.curve(points.ex2C, points.ex1C, points.ex1)
.curve(points.ex1CFlipped, points.ex2CFlipped, points.ex2Flipped)
.close()
}

View file

@ -1,6 +1,6 @@
import { draftRingSector } from './shared.mjs'
import { pctBasedOn } from '@freesewing/core'
import { elastics } from '@freesewing/snapseries'
import { ringsectorPlugin } from '@freesewing/plugin-ringsector'
function sandySkirt({
utils,
@ -85,29 +85,50 @@ function sandySkirt({
const radiusHem =
radiusWaist + store.get('fullLength') * options.lengthBonus - absoluteOptions.waistbandWidth
/**
* The ring sector will be rotated an angle an/2 so we
* display the part with one edge of the skirt vertical
*/
const rot = an / 2
// Call the RingSector macro to draft the part
macro('ringsector', {
angle: an,
insideRadius: radiusWaist,
outsideRadius: radiusHem,
rotate: true,
})
const storeRoot = [
'parts',
part.name,
'macros',
'@freesewing/plugin-ringsector',
'ids',
'ringsector',
]
const pathId = store.get([...storeRoot, 'paths', 'path'])
paths.seam = paths[pathId].clone().addClass('fabric')
paths[pathId].hide()
// Call draftRingSector to draft the part
paths.seam = draftRingSector(part, rot, an, radiusWaist, radiusHem, true).attr('class', 'fabric')
/*
* Macros ensure they can be used more than once in a part, and will generate unique (and complex)
* point names. Since we're only calling the macro once here, we will simplify these names
*/
for (const [shortId, uid] of Object.entries(store.get([...storeRoot, 'points']))) {
points[shortId] = points[uid].copy()
// Some points are rotated, we need those too
if (points[uid + 'Rotated']) points[shortId + 'Rotated'] = points[uid + 'Rotated'].copy()
}
// Anchor samples to the centre of the waist
points.gridAnchor = points.in2Flipped.clone()
if (sa) {
paths.hemBase = new Path()
.move(points.ex1Rotated)
.curve(points.ex1CFlippedRotated, points.ex2CFlippedRotated, points.ex2FlippedRotated)
.curve(points.ex1CFlipped, points.ex2CFlipped, points.ex2Flipped)
.move(points.ex2)
.curve(points.ex2c, points.ex1c, points.ex1)
.curve(points.ex1cFlipped, points.ex2cFlipped, points.ex2Flipped)
.offset(store.get('fullLength') * options.lengthBonus * options.hemWidth * -1)
paths.saBase = new Path()
.move(points.in2Flipped)
.curve(points.in2CFlipped, points.in1CFlipped, points.in2FlippedRotated)
.curve(points.in2CFlippedRotated, points.in1CFlippedRotated, points.in1Rotated)
if (!options.seamlessFullCircle) paths.saBase = paths.saBase.line(points.ex1Rotated)
.curve(points.in2cFlipped, points.in1cFlipped, points.in1)
.curve(points.in1c, points.in2c, points.in2)
if (!options.seamlessFullCircle) paths.saBase = paths.saBase.line(points.ex2)
paths.saBase = paths.saBase.offset(sa * -1)
paths.hemBase.hide()
@ -118,8 +139,8 @@ function sandySkirt({
.move(points.in2Flipped)
.line(paths.saBase.start())
.join(paths.saBase)
.line(points.in1Rotated)
.move(points.ex1Rotated)
.line(points.in2)
.move(points.ex2)
.line(paths.hemBase.start())
.join(paths.hemBase)
.line(points.ex2Flipped)
@ -150,9 +171,10 @@ function sandySkirt({
})
if (options.seamlessFullCircle) {
macro('cutonfold', {
from: points.ex1Rotated,
to: points.in1Rotated,
from: points.ex2,
to: points.in2,
id: 'double',
reverse: true,
})
}
@ -171,7 +193,7 @@ function sandySkirt({
// Notches
macro('sprinkle', {
snippet: 'notch',
on: ['in1Rotated', 'gridAnchor'],
on: ['in2', 'gridAnchor'],
})
snippets.center = new Snippet('bnotch', points.center)
@ -244,5 +266,6 @@ export const skirt = {
menu: 'fit',
},
},
plugins: ringsectorPlugin,
draft: sandySkirt,
}

View file

@ -0,0 +1,92 @@
---
title: ringsector
---
The `ringsector` macro drafts a ring sector, which is like a part of a donut
with an inside and outside radius. It is particularly useful for drafting
curved waistbands, circle skirts, and so on.
It is provided by the [ringsector plugin](/reference/plugins/ringsector).
<Note>
##### Not a core-plugins macro
The `ringsector` macro is not provided by the [core-plugins](/reference/plugins/core),
so you need to load the [ringsector plugin](/reference/plugins/ringsector) explicitly
if you want to use it.
</Note>
## Signature
```js
macro('ringsector', {
Point center = new Point(0,0),
Number angle,
Number insideRadius,
Number outsideRadius,
Boolean rotate = false,
String id='ringsector',
})
```
## Example
<Example caption="Example of a ring sector drafted by this macro">
```js
({ Point, macro, Path, paths, part }) => {
macro('ringsector', {
angle: 60,
insideRadius: 30,
outsideRadius: 45,
})
return part
}
```
</Example>
## Configuration
| Property | Default | Type | Description |
|---------------:|-------------------|------------|-------------|
| `center` | `new Point(0,0)` | [Point][1] | The center point of the ring sector |
| `angle` | | Number | The angle the ring sector should cover |
| `insideRadius` | | Number | The inside radius of the ring sector |
| `outsideRadius` | | Number | The outside radius of the ring sector |
| `rotate` | `false` | Boolean | Whether or not to rotate the ringsector so one of its sides is vertical (see [example below](#example-when-rotatetrue)) |
| `id` | `ringsector` | String | The id to use in auto-generate macro points and paths |
[1]: /reference/api/point
## Notes
### Nodes generated by this macro
This macro will add points and a single path to your part.
Their IDs will be saved in store under:
`parts.{part.name}.macros.@freesewing/plugin-ringsector.ids.{id}`
### Removing a ring sector
If you inherit a part with a ring sector drafted by this macro and you'd like to remove it,
you can do so with [the rmringsector macro](/reference/macros/rmringsector).
### Example when rotate=true
<Example caption="Example of a ring sector drafted by this macro when rotate is truthy">
```js
({ Point, macro, Path, paths, part }) => {
macro('ringsector', {
angle: 60,
insideRadius: 30,
outsideRadius: 45,
rotate: true,
})
return part
}
```
</Example>

View file

@ -0,0 +1,56 @@
---
title: rmringsector
---
The `rmringsector` macro removes the nodes added by [the ringsector macro](/reference/macros/ringsector).
It is the recommended way to remove (the effects of) a `ringsector` macro.
It is provided by the [ringsector plugin](/reference/plugins/ringsector).
<Note>
##### Not a core-plugins macro
The `rmringsector` macro is not provided by the [core-plugins](/reference/plugins/core),
so you need to load the [ringsector plugin](/reference/plugins/ringsector) explicitly
if you want to use it.
</Note>
## Signature
```js
macro('rmringsector', String id = 'ringsector')
```
## Example
<Example caption="Example of a ring sector removed by this macro">
```js
({ Point, macro, Path, paths, part }) => {
macro('ringsector', {
angle: 60,
insideRadius: 30,
outsideRadius: 45,
})
macro('rmringsector')
return part
}
```
</Example>
## Configuration
| Property | Default | Type | Description |
|---------:|--------------|--------|-------------|
| `id` | `ringsector` | String | The id of the ringsector macro to remove |
## Notes
### Nodes removed by this macro
This macro will remove points and a single path from your part.
Their IDs have been saved in store under:
`parts.{part.name}.macros.@freesewing/plugin-ringsector.ids.{id}`
by the [the ringsector macro](/reference/macros/ringsector).

View file

@ -0,0 +1,30 @@
---
title: plugin-ringsector
---
Published as [@freesewing/plugin-ringsector][1], this plugin facilitates
drafting a ring sector (like a part of a donut).
It is particularly usefor for drafting curved waistband, circle skirts, and so on.
## Provides
- [The ringsector macro](/reference/macros/ringsector)
- [The rmringsector macro](/reference/macros/rmringsector)
## Installation
```sh
npm install @freesewing/plugin-ringsector
```
## Usage
You should [add it as a part plugin](/reference/api/part/config/plugins).
Refer to the documentation of [the provided macros](#provides) for details on how to use them.
To import the plugin for use:
```js
import { plugin as ringsectorPlugin } from '@freesewing/plugin-ringsector'
```
[1]: https://www.npmjs.com/package/@freesewing/plugin-ringsector

View file

@ -0,0 +1,17 @@
# Change log for: @freesewing/plugin-ringsector
## 3.0.0 (2022-09-30)
### Changed
- All FreeSewing pacakges are now ESM only.
- All FreeSewing pacakges now use named exports.
- Dropped support for NodeJS 14. NodeJS 18 (LTS/hydrogen) or more recent is now required.
This is the **initial release**, and the start of this change log.
> Prior to version 2, FreeSewing was not a JavaScript project.
> As such, that history is out of scope for this change log.

View file

@ -0,0 +1,143 @@
![FreeSewing](https://static.freesewing.org/banner.png)
<p align='center'><a
href="https://www.npmjs.com/package/@freesewing/plugin-ringsector"
title="@freesewing/plugin-ringsector on NPM"
><img src="https://img.shields.io/npm/v/@freesewing/plugin-ringsector.svg"
alt="@freesewing/plugin-ringsector on NPM"/>
</a><a
href="https://opensource.org/licenses/MIT"
title="License: MIT"
><img src="https://img.shields.io/npm/l/@freesewing/plugin-ringsector.svg?label=License"
alt="License: MIT"/>
</a><a
href="https://deepscan.io/dashboard#view=project&tid=2114&pid=2993&bid=23256"
title="Code quality on DeepScan"
><img src="https://deepscan.io/api/teams/2114/projects/2993/branches/23256/badge/grade.svg"
alt="Code quality on DeepScan"/>
</a><a
href="https://github.com/freesewing/freesewing/issues?q=is%3Aissue+is%3Aopen+label%3Apkg%3Aplugin-ringsector"
title="Open issues tagged pkg:plugin-ringsector"
><img src="https://img.shields.io/github/issues/freesewing/freesewing/pkg:plugin-ringsector.svg?label=Issues"
alt="Open issues tagged pkg:plugin-ringsector"/>
</a><a
href="#contributors-"
title="All Contributors"
><img src="https://img.shields.io/badge/all_contributors-111-pink.svg"
alt="All Contributors"/>
</a></p><p align='center'><a
href="https://twitter.com/freesewing_org"
title="Follow @freesewing_org on Twitter"
><img src="https://img.shields.io/badge/%F3%A0%80%A0-Follow%20us-blue.svg?logo=twitter&logoColor=white&logoWidth=15"
alt="Follow @freesewing_org on Twitter"/>
</a><a
href="https://chat.freesewing.org"
title="Chat with us on Discord"
><img src="https://img.shields.io/discord/698854858052075530?label=Chat%20on%20Discord"
alt="Chat with us on Discord"/>
</a><a
href="https://freesewing.org/patrons/join"
title="Become a FreeSewing Patron"
><img src="https://img.shields.io/badge/%F3%A0%80%A0-Support%20us-blueviolet.svg?logo=cash-app&logoColor=white&logoWidth=15"
alt="Become a FreeSewing Patron"/>
</a><a
href="https://instagram.com/freesewing_org"
title="Follow @freesewing_org on Twitter"
><img src="https://img.shields.io/badge/%F3%A0%80%A0-Follow%20us-E4405F.svg?logo=instagram&logoColor=white&logoWidth=15"
alt="Follow @freesewing_org on Twitter"/>
</a></p>
# @freesewing/plugin-ringsector
A FreeSewing plugin to draft a ring sector
## What am I looking at? 🤔
This repository is the FreeSewing *monorepo* holding all FreeSewing's websites, documentation, designs, plugins, and other NPM packages.
This folder holds: @freesewing/plugin-ringsector
If you're not entirely sure what to do or how to start, type this command:
```
npm run tips
```
> If you don't want to set up a dev environment, you can run it in your browser:
>
> [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/freesewing/freesewing)
>
> We recommend that you fork our repository and then
> put `gitpod.io/#<entire-url-of-your-fork` into a browser
> to start up a browser-based dev environment of your own.
## About FreeSewing 💀
Where the world of makers and developers collide, that's where you'll find FreeSewing.
If you're a maker, checkout [freesewing.org](https://freesewing.org/) where you can generate
sewing patterns adapted to your measurements.
If you're a developer, the FreeSewing documentation lives at [freesewing.dev](https://freesewing.dev/).
The FreeSewing [core library](https://freesewing.dev/reference/api/) is a *batteries-included* toolbox
for parametric design of sewing patterns. But FreeSewing also provides a range
of [plugins](https://freesewing.dev/reference/plugins/) that further extend the
functionality of the platform.
If you have NodeJS installed, you can try it right now by running:
```bash
npx create-freesewing-pattern
```
Getting started guides are available for:
- [Linux](https://freesewing.dev/tutorials/getting-started-linux/)
- [MacOS](https://freesewing.dev/tutorials/getting-started-mac/)
- [Windows](https://freesewing.dev/tutorials/getting-started-windows/)
The [pattern design tutorial](https://freesewing.dev/tutorials/pattern-design/) will
show you how to create your first parametric design.
## Support FreeSewing: Become a patron 🥰
FreeSewing is an open source project maintained by Joost De Cock and financially supported by the FreeSewing patrons.
If you feel FreeSewing is worthwhile, and you can spend a few coind without
hardship, then you should [join us and become a patron](https://freesewing.org/community/join).
## Links 👩‍💻
**Official channels**
- 💻 Makers website: [FreeSewing.org](https://freesewing.org)
- 💻 Developers website: [FreeSewing.dev](https://freesewing.dev)
- ✅ [Support](https://github.com/freesewing/freesewing/issues/new/choose),
[Issues](https://github.com/freesewing/freesewing/issues) &
[Discussions](https://github.com/freesewing/freesewing/discussions) on
[GitHub](https://github.com/freesewing/freesewing)
**Social media**
- 🐦 Twitter: [@freesewing_org](https://twitter.com/freesewing_org)
- 📷 Instagram: [@freesewing_org](https://instagram.com/freesewing_org)
**Places the FreeSewing community hangs out**
- 💬 [Discord](https://discord.freesewing.org/)
- 💬 [Facebook](https://www.facebook.com/groups/627769821272714/)
- 💬 [Reddit](https://www.reddit.com/r/freesewing/)
## License: MIT 🤓
© [Joost De Cock](https://github.com/joostdecock).
See [the license file](https://github.com/freesewing/freesewing/blob/develop/LICENSE) for details.
## Where to get help 🤯
For [Support](https://github.com/freesewing/freesewing/issues/new/choose),
please use the [Issues](https://github.com/freesewing/freesewing/issues) &
[Discussions](https://github.com/freesewing/freesewing/discussions) on
[GitHub](https://github.com/freesewing/freesewing).

View file

@ -0,0 +1,35 @@
/* This script will build the package with esbuild */
import esbuild from 'esbuild'
import pkg from './package.json' assert { type: 'json' }
// Create banner based on package info
const banner = `/**
* ${pkg.name} | v${pkg.version}
* ${pkg.description}
* (c) ${new Date().getFullYear()} ${pkg.author}
* @license ${pkg.license}
*/`
// Shared esbuild options
const options = {
banner: { js: banner },
bundle: true,
entryPoints: ['src/index.mjs'],
format: 'esm',
outfile: 'dist/index.mjs',
external: ['@freesewing'],
metafile: process.env.VERBOSE ? true : false,
minify: process.env.NO_MINIFY ? false : true,
sourcemap: true,
}
// Let esbuild generate the build
const build = async () => {
const result = await esbuild.build(options).catch(() => process.exit(1))
if (process.env.VERBOSE) {
const info = await esbuild.analyzeMetafile(result.metafile)
console.log(info)
}
}
build()

View file

@ -0,0 +1,4 @@
// This file is auto-generated | All changes you make will be overwritten.
export const name = '@freesewing/plugin-ringsector'
export const version = '3.0.0'
export const data = { name, version }

View file

@ -0,0 +1,71 @@
{
"name": "@freesewing/plugin-ringsector",
"version": "3.0.0",
"description": "A FreeSewing plugin to draft a ring sector",
"author": "Joost De Cock <joost@joost.at> (https://github.com/joostdecock)",
"homepage": "https://freesewing.org/",
"repository": "github:freesewing/freesewing",
"license": "MIT",
"bugs": {
"url": "https://github.com/freesewing/freesewing/issues"
},
"funding": {
"type": "individual",
"url": "https://freesewing.org/patrons/join"
},
"keywords": [
"freesewing",
"plugin",
"sewing pattern",
"sewing",
"design",
"parametric design",
"made to measure",
"diy",
"fashion"
],
"type": "module",
"module": "dist/index.mjs",
"exports": {
".": {
"internal": "./src/index.mjs",
"default": "./dist/index.mjs"
}
},
"scripts": {
"build": "node build.mjs",
"build:all": "yarn build",
"clean": "rimraf dist",
"mbuild": "NO_MINIFY=1 node build.mjs",
"symlink": "mkdir -p ./node_modules/@freesewing && cd ./node_modules/@freesewing && ln -s -f ../../../* . && cd -",
"test": "npx mocha tests/*.test.mjs",
"vbuild": "VERBOSE=1 node build.mjs",
"lab": "cd ../../sites/lab && yarn start",
"tips": "node ../../scripts/help.mjs",
"lint": "npx eslint 'src/**' 'tests/*.mjs'",
"prettier": "npx prettier --write 'src/*.mjs' 'tests/*.mjs'",
"testci": "NODE_OPTIONS=\"--conditions=internal\" npx mocha tests/*.test.mjs --reporter ../../tests/reporters/terse.js",
"wbuild": "node build.mjs",
"wbuild:all": "yarn wbuild"
},
"peerDependencies": {
"@freesewing/core": "3.0.0"
},
"dependencies": {},
"devDependencies": {
"mocha": "10.2.0",
"chai": "4.3.10"
},
"files": [
"dist/*",
"README.md"
],
"publishConfig": {
"access": "public",
"tag": "latest"
},
"engines": {
"node": "18",
"npm": "9"
}
}

View file

@ -0,0 +1,171 @@
import { name, version } from '../data.mjs'
/*
* Helper method to get the various IDs for a macro
*/
export const getIds = (keys, id) => {
const ids = {}
for (const key of keys) ids[key] = `__macro_ringsector_${id}_${key}`
return ids
}
/*
* Helper method to calculate the arc
*/
const roundExtended = (radius, angle = 90, utils) => {
const arg = utils.deg2rad(angle / 2)
return (radius * 4 * (1 - Math.cos(arg))) / Math.sin(arg) / 3
}
/*
* Short IDs
*/
const keys = [
'center',
'in1',
'in1c',
'in2',
'in2c',
'ex1',
'ex1c',
'ex2',
'ex2c',
'in2Flipped',
'in2cFlipped',
'in1cFlipped',
'ex1cFlipped',
'ex2cFlipped',
'ex2Flipped',
]
/*
* The plugin object itself
*/
export const plugin = {
name,
version,
macros: {
rmringsector: function (id = 'ringsector', { points, paths, store, part }) {
const storeRoot = ['parts', part.name, 'macros', '@freesewing/plugin-ringsector', 'ids', id]
for (const id of Object.values(store.get([...storeRoot, 'paths']))) delete paths[id]
for (const id of Object.values(store.get([...storeRoot, 'points']))) delete points[id]
},
ringsector: function (mc, { utils, Point, points, Path, paths, store, part }) {
const {
angle,
insideRadius,
outsideRadius,
rotate = false,
center = new Point(0, 0),
id = 'ringsector',
} = mc
/*
* Get the list of IDs
*/
const ids = getIds(keys, id)
const pathId = getIds(['path'], id).path
/**
* Calculates the distance of the control point for the internal
* and external arcs using bezierCircleExtended
*/
const distIn = roundExtended(insideRadius, angle / 2, utils)
const distEx = roundExtended(outsideRadius, angle / 2, utils)
// The centre of the circles
points[ids.center] = center.copy()
/**
* This function is expected to draft ring sectors for
* angles up to 180%. Since roundExtended works
* best for angles until 90º, we generate the ring
* sector using the half angle and then duplicate it
*/
/**
* The first point of the internal arc, situated at
* a insideRadius distance below the centre
*/
points[ids.in1] = points[ids.center].shift(-90, insideRadius)
/**
* The control point for 'in1'. It's situated at a
* distance $distIn calculated with bezierCircleExtended
* and the line between it and 'in1' is perpendicular to
* the line between 'in1' and the centre, so it's
* shifted in the direction 0º
*/
points[ids.in1c] = points[ids.in1].shift(0, distIn)
/**
* The second point of the internal arc, situated at
* a $insideRadius distance of the centre in the direction
* $angle/2 - 90º
*/
points[ids.in2] = points[ids.center].shift(angle / 2 - 90, insideRadius)
/**
* The control point for 'in2'. It's situated at a
* distance $distIn calculated with bezierCircleExtended
* and the line between it and 'in2' is perpendicular to
* the line between 'in2' and the centre, so it's
* shifted in the direction $angle/2 + 180º
*/
points[ids.in2c] = points[ids.in2].shift(angle / 2 + 180, distIn)
/**
* The points for the external arc are generated in the
* same way, using $outsideRadius and $distEx instead
*/
points[ids.ex1] = points[ids.center].shift(-90, outsideRadius)
points[ids.ex1c] = points[ids.ex1].shift(0, distEx)
points[ids.ex2] = points[ids.center].shift(angle / 2 - 90, outsideRadius)
points[ids.ex2c] = points[ids.ex2].shift(angle / 2 + 180, distEx)
// Flip all the points to generate the full ring sector
for (const id of ['in2', 'in2c', 'in1c', 'ex1c', 'ex2c', 'ex2']) {
points[ids[id + 'Flipped']] = points[ids[id]].flipX()
}
// Rotate all the points angle/2
for (const id of keys) {
points[ids[id] + 'Rotated'] = points[ids[id]].rotate(angle / 2, points[ids.center])
// Also add this to the ids so we can save them later
ids[id + 'Rotated'] = ids[id] + 'Rotated'
}
// (optionally) Rotate all points so the line from in1Rotated to ex1Rotated is vertical
if (rotate) {
const deg = 270 - points[ids.in2Flipped].angle(points[ids.ex2Flipped])
for (const id of keys) {
points[ids[id]] = points[ids[id]].rotate(deg, points[ids.in2Flipped])
// We need the rotated points too
points[ids[id] + 'Rotated'].rotate(deg, points[ids.in2Flipped])
}
}
// Return the path of the full ring sector
paths[pathId] = new Path()
.move(points[ids.in2Flipped])
.curve(points[ids.in2cFlipped], points[ids.in1cFlipped], points[ids.in1])
.curve(points[ids.in1c], points[ids.in2c], points[ids.in2])
.line(points[ids.ex2])
.curve(points[ids.ex2c], points[ids.ex1c], points[ids.ex1])
.curve(points[ids.ex1cFlipped], points[ids.ex2cFlipped], points[ids.ex2Flipped])
.close()
/*
* Store all IDs in the store so we can remove this macro with rmringsector
*/
store.set(['parts', part.name, 'macros', name, 'ids', id, 'paths'], { path: pathId })
store.set(['parts', part.name, 'macros', name, 'ids', id, 'points'], ids)
},
},
}
// More specifically named exports
export const ringsectorPlugin = plugin
export const ringSectorPlugin = plugin
export const pluginRingSector = plugin
export const pluginRingsector = plugin

View file

@ -0,0 +1,6 @@
// This file is auto-generated | Any changes you make will be overwritten.
import { plugin } from '../src/index.mjs'
import { sharedPluginTests } from '../../../tests/plugins/shared.mjs'
// Run shared tests
sharedPluginTests(plugin)

View file

@ -2,6 +2,7 @@ import { Tab, Tabs } from '../tabs.mjs'
import { Mdx } from 'shared/components/mdx/dynamic.mjs'
import { pluginFlip } from '@freesewing/plugin-flip'
import { pluginGore } from '@freesewing/plugin-gore'
import { pluginRingsector } from '@freesewing/plugin-ringsector'
import { Design } from '@freesewing/core'
import yaml from 'js-yaml'
import { Pattern, PatternXray } from 'pkgs/react-components/src/index.mjs'
@ -48,7 +49,7 @@ const buildPattern = (children, settings = { margin: 5 }, tutorial = false, pape
lengthRatio: { pct: 75, min: 55, max: 85, menu: 'style' },
}
: {},
plugins: [pluginFlip, pluginGore],
plugins: [pluginFlip, pluginGore, pluginRingsector],
}
const design = new Design({
parts: [part],