2018-08-20 14:33:29 +02:00
|
|
|
/** Calculates the differece between actual and optimal sleevecap length
|
|
|
|
* Positive values mean sleevecap is longer than armhole
|
|
|
|
*/
|
|
|
|
function sleevecapDelta(store) {
|
2019-08-03 15:03:33 +02:00
|
|
|
return store.get('sleevecapLength') - store.get('sleevecapTarget')
|
2018-08-20 14:33:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function sleevecapAdjust(store) {
|
2019-08-03 15:03:33 +02:00
|
|
|
let delta = sleevecapDelta(store)
|
|
|
|
let factor = store.get('sleeveFactor')
|
|
|
|
if (delta > 0) factor = factor * 0.98
|
|
|
|
else factor = factor * 1.02
|
|
|
|
store.set('sleeveFactor', factor)
|
2018-08-20 14:33:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function draftSleevecap(part, run) {
|
2019-08-03 15:03:33 +02:00
|
|
|
let { debug, units, store, measurements, options, Point, points, Path, paths } = part.shorthand()
|
2018-08-20 14:33:29 +02:00
|
|
|
// Sleeve center axis
|
2019-08-03 15:03:33 +02:00
|
|
|
points.centerBiceps = new Point(0, 0)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.centerCap = points.centerBiceps.shift(
|
|
|
|
90,
|
2019-02-24 08:49:30 +01:00
|
|
|
options.sleevecapTopFactorY *
|
|
|
|
(measurements.bicepsCircumference *
|
|
|
|
(1 + options.bicepsEase) *
|
|
|
|
options.armholeDepthFactor *
|
2019-08-03 15:03:33 +02:00
|
|
|
store.get('sleeveFactor'))
|
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
|
|
|
|
// Left and right biceps points, limit impact of sleeveFactor to 25%
|
2019-08-03 15:03:33 +02:00
|
|
|
let halfWidth = (measurements.bicepsCircumference * (1 + options.bicepsEase)) / 2
|
2018-08-20 14:33:29 +02:00
|
|
|
points.bicepsLeft = points.centerBiceps.shift(
|
|
|
|
180,
|
|
|
|
halfWidth * options.sleeveWidthGuarantee +
|
2019-08-03 15:03:33 +02:00
|
|
|
halfWidth * (1 - options.sleeveWidthGuarantee) * store.get('sleeveFactor')
|
|
|
|
)
|
|
|
|
points.bicepsRight = points.bicepsLeft.flipX(points.centerBiceps)
|
2018-08-20 14:33:29 +02:00
|
|
|
|
2019-02-24 08:49:30 +01:00
|
|
|
// Adapt sleeve center axis
|
2019-08-03 15:03:33 +02:00
|
|
|
points.capLeft = new Point(points.bicepsLeft.x, points.centerCap.y)
|
|
|
|
points.capRight = points.capLeft.flipX()
|
2019-02-24 08:49:30 +01:00
|
|
|
points.centerCap = points.capLeft.shiftFractionTowards(
|
|
|
|
points.capRight,
|
|
|
|
options.sleevecapTopFactorX
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2019-02-24 08:49:30 +01:00
|
|
|
|
2018-08-20 14:33:29 +02:00
|
|
|
// Pitch points
|
2019-08-03 15:03:33 +02:00
|
|
|
let width = points.bicepsRight.x
|
|
|
|
let height = points.centerCap.y
|
2018-08-20 14:33:29 +02:00
|
|
|
points.backPitch = new Point(
|
|
|
|
-1 * width * options.sleevecapBackFactorX,
|
|
|
|
height * options.sleevecapBackFactorY
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.frontPitch = new Point(
|
|
|
|
width * options.sleevecapFrontFactorX,
|
|
|
|
height * options.sleevecapFrontFactorY
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
|
|
|
|
// 4 sleevecap quadrants
|
|
|
|
// Base points
|
2019-08-03 15:03:33 +02:00
|
|
|
points.capQ1Base = points.frontPitch.shiftFractionTowards(points.bicepsRight, 0.5)
|
|
|
|
points.capQ2Base = points.frontPitch.shiftFractionTowards(points.centerCap, 0.5)
|
|
|
|
points.capQ3Base = points.backPitch.shiftFractionTowards(points.centerCap, 0.5)
|
|
|
|
points.capQ4Base = points.backPitch.shiftFractionTowards(points.bicepsLeft, 0.5)
|
2018-08-20 14:33:29 +02:00
|
|
|
// Offset points
|
2019-08-03 15:03:33 +02:00
|
|
|
let baseOffset = measurements.bicepsCircumference * (1 + options.bicepsEase)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.capQ1 = points.capQ1Base.shift(
|
|
|
|
points.bicepsRight.angle(points.frontPitch) + 90,
|
|
|
|
baseOffset * options.sleevecapQ1Offset
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.capQ2 = points.capQ2Base.shift(
|
|
|
|
points.centerCap.angle(points.frontPitch) + 90,
|
|
|
|
baseOffset * options.sleevecapQ2Offset
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.capQ3 = points.capQ3Base.shift(
|
|
|
|
points.centerCap.angle(points.backPitch) - 90,
|
|
|
|
baseOffset * options.sleevecapQ3Offset
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.capQ4 = points.capQ4Base.shift(
|
|
|
|
points.bicepsLeft.angle(points.backPitch) - 90,
|
|
|
|
baseOffset * options.sleevecapQ4Offset
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
// Control points
|
|
|
|
points.capQ1Cp1 = points.capQ1.shift(
|
|
|
|
points.frontPitch.angle(points.bicepsRight),
|
|
|
|
baseOffset * options.sleevecapQ1Spread1
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.capQ1Cp2 = points.capQ1.shift(
|
|
|
|
points.frontPitch.angle(points.bicepsRight),
|
|
|
|
baseOffset * options.sleevecapQ1Spread2 * -1
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.capQ2Cp1 = points.capQ2.shift(
|
|
|
|
points.centerCap.angle(points.frontPitch),
|
|
|
|
baseOffset * options.sleevecapQ2Spread1
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.capQ2Cp2 = points.capQ2.shift(
|
|
|
|
points.centerCap.angle(points.frontPitch),
|
|
|
|
baseOffset * options.sleevecapQ2Spread2 * -1
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.capQ3Cp1 = points.capQ3.shift(
|
|
|
|
points.backPitch.angle(points.centerCap),
|
|
|
|
baseOffset * options.sleevecapQ3Spread1
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.capQ3Cp2 = points.capQ3.shift(
|
|
|
|
points.backPitch.angle(points.centerCap),
|
|
|
|
baseOffset * options.sleevecapQ3Spread2 * -1
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.capQ4Cp1 = points.capQ4.shift(
|
|
|
|
points.bicepsLeft.angle(points.backPitch),
|
|
|
|
baseOffset * options.sleevecapQ4Spread1
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
points.capQ4Cp2 = points.capQ4.shift(
|
|
|
|
points.bicepsLeft.angle(points.backPitch),
|
|
|
|
baseOffset * options.sleevecapQ4Spread2 * -1
|
2019-08-03 15:03:33 +02:00
|
|
|
)
|
2018-08-20 14:33:29 +02:00
|
|
|
|
|
|
|
// Sleevecap seamline
|
|
|
|
paths.sleevecap = new Path()
|
|
|
|
.move(points.bicepsRight)
|
|
|
|
.curve(points.bicepsRight, points.capQ1Cp1, points.capQ1)
|
|
|
|
.curve(points.capQ1Cp2, points.capQ2Cp1, points.capQ2)
|
|
|
|
.curve(points.capQ2Cp2, points.capQ3Cp1, points.capQ3)
|
|
|
|
.curve(points.capQ3Cp2, points.capQ4Cp1, points.capQ4)
|
2019-08-03 15:03:33 +02:00
|
|
|
.curve(points.capQ4Cp2, points.bicepsLeft, points.bicepsLeft)
|
2018-08-20 14:33:29 +02:00
|
|
|
|
|
|
|
// Store sleevecap length
|
2019-08-03 15:03:33 +02:00
|
|
|
store.set('sleevecapLength', paths.sleevecap.length())
|
2018-09-23 13:14:25 +02:00
|
|
|
if (run === 0) {
|
2019-08-03 15:03:33 +02:00
|
|
|
let armholeLength = store.get('frontArmholeLength') + store.get('backArmholeLength')
|
|
|
|
let sleevecapEase = armholeLength * options.sleevecapEase
|
|
|
|
store.set('sleevecapEase', sleevecapEase)
|
|
|
|
store.set('sleevecapTarget', armholeLength + sleevecapEase)
|
2018-12-21 19:00:52 +01:00
|
|
|
debug({
|
2019-08-03 15:03:33 +02:00
|
|
|
style: 'info',
|
|
|
|
label: '🗸 Sleevecap ease',
|
2018-12-21 19:00:52 +01:00
|
|
|
msg: units(sleevecapEase)
|
2019-08-03 15:03:33 +02:00
|
|
|
})
|
2018-08-20 14:33:29 +02:00
|
|
|
|
|
|
|
// Uncomment this line to see all sleevecap iterations
|
|
|
|
//paths[run] = paths.sleevecap;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-17 14:42:28 +01:00
|
|
|
export default part => {
|
2019-08-03 15:03:33 +02:00
|
|
|
let { debug, store, units, options, Point, points, paths } = part.shorthand()
|
2018-08-20 14:33:29 +02:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
store.set('sleeveFactor', 1)
|
|
|
|
let run = 0
|
|
|
|
let delta = 0
|
2018-12-17 14:42:28 +01:00
|
|
|
do {
|
2019-08-03 15:03:33 +02:00
|
|
|
draftSleevecap(part, run)
|
|
|
|
delta = sleevecapDelta(store)
|
|
|
|
sleevecapAdjust(store)
|
|
|
|
run++
|
|
|
|
} while (options.brianFitSleeve === true && run < 30 && Math.abs(sleevecapDelta(store)) > 2)
|
2018-12-17 14:42:28 +01:00
|
|
|
if (options.brianFitSleeve) {
|
2018-12-21 19:00:52 +01:00
|
|
|
debug({
|
2019-08-03 15:03:33 +02:00
|
|
|
style: 'success',
|
|
|
|
label: '🏁 Sleevecap fitted',
|
|
|
|
msg: `Target was ${units(store.get('sleevecapTarget'))}, delta of ${units(
|
2018-12-17 14:42:28 +01:00
|
|
|
delta
|
|
|
|
)} reached in ${run} attempts.`
|
2019-08-03 15:03:33 +02:00
|
|
|
})
|
2018-12-17 14:42:28 +01:00
|
|
|
} else
|
2018-12-21 19:00:52 +01:00
|
|
|
debug({
|
2019-08-03 15:03:33 +02:00
|
|
|
style: 'warning',
|
|
|
|
label: '🚫 Not fittingsleevecap',
|
|
|
|
msg: '(in Brian)'
|
|
|
|
})
|
2018-08-20 14:33:29 +02:00
|
|
|
|
2018-12-17 14:42:28 +01:00
|
|
|
// Paths
|
2019-08-03 15:03:33 +02:00
|
|
|
paths.sleevecap.attr('class', 'fabric')
|
2018-08-20 14:33:29 +02:00
|
|
|
|
2018-12-17 14:42:28 +01:00
|
|
|
// Anchor point for sampling
|
2019-08-03 15:03:33 +02:00
|
|
|
points.gridAnchor = new Point(0, 0)
|
2018-08-20 14:33:29 +02:00
|
|
|
|
2019-08-03 15:03:33 +02:00
|
|
|
return part
|
|
|
|
}
|