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:
parent
5d03d146f7
commit
075cb34184
17 changed files with 727 additions and 166 deletions
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
|
92
markdown/dev/reference/macros/ringsector/en.md
Normal file
92
markdown/dev/reference/macros/ringsector/en.md
Normal 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>
|
||||
|
56
markdown/dev/reference/macros/rmringsector/en.md
Normal file
56
markdown/dev/reference/macros/rmringsector/en.md
Normal 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).
|
||||
|
30
markdown/dev/reference/plugins/ringsector/en.md
Normal file
30
markdown/dev/reference/plugins/ringsector/en.md
Normal 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
|
17
plugins/plugin-ringsector/CHANGELOG.md
Normal file
17
plugins/plugin-ringsector/CHANGELOG.md
Normal 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.
|
||||
|
143
plugins/plugin-ringsector/README.md
Normal file
143
plugins/plugin-ringsector/README.md
Normal file
|
@ -0,0 +1,143 @@
|
|||

|
||||
<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:
|
||||
>
|
||||
> [](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).
|
||||
|
35
plugins/plugin-ringsector/build.mjs
Normal file
35
plugins/plugin-ringsector/build.mjs
Normal 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()
|
4
plugins/plugin-ringsector/data.mjs
Normal file
4
plugins/plugin-ringsector/data.mjs
Normal 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 }
|
71
plugins/plugin-ringsector/package.json
Normal file
71
plugins/plugin-ringsector/package.json
Normal 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"
|
||||
}
|
||||
}
|
171
plugins/plugin-ringsector/src/index.mjs
Normal file
171
plugins/plugin-ringsector/src/index.mjs
Normal 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
|
6
plugins/plugin-ringsector/tests/shared.test.mjs
Normal file
6
plugins/plugin-ringsector/tests/shared.test.mjs
Normal 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)
|
|
@ -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],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue