318 lines
11 KiB
JavaScript
318 lines
11 KiB
JavaScript
import { front } from './front.mjs'
|
|
import { back } from './back.mjs'
|
|
|
|
export function legFromHypotenuseLeg(hypotenuse, leg) {
|
|
return Math.sqrt(hypotenuse * hypotenuse - leg * leg)
|
|
}
|
|
|
|
function draftRaglanSleeve({
|
|
utils,
|
|
Path,
|
|
Point,
|
|
paths,
|
|
points,
|
|
measurements,
|
|
options,
|
|
part,
|
|
store,
|
|
sa,
|
|
macro,
|
|
snippets,
|
|
Snippet,
|
|
scale,
|
|
}) {
|
|
const armholeTweakFactor = options.armholeTweakFactor - options.raglanScoopMagnitude // How much larger to make the armhole as a proportion of the biceps measurement. The constant term is to account for the armhole being a bit wider than the biceps, while subtracting the raglan scoop is to adjust for the extra material that the scoop will insert.
|
|
const bicepsPosition = options.bicepsPosition // How far the biceps measurement is along the arm. 0 means at the armhole. 1 means at the wrist.
|
|
|
|
const neckRadius = store.get('neckRadius')
|
|
const adjustedBiceps = measurements.biceps * (1 + options.sleeveEase)
|
|
const adjustedWrist = measurements.wrist * (1 + options.wristEase)
|
|
|
|
points.raglanCenter = new Point(0, 0)
|
|
points.neckCenter = points.raglanCenter.shift(180, options.neckBalance * neckRadius)
|
|
|
|
const raglanLength = store.get('raglanLength') // Distance from the raglan center/origin to the armholes.
|
|
const scoopDepth = options.raglanScoopMagnitude * raglanLength
|
|
const scoopLength = options.raglanScoopLength * raglanLength
|
|
const widthAtArmhole = (adjustedBiceps / 2) * armholeTweakFactor
|
|
const lengthToArmhole = legFromHypotenuseLeg(raglanLength, widthAtArmhole)
|
|
|
|
const sleeveLength = options.sleeveLength * measurements.shoulderToWrist
|
|
// We're modeling the sleeve as a cone narrowing linearly from the top (biceps) to the bottom (wrist). The following interpolates so that we can make short sleeves, long sleeves, or anything in between.
|
|
let sleeveWidth
|
|
if (options.sleeveLength < bicepsPosition) {
|
|
const sleevePercent = 1 - options.sleeveLength / bicepsPosition
|
|
sleeveWidth = ((armholeTweakFactor - 1) * sleevePercent + 1) * adjustedBiceps
|
|
} else {
|
|
const sleevePercent = Math.min(
|
|
(options.sleeveLength - bicepsPosition) / (1 - bicepsPosition),
|
|
1
|
|
) // Get how far from the biceps to the wrist the sleeve is. Do not taper the sleeve any smaller than the wrist itself, for sleeves that extend past the wrist.
|
|
sleeveWidth = adjustedBiceps * (1 - sleevePercent) + adjustedWrist * sleevePercent // Interpolate
|
|
}
|
|
|
|
sleeveWidth /= 2 // Takes into account that there is fabric on both sides of the center line.
|
|
const totalLength = lengthToArmhole + sleeveLength
|
|
|
|
points.frontArmhole = new Point(-widthAtArmhole, lengthToArmhole)
|
|
points.backArmhole = new Point(widthAtArmhole, lengthToArmhole)
|
|
|
|
const frontRaglanAngle = points.raglanCenter.angle(points.frontArmhole)
|
|
const backRaglanAngle = points.raglanCenter.angle(points.backArmhole)
|
|
|
|
points.frontSleeve = new Point(-sleeveWidth, totalLength)
|
|
points.backSleeve = new Point(sleeveWidth, totalLength)
|
|
|
|
// For short sleeves, an adjustment is needed to account for the scoop making the armhole wider. This adjustment shrinks as the sleeve approaches the biceps, and is unneeded for bicep-length or longer sleeves.
|
|
if (options.sleeveLength < bicepsPosition) {
|
|
const sleevePercent = 1 - options.sleeveLength / bicepsPosition
|
|
points.frontSleeve = points.frontSleeve.shift(frontRaglanAngle - 90, sleevePercent * scoopDepth)
|
|
points.backSleeve = points.backSleeve.shift(backRaglanAngle + 90, sleevePercent * scoopDepth)
|
|
}
|
|
|
|
points.frontNeck = points.raglanCenter.shiftTowards(
|
|
points.frontArmhole,
|
|
store.get('frontNeckRadius')
|
|
)
|
|
points.backNeck = points.raglanCenter.shiftTowards(
|
|
points.backArmhole,
|
|
store.get('backNeckRadius')
|
|
)
|
|
|
|
const angleFrontNeck = points.neckCenter.angle(points.frontNeck)
|
|
const angleBackNeck = points.neckCenter.angle(points.backNeck)
|
|
const neckSleeveWidth = utils.deg2rad(angleBackNeck - angleFrontNeck) * neckRadius
|
|
|
|
const frontNecklineAngle = frontRaglanAngle + (180 - store.get('frontNecklineToRaglanAngle'))
|
|
const backNecklineAngle = backRaglanAngle - (180 - store.get('backNecklineToRaglanAngle'))
|
|
points.neckCP1 = points.backNeck.shift(backNecklineAngle, neckSleeveWidth * (1 / 3))
|
|
points.neckCP2 = points.frontNeck.shift(frontNecklineAngle, neckSleeveWidth * (1 / 3))
|
|
|
|
points.frontArmholeScooped = points.frontArmhole.shift(frontRaglanAngle - 90, scoopDepth)
|
|
points.frontArmholeScoopCP1 = points.frontArmhole.shift(
|
|
frontRaglanAngle + 180,
|
|
(1 / 3) * scoopLength
|
|
)
|
|
points.frontArmholeScoopCP2 = points.frontArmhole.shift(
|
|
frontRaglanAngle + 180,
|
|
(2 / 3) * scoopLength
|
|
)
|
|
points.frontArmholeScoopEnd = points.frontArmhole.shift(frontRaglanAngle + 180, scoopLength)
|
|
|
|
points.backArmholeScooped = points.backArmhole.shift(backRaglanAngle + 90, scoopDepth)
|
|
points.backArmholeScoopCP1 = points.backArmhole.shift(
|
|
backRaglanAngle + 180,
|
|
(1 / 3) * scoopLength
|
|
)
|
|
points.backArmholeScoopCP2 = points.backArmhole.shift(
|
|
backRaglanAngle + 180,
|
|
(2 / 3) * scoopLength
|
|
)
|
|
points.backArmholeScoopEnd = points.backArmhole.shift(backRaglanAngle + 180, scoopLength)
|
|
|
|
paths.saBase = new Path()
|
|
.move(points.backSleeve)
|
|
.line(points.backArmholeScooped)
|
|
.curve(points.backArmholeScoopCP1, points.backArmholeScoopCP2, points.backArmholeScoopEnd)
|
|
.line(points.backNeck)
|
|
.curve(points.neckCP1, points.neckCP2, points.frontNeck)
|
|
.line(points.frontArmholeScoopEnd)
|
|
.curve(points.frontArmholeScoopCP2, points.frontArmholeScoopCP1, points.frontArmholeScooped)
|
|
.line(points.frontSleeve)
|
|
.hide()
|
|
|
|
paths.hemBase = new Path().move(points.frontSleeve).line(points.backSleeve).hide()
|
|
|
|
paths.seam = paths.saBase.join(paths.hemBase).close().addClass('fabric')
|
|
|
|
macro('vd', {
|
|
id: 'hFrontRaglanSleeveStraightPortion',
|
|
from: points.frontNeck,
|
|
to: points.frontArmholeScoopEnd,
|
|
x: points.frontArmholeScooped.x - (sa + 15),
|
|
})
|
|
macro('vd', {
|
|
id: 'hFrontRaglanSleeveCurvedPortion',
|
|
from: points.frontArmholeScoopEnd,
|
|
to: points.frontArmholeScooped,
|
|
x: points.frontArmholeScooped.x - (sa + 15),
|
|
})
|
|
macro('vd', {
|
|
id: 'hFrontRaglanSleeve',
|
|
from: points.frontNeck,
|
|
to: points.frontArmholeScooped,
|
|
x: points.frontArmholeScooped.x - (sa + 30),
|
|
})
|
|
macro('vd', {
|
|
id: 'hFrontSleeve',
|
|
from: points.frontArmholeScooped,
|
|
to: points.frontSleeve,
|
|
x: points.frontArmholeScooped.x - (sa + 15),
|
|
})
|
|
macro('vd', {
|
|
id: 'hFrontTotal',
|
|
from: points.frontNeck,
|
|
to: points.frontSleeve,
|
|
x: points.frontArmholeScooped.x - (sa + 45),
|
|
})
|
|
|
|
macro('vd', {
|
|
id: 'hBackRaglanSleeveStraightPortion',
|
|
from: points.backNeck,
|
|
to: points.backArmholeScoopEnd,
|
|
x: points.backArmholeScooped.x + (sa + 15),
|
|
})
|
|
macro('vd', {
|
|
id: 'hBackRaglanSleeveCurvedPortion',
|
|
from: points.backArmholeScoopEnd,
|
|
to: points.backArmholeScooped,
|
|
x: points.backArmholeScooped.x + (sa + 15),
|
|
})
|
|
macro('vd', {
|
|
id: 'hBackRaglanSleeve',
|
|
from: points.backNeck,
|
|
to: points.backArmholeScooped,
|
|
x: points.backArmholeScooped.x + (sa + 30),
|
|
})
|
|
macro('vd', {
|
|
id: 'hBackSleeve',
|
|
from: points.backArmholeScooped,
|
|
to: points.backSleeve,
|
|
x: points.backArmholeScooped.x + (sa + 15),
|
|
})
|
|
macro('vd', {
|
|
id: 'hBackTotal',
|
|
from: points.backNeck,
|
|
to: points.backSleeve,
|
|
x: points.backArmholeScooped.x + (sa + 45),
|
|
})
|
|
|
|
macro('hd', {
|
|
id: 'wFrontSleeve',
|
|
from: points.frontArmholeScooped,
|
|
to: points.frontSleeve,
|
|
y: points.frontSleeve.y + (sa + 15),
|
|
noStartMarker: true,
|
|
noEndMarker: true,
|
|
})
|
|
macro('hd', {
|
|
id: 'wSleeveHem',
|
|
from: points.frontSleeve,
|
|
to: points.backSleeve,
|
|
y: points.frontSleeve.y + (sa + 15),
|
|
})
|
|
macro('hd', {
|
|
id: 'wBackSleeve',
|
|
from: points.backSleeve,
|
|
to: points.backArmholeScooped,
|
|
y: points.backSleeve.y + (sa + 15),
|
|
noStartMarker: true,
|
|
noEndMarker: true,
|
|
})
|
|
macro('hd', {
|
|
id: 'wWidthAtArmpit',
|
|
from: points.frontArmholeScooped,
|
|
to: points.backArmholeScooped,
|
|
y: points.frontSleeve.y + (sa + 30),
|
|
})
|
|
macro('hd', {
|
|
id: 'wWidthAtArmpit2',
|
|
from: points.frontArmholeScooped,
|
|
to: points.backArmholeScooped,
|
|
y: Math.min(points.frontNeck.y, points.backNeck.y) - (sa + 45),
|
|
})
|
|
|
|
macro('hd', {
|
|
id: 'wFrontRaglanSleeveCurvedPortion',
|
|
from: points.frontArmholeScooped,
|
|
to: points.frontArmholeScoopEnd,
|
|
y: Math.min(points.frontNeck.y, points.backNeck.y) - (sa + 15),
|
|
})
|
|
macro('hd', {
|
|
id: 'wFrontRaglanSleeveStraightPortion',
|
|
from: points.frontArmholeScoopEnd,
|
|
to: points.frontNeck,
|
|
y: Math.min(points.frontNeck.y, points.backNeck.y) - (sa + 15),
|
|
})
|
|
macro('hd', {
|
|
id: 'wNeck',
|
|
from: points.frontNeck,
|
|
to: points.backNeck,
|
|
y: Math.min(points.frontNeck.y, points.backNeck.y) - (sa + 15),
|
|
})
|
|
macro('hd', {
|
|
id: 'wBackRaglanSleeveStraightPortion',
|
|
from: points.backNeck,
|
|
to: points.backArmholeScoopEnd,
|
|
y: Math.min(points.frontNeck.y, points.backNeck.y) - (sa + 15),
|
|
})
|
|
macro('hd', {
|
|
id: 'wBackRaglanSleeveCurvedPortion',
|
|
from: points.backArmholeScoopEnd,
|
|
to: points.backArmholeScooped,
|
|
y: Math.min(points.frontNeck.y, points.backNeck.y) - (sa + 15),
|
|
})
|
|
macro('hd', {
|
|
id: 'wBackRaglanSleeve',
|
|
from: points.backNeck,
|
|
to: points.backArmholeScooped,
|
|
y: Math.min(points.frontNeck.y, points.backNeck.y) - (sa + 30),
|
|
})
|
|
macro('hd', {
|
|
id: 'wFrontRaglanSleeve',
|
|
from: points.frontArmholeScooped,
|
|
to: points.frontNeck,
|
|
y: Math.min(points.frontNeck.y, points.backNeck.y) - (sa + 30),
|
|
})
|
|
|
|
points.grainlineBottom = points.backSleeve.shiftFractionTowards(points.frontSleeve, 0.5)
|
|
points.grainlineTop = points.raglanCenter.shift(270, neckRadius)
|
|
macro('grainline', {
|
|
from: points.grainlineTop,
|
|
to: points.grainlineBottom,
|
|
})
|
|
|
|
store.cutlist.addCut({ cut: 2, from: 'fabric' })
|
|
|
|
snippets.frontArmholeScoopEnd = new Snippet('notch', points.frontArmholeScoopEnd)
|
|
snippets.backArmholeScoopEnd = new Snippet('bnotch', points.backArmholeScoopEnd)
|
|
|
|
points.title = new Point(0, points.backSleeve.y / 3)
|
|
macro('title', { at: points.title, nr: 3, title: 'sleeve' })
|
|
|
|
points.logo = points.title.shift(-90, 70 * scale)
|
|
snippets.logo = new Snippet('logo', points.logo)
|
|
points.scalebox = points.logo.shift(-90, 70 * scale)
|
|
macro('scalebox', { at: points.scalebox })
|
|
|
|
if (sa) {
|
|
paths.sa = paths.saBase
|
|
.offset(sa)
|
|
.join(paths.hemBase.offset(sa * options.sleeveHem))
|
|
.close()
|
|
.addClass('fabric sa')
|
|
}
|
|
|
|
const neckPath = new Path()
|
|
.move(points.backNeck)
|
|
.curve(points.neckCP1, points.neckCP2, points.frontNeck)
|
|
store.set('neckLengthSide', neckPath.length())
|
|
|
|
return part
|
|
}
|
|
|
|
export const raglanSleeve = {
|
|
name: 'shelly.raglanSleeve',
|
|
after: [front, back],
|
|
draft: draftRaglanSleeve,
|
|
measurements: ['wrist', 'shoulderToWrist'],
|
|
options: {
|
|
bicepsPosition: 0.2,
|
|
// How much ease to put around the wrist. For sleeves that don't reach the wrist, this value is interpolated with sleeveEase.
|
|
wristEase: { pct: 0, min: -30, max: 50, menu: 'fit' },
|
|
// How long the sleeve is. 100 is a long sleeve ending at the wrist. 20 is a typical short sleeve.
|
|
sleeveLength: { pct: 20, min: 0, max: 125, menu: 'style' },
|
|
// Length of the hem at the end of the sleeve, as a multiple of the seam allowance.
|
|
sleeveHem: { pct: 200, min: 0, max: 800, menu: 'construction' },
|
|
},
|
|
}
|