2022-09-04 19:03:18 +02:00
|
|
|
import { pctBasedOn } from '@freesewing/core'
|
|
|
|
import { elastics } from '@freesewing/snapseries'
|
2023-10-16 16:19:27 +02:00
|
|
|
import { ringsectorPlugin } from '@freesewing/plugin-ringsector'
|
2019-07-12 10:04:42 +02:00
|
|
|
|
2022-09-11 16:39:52 +02:00
|
|
|
function sandySkirt({
|
|
|
|
utils,
|
|
|
|
store,
|
|
|
|
sa,
|
|
|
|
points,
|
|
|
|
Path,
|
|
|
|
paths,
|
|
|
|
Snippet,
|
|
|
|
snippets,
|
|
|
|
options,
|
|
|
|
measurements,
|
|
|
|
macro,
|
|
|
|
absoluteOptions,
|
|
|
|
part,
|
|
|
|
}) {
|
2019-07-11 16:49:28 +02:00
|
|
|
// Circumference of the top of the waistband, calculated from the waistbandPosition option
|
|
|
|
store.set(
|
2019-08-03 15:03:33 +02:00
|
|
|
'topCircumference',
|
2020-06-28 13:06:26 +02:00
|
|
|
options.waistbandPosition * measurements.hips +
|
|
|
|
(1 - options.waistbandPosition) * measurements.waist
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2019-07-11 16:49:28 +02:00
|
|
|
// Circumference of the bottom of the waistband
|
2019-08-03 15:03:33 +02:00
|
|
|
if (options.waistbandShape === 'curved') {
|
2019-07-11 16:49:28 +02:00
|
|
|
// If the waistband is curved, the bottom circumference is calculated from the measurements
|
|
|
|
store.set(
|
2019-08-03 15:03:33 +02:00
|
|
|
'bottomCircumference',
|
|
|
|
store.get('topCircumference') +
|
2021-10-29 16:06:28 +02:00
|
|
|
(absoluteOptions.waistbandWidth * (measurements.hips - measurements.waist)) /
|
2020-06-07 13:50:13 +02:00
|
|
|
measurements.waistToHips
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2019-07-11 16:49:28 +02:00
|
|
|
} else {
|
|
|
|
// If the waistband is straight, the bottom circumference is the same as the top circumference
|
2019-08-03 15:03:33 +02:00
|
|
|
store.set('bottomCircumference', store.get('topCircumference'))
|
2019-07-11 16:49:28 +02:00
|
|
|
}
|
2019-07-12 10:04:42 +02:00
|
|
|
|
|
|
|
// Overlap of the waistband
|
2019-08-03 15:03:33 +02:00
|
|
|
store.set('waistbandOverlap', store.get('topCircumference') * options.waistbandOverlap)
|
2019-07-12 10:04:42 +02:00
|
|
|
|
2019-07-11 16:49:28 +02:00
|
|
|
// The top circumference of the skirt corresponds to the bottom circumference of the waistband, plus the extraWaist option for gathering/pleating
|
2019-08-03 15:03:33 +02:00
|
|
|
store.set('skirtCircumference', store.get('bottomCircumference') * (1 + options.gathering))
|
2019-07-11 16:49:28 +02:00
|
|
|
|
|
|
|
// The length from the top of the skirt to the floor (max length available)
|
|
|
|
store.set(
|
2019-08-03 15:03:33 +02:00
|
|
|
'fullLength',
|
2020-06-07 13:50:13 +02:00
|
|
|
measurements.waistToFloor - measurements.waistToHips * options.waistbandPosition
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2019-07-11 16:49:28 +02:00
|
|
|
|
2024-01-20 19:55:46 +01:00
|
|
|
let radiusWaist, angle
|
2019-07-11 16:49:28 +02:00
|
|
|
if (options.seamlessFullCircle) {
|
|
|
|
/**
|
|
|
|
* If the seamless full circle option is selected, the angle
|
|
|
|
* is 90º, and the radius of the waist arc is half than if
|
|
|
|
* it's not selected, because in this case the fabric is cut
|
|
|
|
* in a double fold
|
|
|
|
*/
|
2024-01-20 19:55:46 +01:00
|
|
|
angle = 90
|
|
|
|
radiusWaist = store.get('skirtCircumference') / utils.deg2rad(angle) / 4
|
2019-07-11 16:49:28 +02:00
|
|
|
} else {
|
|
|
|
/**
|
|
|
|
* If the seamless full circle option is not selected, the
|
|
|
|
* angle is calculated using the circlePercent option
|
|
|
|
*/
|
2024-01-20 19:55:46 +01:00
|
|
|
const totalAngle = 180 * options.circleRatio
|
|
|
|
angle = totalAngle / options.panels
|
|
|
|
radiusWaist = store.get('skirtCircumference') / utils.deg2rad(totalAngle) / 2
|
2019-07-11 16:49:28 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* If the angle is too large, the seam allowance can fall out
|
|
|
|
* of the fold of the fabric, so we limit the angle to a
|
|
|
|
* maximum angle calculated so the seam allowance fits in the
|
|
|
|
* fabric
|
|
|
|
*/
|
2024-01-20 19:55:46 +01:00
|
|
|
if (angle > 90 && sa) {
|
|
|
|
const maxAngle = utils.rad2deg(Math.atan(radiusWaist / sa))
|
|
|
|
if (angle > 90 + maxAngle) angle = 90 + maxAngle
|
2019-07-11 16:49:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* The radius of the hem arc is the radius of the waist
|
|
|
|
* arc with the length of the skirt added
|
|
|
|
*/
|
2021-11-01 17:40:31 +01:00
|
|
|
const radiusHem =
|
2021-10-29 16:06:28 +02:00
|
|
|
radiusWaist + store.get('fullLength') * options.lengthBonus - absoluteOptions.waistbandWidth
|
2019-07-11 16:49:28 +02:00
|
|
|
|
2023-10-16 16:19:27 +02:00
|
|
|
// Call the RingSector macro to draft the part
|
2023-10-18 16:00:15 +02:00
|
|
|
const ids = macro('ringsector', {
|
2024-01-20 19:55:46 +01:00
|
|
|
angle: angle,
|
2023-10-16 16:19:27 +02:00
|
|
|
insideRadius: radiusWaist,
|
|
|
|
outsideRadius: radiusHem,
|
|
|
|
rotate: true,
|
|
|
|
})
|
2023-10-18 16:00:15 +02:00
|
|
|
const pathId = ids.paths.path
|
2023-10-16 16:19:27 +02:00
|
|
|
paths.seam = paths[pathId].clone().addClass('fabric')
|
|
|
|
paths[pathId].hide()
|
2019-07-11 16:49:28 +02:00
|
|
|
|
2023-10-16 16:19:27 +02:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
2023-10-18 16:00:15 +02:00
|
|
|
for (const [shortId, uid] of Object.entries(ids.points)) {
|
2023-10-16 16:19:27 +02:00
|
|
|
points[shortId] = points[uid].copy()
|
|
|
|
// Some points are rotated, we need those too
|
|
|
|
if (points[uid + 'Rotated']) points[shortId + 'Rotated'] = points[uid + 'Rotated'].copy()
|
|
|
|
}
|
2019-07-11 16:49:28 +02:00
|
|
|
|
|
|
|
// Anchor samples to the centre of the waist
|
2019-08-03 15:03:33 +02:00
|
|
|
points.gridAnchor = points.in2Flipped.clone()
|
2019-07-11 16:49:28 +02:00
|
|
|
|
2023-09-18 14:19:18 +02:00
|
|
|
if (sa) {
|
|
|
|
paths.hemBase = new Path()
|
2023-10-31 18:41:09 +00:00
|
|
|
.move(points.ex2Flipped)
|
|
|
|
.curve(points.ex2cFlipped, points.ex1cFlipped, points.ex1)
|
|
|
|
.curve(points.ex1c, points.ex2c, points.ex2)
|
|
|
|
.offset(store.get('fullLength') * options.lengthBonus * options.hemWidth)
|
2023-09-18 14:19:18 +02:00
|
|
|
paths.saBase = new Path()
|
2023-10-31 18:41:09 +00:00
|
|
|
.move(points.in2)
|
|
|
|
.curve(points.in2c, points.in1c, points.in1)
|
|
|
|
.curve(points.in1cFlipped, points.in2cFlipped, points.in2Flipped)
|
2023-10-16 16:19:27 +02:00
|
|
|
|
2023-10-31 18:41:09 +00:00
|
|
|
if (!options.seamlessFullCircle)
|
|
|
|
paths.saBase = new Path().move(points.ex2).line(points.ex2).join(paths.saBase)
|
|
|
|
paths.saBase = paths.saBase.offset(sa)
|
2023-09-18 14:19:18 +02:00
|
|
|
|
|
|
|
paths.hemBase.hide()
|
|
|
|
paths.saBase.hide()
|
2019-08-03 15:03:33 +02:00
|
|
|
|
2023-09-18 14:19:18 +02:00
|
|
|
if (options.seamlessFullCircle) {
|
|
|
|
paths.sa = new Path()
|
2023-10-31 18:41:09 +00:00
|
|
|
.move(points.ex2Flipped)
|
2023-09-18 14:19:18 +02:00
|
|
|
.line(paths.hemBase.start())
|
|
|
|
.join(paths.hemBase)
|
2023-10-31 18:41:09 +00:00
|
|
|
.line(points.ex2)
|
|
|
|
.move(points.in2)
|
|
|
|
.line(paths.saBase.start())
|
|
|
|
.join(paths.saBase)
|
|
|
|
.line(points.in2Flipped)
|
2023-09-18 14:19:18 +02:00
|
|
|
.attr('class', 'fabric sa')
|
|
|
|
} else {
|
|
|
|
paths.sa = new Path()
|
2023-10-31 18:41:09 +00:00
|
|
|
.move(points.ex2Flipped)
|
2023-09-18 14:19:18 +02:00
|
|
|
.line(paths.hemBase.start())
|
|
|
|
.join(paths.hemBase)
|
2023-10-31 18:41:09 +00:00
|
|
|
.line(paths.saBase.start())
|
|
|
|
.join(paths.saBase)
|
|
|
|
.line(points.in2Flipped)
|
2023-09-18 14:19:18 +02:00
|
|
|
.attr('class', 'fabric sa')
|
2019-07-11 16:49:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-18 14:19:18 +02:00
|
|
|
/*
|
|
|
|
* Annotations
|
|
|
|
*/
|
|
|
|
// Cutlist
|
2024-01-21 13:43:45 +01:00
|
|
|
store.cutlist.setCut({
|
|
|
|
cut: options.seamlessFullCircle ? 1 : Number(options.panels),
|
|
|
|
from: 'fabric',
|
|
|
|
onFold: true,
|
|
|
|
identical: true,
|
|
|
|
})
|
2023-09-18 14:19:18 +02:00
|
|
|
|
|
|
|
// Cutonfold
|
|
|
|
macro('cutonfold', {
|
|
|
|
from: points.in2Flipped,
|
|
|
|
to: points.ex2Flipped,
|
|
|
|
grainline: true,
|
|
|
|
})
|
|
|
|
if (options.seamlessFullCircle) {
|
|
|
|
macro('cutonfold', {
|
2023-10-16 16:19:27 +02:00
|
|
|
from: points.ex2,
|
|
|
|
to: points.in2,
|
2023-09-18 14:19:18 +02:00
|
|
|
id: 'double',
|
2023-10-16 16:19:27 +02:00
|
|
|
reverse: true,
|
2023-09-18 14:19:18 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Logo
|
2023-10-31 18:41:09 +00:00
|
|
|
points.logo = points.in2FlippedRotated.shiftFractionTowards(
|
|
|
|
points.ex2FlippedRotated,
|
|
|
|
options.seamlessFullCircle ? 0.3 : 0.1
|
|
|
|
)
|
2023-09-18 14:19:18 +02:00
|
|
|
snippets.logo = new Snippet('logo', points.logo)
|
|
|
|
|
|
|
|
// Title
|
2023-10-31 18:41:09 +00:00
|
|
|
points.title = points.in2FlippedRotated.shiftFractionTowards(
|
|
|
|
points.ex2FlippedRotated,
|
|
|
|
options.seamlessFullCircle ? 0.5 : 0.25
|
|
|
|
)
|
2023-09-18 14:19:18 +02:00
|
|
|
macro('title', { at: points.title, nr: 1, title: 'skirt' })
|
|
|
|
|
|
|
|
// Scalebox
|
2023-10-31 18:41:09 +00:00
|
|
|
points.scalebox = points.in2FlippedRotated.shiftFractionTowards(
|
|
|
|
points.ex2FlippedRotated,
|
|
|
|
options.seamlessFullCircle ? 0.7 : 0.45
|
|
|
|
)
|
2023-09-18 14:19:18 +02:00
|
|
|
macro('scalebox', { at: points.scalebox })
|
|
|
|
|
|
|
|
// Notches
|
|
|
|
macro('sprinkle', {
|
|
|
|
snippet: 'notch',
|
2023-10-16 16:19:27 +02:00
|
|
|
on: ['in2', 'gridAnchor'],
|
2023-09-18 14:19:18 +02:00
|
|
|
})
|
|
|
|
snippets.center = new Snippet('bnotch', points.center)
|
|
|
|
|
|
|
|
// Dimensions
|
|
|
|
macro('vd', {
|
|
|
|
id: 'hLeft',
|
|
|
|
from: points.ex2Flipped,
|
|
|
|
to: points.in2Flipped,
|
|
|
|
x: points.ex2Flipped.x - sa - 15,
|
|
|
|
})
|
|
|
|
macro('vd', {
|
|
|
|
id: 'hToOpeningLeft',
|
|
|
|
from: points.in2Flipped,
|
|
|
|
to: points.center,
|
|
|
|
x: points.ex2Flipped.x - sa - 15,
|
|
|
|
})
|
|
|
|
macro('vd', {
|
|
|
|
id: 'hFull',
|
|
|
|
from: points.ex2Flipped,
|
|
|
|
to: points.center,
|
|
|
|
x: points.ex2Flipped.x - sa - 30,
|
|
|
|
})
|
2024-01-20 19:55:46 +01:00
|
|
|
if (angle !== 90) {
|
2019-08-03 15:03:33 +02:00
|
|
|
macro('vd', {
|
2023-09-18 14:19:18 +02:00
|
|
|
id: 'hTopToOpeningRight',
|
2024-01-19 20:49:02 -08:00
|
|
|
from: points.ex2,
|
|
|
|
to: points.in2,
|
|
|
|
x: angle > 90 ? points.in2.x - sa - 15 : points.ex2.x + sa + 15,
|
2019-08-03 15:03:33 +02:00
|
|
|
})
|
|
|
|
macro('vd', {
|
2023-09-18 14:19:18 +02:00
|
|
|
id: 'hOpeningRightToCenter',
|
2024-01-19 20:49:02 -08:00
|
|
|
from: points.in2,
|
2019-07-13 08:22:35 +02:00
|
|
|
to: points.center,
|
2024-01-19 20:49:02 -08:00
|
|
|
x: angle > 90 ? points.in2.x - sa - 15 : points.ex2.x + sa + 15,
|
2019-08-03 15:03:33 +02:00
|
|
|
})
|
|
|
|
macro('vd', {
|
2023-09-18 14:19:18 +02:00
|
|
|
id: 'hHemRightToCenter',
|
2024-01-19 20:49:02 -08:00
|
|
|
from: points.ex2,
|
2019-07-13 08:22:35 +02:00
|
|
|
to: points.center,
|
2024-01-19 20:49:02 -08:00
|
|
|
x: angle > 90 ? points.in2.x - sa - 30 : points.ex2.x + sa + 30,
|
|
|
|
})
|
|
|
|
macro('hd', {
|
|
|
|
id: 'wHemToOpeningRight',
|
|
|
|
from: points.ex2,
|
|
|
|
to: points.in2,
|
|
|
|
y: angle < 90 ? points.center.y - sa - 15 : points.ex2.y - sa - 15,
|
|
|
|
})
|
|
|
|
macro('hd', {
|
|
|
|
id: 'wOpeningRightToCenter',
|
|
|
|
from: points.center,
|
|
|
|
to: points.in2,
|
|
|
|
y: angle < 90 ? points.center.y - sa - 15 : points.ex2.y - sa - 15,
|
|
|
|
})
|
|
|
|
macro('hd', {
|
|
|
|
id: 'wHemToCenter',
|
|
|
|
from: points.center,
|
|
|
|
to: points.ex2,
|
|
|
|
y: angle < 90 ? points.center.y - sa - 30 : points.ex2.y - sa - 30,
|
2019-08-03 15:03:33 +02:00
|
|
|
})
|
2019-07-11 16:49:28 +02:00
|
|
|
}
|
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return part
|
2019-07-11 16:49:28 +02:00
|
|
|
}
|
2022-09-04 19:03:18 +02:00
|
|
|
|
|
|
|
export const skirt = {
|
|
|
|
name: 'sandy.skirt',
|
|
|
|
measurements: ['waist', 'waistToFloor', 'waistToHips', 'hips'],
|
|
|
|
options: {
|
|
|
|
minimumOverlap: 15, // Lower than this and we don't draw a button
|
2022-09-05 20:41:19 -07:00
|
|
|
seamlessFullCircle: { bool: false, menu: 'construction' },
|
2022-09-11 16:39:52 +02:00
|
|
|
waistbandWidth: {
|
|
|
|
pct: 4,
|
|
|
|
min: 1,
|
|
|
|
max: 8,
|
|
|
|
snap: elastics,
|
|
|
|
...pctBasedOn('waistToFloor'),
|
|
|
|
menu: 'style',
|
|
|
|
},
|
2022-09-05 20:41:19 -07:00
|
|
|
waistbandPosition: { pct: 50, min: 0, max: 100, menu: 'fit' },
|
|
|
|
lengthBonus: { pct: 50, min: 10, max: 100, menu: 'style' },
|
|
|
|
circleRatio: { pct: 50, min: 20, max: 100, menu: 'style' },
|
|
|
|
waistbandOverlap: { pct: 3, min: 0, max: 15, menu: 'style' },
|
|
|
|
gathering: { pct: 0, min: 0, max: 200, menu: 'style' },
|
|
|
|
hemWidth: { pct: 2, min: 1, max: 10, menu: 'construction' },
|
2022-09-04 19:03:18 +02:00
|
|
|
waistbandShape: {
|
|
|
|
list: ['straight', 'curved'],
|
|
|
|
dflt: 'straight',
|
2022-09-05 20:41:19 -07:00
|
|
|
menu: 'fit',
|
2022-09-04 19:03:18 +02:00
|
|
|
},
|
2024-01-20 19:55:46 +01:00
|
|
|
panels: { count: 1, min: 1, max: 8, menu: 'construction' },
|
2022-09-04 19:03:18 +02:00
|
|
|
},
|
2023-10-16 16:19:27 +02:00
|
|
|
plugins: ringsectorPlugin,
|
2022-09-04 19:03:18 +02:00
|
|
|
draft: sandySkirt,
|
|
|
|
}
|