diff --git a/designs/aaron/build.js b/designs/aaron/build.js index 7248b245c69..ee51943e8cf 100644 --- a/designs/aaron/build.js +++ b/designs/aaron/build.js @@ -23,17 +23,22 @@ const options = { // Different formats const formats = { - cjs: "dist/index.js", - esm: "dist/index.mjs", + cjs: "js", + esm: "mjs", } // Let esbuild generate different formats let result (async () => { - for (const [format, outfile] of Object.entries(formats)) { + for (const [format, ext] of Object.entries(formats)) { + // Regular build result = await esbuild - .build({ ...options, outfile, format }) - .catch(() => process.exit(1)) + .build({ ...options, format, outfile: `dist/index.${ext}` }) + .catch(() => process.exit(1)) + // Config build + await esbuild + .build({ ...options, format, outfile: `dist/config.${ext}`, entryPoints: ['config/index.js'] }) + .catch(() => process.exit(1)) } if (process.env.VERBOSE) { @@ -41,11 +46,13 @@ let result console.log(info) } + // Also build a version that has all dependencies bundled // This makes it easy to run tests await esbuild .build({ ...options, + entryPoints: ['src/index.js'], minify: false, sourcemap: false, outfile: 'tests/dist/index.mjs', diff --git a/designs/aaron/config/index.js b/designs/aaron/config/index.js index 79cb08c948f..bde54bc9cd4 100644 --- a/designs/aaron/config/index.js +++ b/designs/aaron/config/index.js @@ -1,8 +1,6 @@ import { version } from '../package.json' -import configHelpers from '@freesewing/config-helpers' -const { pctBasedOn } = configHelpers -export default { +export const info = { version, name: 'aaron', design: 'Joost De Cock', @@ -22,7 +20,9 @@ export default { 'lengthBonus', ], }, - measurements: [ +} + +export const measurements = [ 'biceps', 'chest', 'hpsToWaistBack', @@ -31,43 +31,7 @@ export default { 'shoulderSlope', 'shoulderToShoulder', 'hips', - ], - optionalMeasurements: ['highBust'], - dependencies: { - front: 'base', - back: 'front', - }, - inject: { - front: 'base', - back: 'front', - }, - hide: ['base'], - options: { - // Constants - brianFitCollar: false, - collarFactor: 4.8, - acrossBackFactor: 0.97, - backNeckCutout: 0.05, - bicepsEase: 0.05, - shoulderEase: 0, - collarEase: 0, - frontArmholeDeeper: 0, - armholeDepthFactor: 0.6, - shoulderSlopeReduction: 0, +] - // Percentages - armholeDrop: { pct: 10, min: 0, max: 75 }, - backlineBend: { pct: 50, min: 25, max: 100 }, - chestEase: { pct: 8, min: 0, max: 20, ...pctBasedOn('chest') }, - hipsEase: { pct: 8, min: 0, max: 20 }, - lengthBonus: { pct: 10, min: -20, max: 60 }, - necklineBend: { pct: 100, min: 40, max: 100 }, - necklineDrop: { pct: 20, min: 10, max: 35 }, - stretchFactor: { pct: 5, min: 0, max: 15 }, - shoulderStrapWidth: { pct: 15, min: 10, max: 40 }, - shoulderStrapPlacement: { pct: 40, min: 20, max: 80 }, +export const optionalMeasurements = ['highBust'] - // draft for high bust - draftForHighBust: { bool: false }, - }, -} diff --git a/designs/aaron/config/options.js b/designs/aaron/config/options.js new file mode 100644 index 00000000000..89009658c61 --- /dev/null +++ b/designs/aaron/config/options.js @@ -0,0 +1,28 @@ +import configHelpers from '@freesewing/config-helpers' +const { pctBasedOn } = configHelpers + +export const brianFitCollar = false +export const brianFitSleeve = false +export const acrossBackFactor = 0.97 +export const backNeckCutout = 0.05 +export const bicepsEase = 0.05 +export const shoulderEase = 0 +export const collarEase = 0 +export const frontArmholeDeeper = 0 +export const armholeDepthFactor = 0.6 +export const shoulderSlopeReduction = 0 + +// Percentages +export const armholeDrop = { pct: 10, min: 0, max: 75 } +export const backlineBend = { pct: 50, min: 25, max: 100 } +export const chestEase = { pct: 8, min: 0, max: 20, ...pctBasedOn('chest') } +export const hipsEase = { pct: 8, min: 0, max: 20 } +export const lengthBonus = { pct: 10, min: -20, max: 60 } +export const necklineBend = { pct: 100, min: 40, max: 100 } +export const necklineDrop = { pct: 20, min: 10, max: 35 } +export const stretchFactor = { pct: 5, min: 0, max: 15 } +export const shoulderStrapWidth = { pct: 15, min: 10, max: 40 } +export const shoulderStrapPlacement = { pct: 40, min: 20, max: 80 } + +// draft for high bust +export const draftForHighBust = { bool: false } diff --git a/designs/aaron/src/back.js b/designs/aaron/src/back.js index e487aff46b9..1837205fa2d 100644 --- a/designs/aaron/src/back.js +++ b/designs/aaron/src/back.js @@ -1,103 +1,108 @@ import { dimensions } from './shared' +import front from "./front.js" -export default function (part) { - let { - store, - sa, - Point, - points, - Path, - paths, - options, - complete, - paperless, - macro, - utils, - units, - measurements, - } = part.shorthand() +export default { + from: front, + name: 'back', + draft: function (part) { + const { + store, + sa, + Point, + points, + Path, + paths, + options, + complete, + paperless, + macro, + utils, + units, + measurements, + } = part.shorthand() - // Lower back neck a bit - points.cbNeck.y = measurements.neck / 10 + // Lower back neck a bit + points.cbNeck.y = measurements.neck / 10 - points.strapLeftCp2 = utils.beamsIntersect( - points.strapLeft, - points.strapCenter.rotate(90, points.strapLeft), - points.cbNeck, - points.cbNeck.shift(0, 10) - ) + points.strapLeftCp2 = utils.beamsIntersect( + points.strapLeft, + points.strapCenter.rotate(90, points.strapLeft), + points.cbNeck, + points.cbNeck.shift(0, 10) + ) - points.armholeCp2 = points.armhole.shiftFractionTowards( - points.armholeCorner, - options.backlineBend - ) - points.strapRightCp1 = points.strapRight.shiftFractionTowards( - points.armholeCorner, - options.backlineBend - ) + points.armholeCp2 = points.armhole.shiftFractionTowards( + points.armholeCorner, + options.backlineBend + ) + points.strapRightCp1 = points.strapRight.shiftFractionTowards( + points.armholeCorner, + options.backlineBend + ) - points.anchor = points.cbNeck.clone() + points.anchor = points.cbNeck.clone() - // Seamline - paths.seam = new Path() - .move(points.cbNeck) - .line(points.cbHem) - .line(points.hem) - .curve_(points.hipsCp2, points.armhole) - .curve(points.armholeCp2, points.strapRightCp1, points.strapRight) - .line(points.strapLeft) - .line(points.strapLeft) - .curve(points.strapLeftCp2, points.cbNeck, points.cbNeck) - .close() - .attr('class', 'fabric') + // Seamline + paths.seam = new Path() + .move(points.cbNeck) + .line(points.cbHem) + .line(points.hem) + .curve_(points.hipsCp2, points.armhole) + .curve(points.armholeCp2, points.strapRightCp1, points.strapRight) + .line(points.strapLeft) + .line(points.strapLeft) + .curve(points.strapLeftCp2, points.cbNeck, points.cbNeck) + .close() + .attr('class', 'fabric') - // Complete pattern? - if (complete) { - let neckOpeningLength = - new Path() - .move(points.strapLeft) - .curve(points.strapLeftCp2, points.cbNeck, points.cbNeck) - .length() + store.get('frontNeckOpeningLength') - let armholeLength = - new Path() - .move(points.armhole) - .curve(points.armholeCp2, points.strapRightCp1, points.strapRight) - .length() + store.get('frontArmholeLength') - points.bindingAnchor = new Point(points.armhole.x / 4, points.armhole.y) - .attr('data-text', 'cutTwoStripsToFinishTheArmholes') - .attr('data-text', ':\n') - .attr('data-text', `2x: ${units(sa * 6 || 60)} x ${units(armholeLength * 0.95 + 2 * sa)}`) - .attr('data-text', '\n \n') - .attr('data-text', 'cutOneStripToFinishTheNeckOpening') - .attr('data-text', ':\n') - .attr('data-text', 'width') - .attr('data-text', ':') - .attr( - 'data-text', - `${units((sa || 10) * 6)} x ${units(neckOpeningLength * 2 * 0.95 + 2 * sa)}` - ) - //.attr('data-text-class', 'text-sm') + // Complete pattern? + if (complete) { + let neckOpeningLength = + new Path() + .move(points.strapLeft) + .curve(points.strapLeftCp2, points.cbNeck, points.cbNeck) + .length() + store.get('frontNeckOpeningLength') + let armholeLength = + new Path() + .move(points.armhole) + .curve(points.armholeCp2, points.strapRightCp1, points.strapRight) + .length() + store.get('frontArmholeLength') + points.bindingAnchor = new Point(points.armhole.x / 4, points.armhole.y) + .attr('data-text', 'cutTwoStripsToFinishTheArmholes') + .attr('data-text', ':\n') + .attr('data-text', `2x: ${units(sa * 6 || 60)} x ${units(armholeLength * 0.95 + 2 * sa)}`) + .attr('data-text', '\n \n') + .attr('data-text', 'cutOneStripToFinishTheNeckOpening') + .attr('data-text', ':\n') + .attr('data-text', 'width') + .attr('data-text', ':') + .attr( + 'data-text', + `${units((sa || 10) * 6)} x ${units(neckOpeningLength * 2 * 0.95 + 2 * sa)}` + ) + //.attr('data-text-class', 'text-sm') - macro('cutonfold', { - from: points.cfNeck, - to: points.cfHem, - grainline: true, - }) + macro('cutonfold', { + from: points.cfNeck, + to: points.cfHem, + grainline: true, + }) - macro('title', { at: points.title, nr: 2, title: 'back' }) - points.scaleboxAnchor = points.scalebox = points.title.shift(90, 100) - macro('scalebox', { at: points.scalebox }) + macro('title', { at: points.title, nr: 2, title: 'back' }) + points.scaleboxAnchor = points.scalebox = points.title.shift(90, 100) + macro('scalebox', { at: points.scalebox }) + } + + // Paperless? + if (paperless) { + dimensions(macro, points, sa) + macro('vd', { + from: points.cbHem, + to: points.cbNeck, + x: points.cbHem.x - sa - 15, + }) + } + + return part } - - // Paperless? - if (paperless) { - dimensions(macro, points, sa) - macro('vd', { - from: points.cbHem, - to: points.cbNeck, - x: points.cbHem.x - sa - 15, - }) - } - - return part } diff --git a/designs/aaron/src/front.js b/designs/aaron/src/front.js index a23c06238c0..d4ad7b678f1 100644 --- a/designs/aaron/src/front.js +++ b/designs/aaron/src/front.js @@ -1,169 +1,174 @@ import { dimensions } from './shared' +import { base } from '@freesewing/brian' -export default function (part) { - let { - utils, - store, - sa, - Point, - points, - Path, - paths, - Snippet, - snippets, - options, - measurements, - complete, - paperless, - macro, - } = part.shorthand() +export default { + from: base, + name: 'front', + draft: function (part) { + const { + utils, + store, + sa, + Point, + points, + Path, + paths, + Snippet, + snippets, + options, + measurements, + complete, + paperless, + macro, + } = part.shorthand() - // Hide Brian paths - for (let key of Object.keys(paths)) paths[key].render = false + // Hide Brian paths + for (let key of Object.keys(paths)) paths[key].render = false - // Handle stretch - for (let i in points) points[i].x = points[i].x * (1 - options.stretchFactor) + // Handle stretch + for (let i in points) points[i].x = points[i].x * (1 - options.stretchFactor) - // Clone cb (center back) into cf (center front) - for (let key of ['Neck', 'Shoulder', 'Armhole', 'Hips', 'Hem']) { - points[`cf${key}`] = points[`cb${key}`].clone() - } - - // Neckline - points.cfNeck = points.cfNeck.shift(-90, options.necklineDrop * measurements.hpsToWaistBack) - - // Strap - points.strapCenter = points.neck.shiftFractionTowards( - points.shoulder, - options.shoulderStrapPlacement - ) - points.strapLeft = points.strapCenter.shiftTowards( - points.neck, - points.neck.dist(points.shoulder) * options.shoulderStrapWidth - ) - points.strapRight = points.strapLeft.rotate(180, points.strapCenter) - points.necklineCorner = utils.beamsIntersect( - points.strapLeft, - points.strapRight.rotate(-90, points.strapLeft), - points.cfNeck.shift(0, points.armholePitch.x / 4), - points.cfNeck - ) - points.strapLeftCp2 = points.strapLeft.shiftFractionTowards( - points.necklineCorner, - options.necklineBend - ) - points.cfNeckCp1 = points.cfNeck.shiftFractionTowards(points.necklineCorner, options.necklineBend) - - // This will come in handy - store.set('armholeY', points.armhole.y * (1 + options.armholeDrop)) - - // Hips - points.hips.x = - ((measurements.hips + options.hipsEase * measurements.hips) / 4) * (1 - options.stretchFactor) - points.waist.x = points.hips.x // Because stretch - - points.hipsCp2 = new Point( - points.hips.x, - store.get('armholeY') + (points.hips.y - store.get('armholeY')) / 2 - ) - - // Hem - points.hem.x = points.hips.x - - // Armhole - points.armhole = utils.beamIntersectsY( - points.armhole, - points.hips, - points.armhole.y * (1 + options.armholeDrop) - ) - points.armholeCorner = utils.beamsIntersect( - points.armhole, - points.armhole.shift(180, 10), - points.strapRight, - points.strapLeft.rotate(90, points.strapRight) - ) - points.armholeCp2 = points.armhole.shiftFractionTowards(points.armholeCorner, 0.5) - points.strapRightCp1 = points.strapRight.shiftFractionTowards(points.armholeCorner, 0.5) - - points.anchor = points.cfNeck.clone() - - // Seamline - paths.seam = new Path() - .move(points.cfNeck) - .line(points.cfHem) - .line(points.hem) - .curve_(points.hipsCp2, points.armhole) - .curve(points.armholeCp2, points.strapRightCp1, points.strapRight) - .line(points.strapLeft) - .curve(points.strapLeftCp2, points.cfNeckCp1, points.cfNeck) - .close() - .attr('class', 'fabric') - - // Store length of armhole and neck opening - store.set( - 'frontArmholeLength', - new Path() - .move(points.armhole) - .curve(points.armholeCp2, points.strapRightCp1, points.strapRight) - .length() - ) - store.set( - 'frontNeckOpeningLength', - new Path() - .move(points.strapLeft) - .curve(points.cfNeckCp1, points.cfNeckCp1, points.cfNeck) - .length() - ) - - // Complete pattern? - if (complete) { - macro('cutonfold', { - from: points.cfNeck, - to: points.cfHem, - grainline: true, - }) - points.title = new Point(points.waist.x / 2, points.waist.y) - macro('title', { at: points.title, nr: 1, title: 'front' }) - points.logo = points.title.shift(-90, 75) - snippets.logo = new Snippet('logo', points.logo) - - if (sa) { - let saShoulder = new Path().move(points.strapRight).line(points.strapLeft).offset(sa) - paths.saShoulder = new Path() - .move(points.strapRight) - .line(saShoulder.start()) - .join(saShoulder) - .line(points.strapLeft) - .attr('class', 'fabric sa') - paths.sa = new Path() - .move(points.cfHem) - .line(points.cfHem) - .join( - new Path() - .move(points.cfHem) - .line(points.hem) - .offset(sa * 2.5) - ) - .join( - new Path() - .move(points.hem) - .curve_(points.waist, points.armhole) - .offset(sa) - .line(points.armhole) - ) - .attr('class', 'fabric sa') + // Clone cb (center back) into cf (center front) + for (let key of ['Neck', 'Shoulder', 'Armhole', 'Hips', 'Hem']) { + points[`cf${key}`] = points[`cb${key}`].clone() } - } - // Paperless? - if (paperless) { - dimensions(macro, points, sa) - macro('vd', { - from: points.cfHem, - to: points.cfNeck, - x: points.cfHem.x - sa - 15, - }) - } + // Neckline + points.cfNeck = points.cfNeck.shift(-90, options.necklineDrop * measurements.hpsToWaistBack) - return part + // Strap + points.strapCenter = points.neck.shiftFractionTowards( + points.shoulder, + options.shoulderStrapPlacement + ) + points.strapLeft = points.strapCenter.shiftTowards( + points.neck, + points.neck.dist(points.shoulder) * options.shoulderStrapWidth + ) + points.strapRight = points.strapLeft.rotate(180, points.strapCenter) + points.necklineCorner = utils.beamsIntersect( + points.strapLeft, + points.strapRight.rotate(-90, points.strapLeft), + points.cfNeck.shift(0, points.armholePitch.x / 4), + points.cfNeck + ) + points.strapLeftCp2 = points.strapLeft.shiftFractionTowards( + points.necklineCorner, + options.necklineBend + ) + points.cfNeckCp1 = points.cfNeck.shiftFractionTowards(points.necklineCorner, options.necklineBend) + + // This will come in handy + store.set('armholeY', points.armhole.y * (1 + options.armholeDrop)) + + // Hips + points.hips.x = + ((measurements.hips + options.hipsEase * measurements.hips) / 4) * (1 - options.stretchFactor) + points.waist.x = points.hips.x // Because stretch + + points.hipsCp2 = new Point( + points.hips.x, + store.get('armholeY') + (points.hips.y - store.get('armholeY')) / 2 + ) + + // Hem + points.hem.x = points.hips.x + + // Armhole + points.armhole = utils.beamIntersectsY( + points.armhole, + points.hips, + points.armhole.y * (1 + options.armholeDrop) + ) + points.armholeCorner = utils.beamsIntersect( + points.armhole, + points.armhole.shift(180, 10), + points.strapRight, + points.strapLeft.rotate(90, points.strapRight) + ) + points.armholeCp2 = points.armhole.shiftFractionTowards(points.armholeCorner, 0.5) + points.strapRightCp1 = points.strapRight.shiftFractionTowards(points.armholeCorner, 0.5) + + points.anchor = points.cfNeck.clone() + + // Seamline + paths.seam = new Path() + .move(points.cfNeck) + .line(points.cfHem) + .line(points.hem) + .curve_(points.hipsCp2, points.armhole) + .curve(points.armholeCp2, points.strapRightCp1, points.strapRight) + .line(points.strapLeft) + .curve(points.strapLeftCp2, points.cfNeckCp1, points.cfNeck) + .close() + .attr('class', 'fabric') + + // Store length of armhole and neck opening + store.set( + 'frontArmholeLength', + new Path() + .move(points.armhole) + .curve(points.armholeCp2, points.strapRightCp1, points.strapRight) + .length() + ) + store.set( + 'frontNeckOpeningLength', + new Path() + .move(points.strapLeft) + .curve(points.cfNeckCp1, points.cfNeckCp1, points.cfNeck) + .length() + ) + + // Complete pattern? + if (complete) { + macro('cutonfold', { + from: points.cfNeck, + to: points.cfHem, + grainline: true, + }) + points.title = new Point(points.waist.x / 2, points.waist.y) + macro('title', { at: points.title, nr: 1, title: 'front' }) + points.logo = points.title.shift(-90, 75) + snippets.logo = new Snippet('logo', points.logo) + + if (sa) { + let saShoulder = new Path().move(points.strapRight).line(points.strapLeft).offset(sa) + paths.saShoulder = new Path() + .move(points.strapRight) + .line(saShoulder.start()) + .join(saShoulder) + .line(points.strapLeft) + .attr('class', 'fabric sa') + paths.sa = new Path() + .move(points.cfHem) + .line(points.cfHem) + .join( + new Path() + .move(points.cfHem) + .line(points.hem) + .offset(sa * 2.5) + ) + .join( + new Path() + .move(points.hem) + .curve_(points.waist, points.armhole) + .offset(sa) + .line(points.armhole) + ) + .attr('class', 'fabric sa') + } + } + + // Paperless? + if (paperless) { + dimensions(macro, points, sa) + macro('vd', { + from: points.cfHem, + to: points.cfNeck, + x: points.cfHem.x - sa - 15, + }) + } + + return part + } } diff --git a/designs/aaron/src/index.js b/designs/aaron/src/index.js index ed1023c4cde..856f6454e01 100644 --- a/designs/aaron/src/index.js +++ b/designs/aaron/src/index.js @@ -1,38 +1,33 @@ +// FreeSewing core library import freesewing from '@freesewing/core' -import Brian from '@freesewing/brian' -import plugins from '@freesewing/plugin-bundle' -import plugin from '@freesewing/plugin-bust' // Note: conditional plugin -import config from '../config' +// FreeSewing Plugins +import pluginBundle from '@freesewing/plugin-bundle' +import bustPlugin from '@freesewing/plugin-bust' // Note: conditional plugin +// Design config & options +import { info, measurements, optionalMeasurements } from '../config/index' +import * as options from '../config/options' +// Design parts +import back from './back' +import front from './front' -// Parts -import draftBack from './back' -import draftFront from './front' - -/* Check to see whether we should load the bust plugin - * Only of the `draftForHighBust` options is set - * AND the highBust measurement is available - */ -const condition = (settings = false) => - settings && - settings.options && - settings.options.draftForHighBust && - settings.measurements.highBust - ? true - : false - -// Create design -const Aaron = new freesewing.Design(config, plugins, { plugin, condition }) - -// Attach draft methods to prototype -Aaron.prototype.draftBase = function (part) { - // Getting the base part from Brian - return new Brian(this.settings).draftBase(part) -} -Aaron.prototype.draftFront = (part) => draftFront(part) -Aaron.prototype.draftBack = (part) => draftBack(part) +// Setup design +const Aaron = new freesewing.Design({ + ...info, + measurements, + optionalMeasurements, + options: { ...options }, + parts: { back, front }, + plugins: pluginBundle, + conditionalPlugins: { + plugin: bustPlugin, + condition: (settings=false) => + settings?.options?.draftForHighBust && + settings?.measurements?.highBust + ? true : false + } +}) // Named exports -export { config, Aaron } - +export { front, back, Aaron } // Default export export default Aaron diff --git a/designs/brian/build.js b/designs/brian/build.js index 7248b245c69..ee51943e8cf 100644 --- a/designs/brian/build.js +++ b/designs/brian/build.js @@ -23,17 +23,22 @@ const options = { // Different formats const formats = { - cjs: "dist/index.js", - esm: "dist/index.mjs", + cjs: "js", + esm: "mjs", } // Let esbuild generate different formats let result (async () => { - for (const [format, outfile] of Object.entries(formats)) { + for (const [format, ext] of Object.entries(formats)) { + // Regular build result = await esbuild - .build({ ...options, outfile, format }) - .catch(() => process.exit(1)) + .build({ ...options, format, outfile: `dist/index.${ext}` }) + .catch(() => process.exit(1)) + // Config build + await esbuild + .build({ ...options, format, outfile: `dist/config.${ext}`, entryPoints: ['config/index.js'] }) + .catch(() => process.exit(1)) } if (process.env.VERBOSE) { @@ -41,11 +46,13 @@ let result console.log(info) } + // Also build a version that has all dependencies bundled // This makes it easy to run tests await esbuild .build({ ...options, + entryPoints: ['src/index.js'], minify: false, sourcemap: false, outfile: 'tests/dist/index.mjs', diff --git a/designs/brian/config/index.js b/designs/brian/config/index.js index 716831764d8..6d32a01100f 100644 --- a/designs/brian/config/index.js +++ b/designs/brian/config/index.js @@ -1,6 +1,6 @@ import { version } from '../package.json' -export default { +export const info = { version, name: 'brian', design: 'Joost De Cock', @@ -52,74 +52,19 @@ export default { }, ], }, - measurements: [ - 'biceps', - 'chest', - 'hpsToWaistBack', - 'waistToHips', - 'neck', - 'shoulderSlope', - 'shoulderToShoulder', - 'shoulderToWrist', - 'wrist', - ], - optionalMeasurements: ['highBust'], - dependencies: { - back: 'base', - front: 'back', - sleevecap: 'front', - sleeve: 'sleevecap', - }, - inject: { - back: 'base', - front: 'back', - sleeve: 'sleevecap', - }, - hide: ['base', 'sleevecap'], - options: { - // Constants - brianFitSleeve: true, - brianFitCollar: true, - collarFactor: 4.8, - - // Percentages - acrossBackFactor: { pct: 98, min: 93, max: 100 }, - armholeDepthFactor: { pct: 55, min: 50, max: 70 }, - backNeckCutout: { pct: 5, min: 2, max: 8 }, - bicepsEase: { pct: 15, min: 0, max: 50 }, - chestEase: { pct: 15, min: -4, max: 35 }, - collarEase: { pct: 5, min: 0, max: 10 }, - cuffEase: { pct: 20, min: 0, max: 200 }, - frontArmholeDeeper: { pct: 0.2, min: 0, max: 0.5 }, - lengthBonus: { pct: 0, min: -4, max: 60 }, - shoulderEase: { pct: 0, min: -2, max: 6 }, - shoulderSlopeReduction: { pct: 0, min: 0, max: 80 }, - // s3 is short for Shoulder Seam Shift - s3Collar: { pct: 0, min: -100, max: 100 }, - s3Armhole: { pct: 0, min: -100, max: 100 }, - sleevecapEase: { pct: 0, min: 0, max: 10 }, - sleevecapTopFactorX: { pct: 50, min: 25, max: 75 }, - sleevecapTopFactorY: { pct: 45, min: 35, max: 125 }, - sleevecapBackFactorX: { pct: 60, min: 35, max: 65 }, - sleevecapBackFactorY: { pct: 33, min: 30, max: 65 }, - sleevecapFrontFactorX: { pct: 55, min: 35, max: 65 }, - sleevecapFrontFactorY: { pct: 33, min: 30, max: 65 }, - sleevecapQ1Offset: { pct: 1.7, min: 0, max: 7 }, - sleevecapQ2Offset: { pct: 3.5, min: 0, max: 7 }, - sleevecapQ3Offset: { pct: 2.5, min: 0, max: 7 }, - sleevecapQ4Offset: { pct: 1, min: 0, max: 7 }, - sleevecapQ1Spread1: { pct: 10, min: 4, max: 20 }, - sleevecapQ1Spread2: { pct: 15, min: 4, max: 20 }, - sleevecapQ2Spread1: { pct: 15, min: 4, max: 20 }, - sleevecapQ2Spread2: { pct: 10, min: 4, max: 20 }, - sleevecapQ3Spread1: { pct: 10, min: 4, max: 20 }, - sleevecapQ3Spread2: { pct: 8, min: 4, max: 20 }, - sleevecapQ4Spread1: { pct: 7, min: 4, max: 20 }, - sleevecapQ4Spread2: { pct: 6.3, min: 4, max: 20 }, - sleeveWidthGuarantee: { pct: 90, min: 25, max: 100 }, - sleeveLengthBonus: { pct: 0, min: -40, max: 10 }, - - // draft for high bust - draftForHighBust: { bool: false }, - }, } + +export const measurements = [ + 'biceps', + 'chest', + 'hpsToWaistBack', + 'waistToHips', + 'neck', + 'shoulderSlope', + 'shoulderToShoulder', + 'shoulderToWrist', + 'wrist', +] + +export const optionalMeasurements = ['highBust'] + diff --git a/designs/brian/config/options.js b/designs/brian/config/options.js new file mode 100644 index 00000000000..88cb0ffe1db --- /dev/null +++ b/designs/brian/config/options.js @@ -0,0 +1,94 @@ +// Constants +export const brianFitSleeve = true +export const brianFitCollar = true +export const collarFactor = 4.8 + +// Percentages +export const acrossBackFactor = { pct: 98, min: 93, max: 100 } +export const armholeDepthFactor = { pct: 55, min: 50, max: 70 } +export const backNeckCutout = { pct: 5, min: 2, max: 8 } +export const bicepsEase = { pct: 15, min: 0, max: 50 } +export const chestEase = { pct: 15, min: -4, max: 35 } +export const collarEase = { pct: 5, min: 0, max: 10 } +export const cuffEase = { pct: 20, min: 0, max: 200 } +export const frontArmholeDeeper = { pct: 0.2, min: 0, max: 0.5 } +export const lengthBonus = { pct: 0, min: -4, max: 60 } +export const shoulderEase = { pct: 0, min: -2, max: 6 } +export const shoulderSlopeReduction = { pct: 0, min: 0, max: 80 } + +// s3 is short for Shoulder Seam Shift +export const s3Collar = { pct: 0, min: -100, max: 100 } +export const s3Armhole = { pct: 0, min: -100, max: 100 } + +// Sleevecap +export const sleevecapEase = { pct: 0, min: 0, max: 10 } +export const sleevecapTopFactorX = { pct: 50, min: 25, max: 75 } +export const sleevecapTopFactorY = { pct: 45, min: 35, max: 125 } +export const sleevecapBackFactorX = { pct: 60, min: 35, max: 65 } +export const sleevecapBackFactorY = { pct: 33, min: 30, max: 65 } +export const sleevecapFrontFactorX = { pct: 55, min: 35, max: 65 } +export const sleevecapFrontFactorY = { pct: 33, min: 30, max: 65 } +export const sleevecapQ1Offset = { pct: 1.7, min: 0, max: 7 } +export const sleevecapQ2Offset = { pct: 3.5, min: 0, max: 7 } +export const sleevecapQ3Offset = { pct: 2.5, min: 0, max: 7 } +export const sleevecapQ4Offset = { pct: 1, min: 0, max: 7 } +export const sleevecapQ1Spread1 = { pct: 10, min: 4, max: 20 } +export const sleevecapQ1Spread2 = { pct: 15, min: 4, max: 20 } +export const sleevecapQ2Spread1 = { pct: 15, min: 4, max: 20 } +export const sleevecapQ2Spread2 = { pct: 10, min: 4, max: 20 } +export const sleevecapQ3Spread1 = { pct: 10, min: 4, max: 20 } +export const sleevecapQ3Spread2 = { pct: 8, min: 4, max: 20 } +export const sleevecapQ4Spread1 = { pct: 7, min: 4, max: 20 } +export const sleevecapQ4Spread2 = { pct: 6.3, min: 4, max: 20 } +// Sleeve +export const sleeveWidthGuarantee = { pct: 90, min: 25, max: 100 } +export const sleeveLengthBonus = { pct: 0, min: -40, max: 10 } + +// Draft for high bust +export const draftForHighBust = { bool: false } + +// Helper objects for per-part options +export const _base = { + brianFitSleeve, + brianFitCollar, + collarFactor, + acrossBackFactor, + armholeDepthFactor, + backNeckCutout, + bicepsEase, + chestEase, + collarEase, + cuffEase, + frontArmholeDeeper, + lengthBonus, + shoulderEase, + shoulderSlopeReduction, + s3Collar, + s3Armhole, + draftForHighBust, +} +export const _sleevecap = { + sleevecapEase, + sleevecapTopFactorX, + sleevecapTopFactorY, + sleevecapBackFactorX, + sleevecapBackFactorY, + sleevecapFrontFactorX, + sleevecapFrontFactorY, + sleevecapQ1Offset, + sleevecapQ2Offset, + sleevecapQ3Offset, + sleevecapQ4Offset, + sleevecapQ1Spread1, + sleevecapQ1Spread2, + sleevecapQ2Spread1, + sleevecapQ2Spread2, + sleevecapQ3Spread1, + sleevecapQ3Spread2, + sleevecapQ4Spread1, + sleevecapQ4Spread2, + sleeveWidthGuarantee, +} +export const _sleeve = { sleeveLengthBonus } + + diff --git a/designs/brian/src/back.js b/designs/brian/src/back.js index a640aca822e..ce869337ab3 100644 --- a/designs/brian/src/back.js +++ b/designs/brian/src/back.js @@ -1,184 +1,189 @@ import * as shared from './shared' +import base from './base' -export default (part) => { - let { - store, - sa, - points, - Path, - paths, - Snippet, - snippets, - complete, - paperless, - macro, - options, - utils, - } = part.shorthand() +export default { + from: base, + name: 'back', + draft: (part) => { + const { + store, + sa, + points, + Path, + paths, + Snippet, + snippets, + complete, + paperless, + macro, + options, + utils, + } = part.shorthand() - points.anchor = points.hps.clone() + points.anchor = points.hps.clone() - // Adapt the shoulder seam according to the relevant options - // Note: s3 stands for Shoulder Seam Shift - // Don't bother with less than 10% as that's just asking for trouble - if (options.s3Collar < 0.1 && options.s3Collar > -0.1) { - points.s3CollarSplit = points.hps - paths.backCollar = new Path() - .move(points.hps) - .curve_(points.neckCp2, points.cbNeck) - .setRender(false) - } else if (options.s3Collar > 0) { - // Shift shoulder seam forward on the collar side - points.s3CollarSplit = utils.curveIntersectsY( - points.hps, - points.mirroredNeckCp2Front, - points.mirroredCfNeckCp1, - points.mirroredCfNeck, - store.get('s3CollarMaxFront') * -1 * options.s3Collar - ) - paths.backCollar = new Path() - .move(points.hps) - ._curve(points.mirroredNeckCp2Front, points.mirroredCfNeckCp1, points.mirroredCfNeck) - .split(points.s3CollarSplit)[0] - .reverse() - .join(new Path().move(points.hps).curve_(points.neckCp2, points.cbNeck)) - .setRender(false) - } else if (options.s3Collar < 0) { - // Shift shoulder seam backward on the collar side - points.s3CollarSplit = utils.curveIntersectsY( - points.hps, - points.neckCp2, - points.cbNeck, - points.cbNeck, - store.get('s3CollarMaxBack') * -1 * options.s3Collar - ) - paths.backCollar = new Path() - .move(points.cbNeck) - ._curve(points.neckCp2, points.neck) - .split(points.s3CollarSplit)[0] - .reverse() - .setRender(false) - } - // Don't bother with less than 10% as that's just asking for trouble - if (options.s3Armhole < 0.1 && options.s3Armhole > -0.1) { - points.s3ArmholeSplit = points.shoulder - paths.backArmhole = new Path() - .move(points.armholePitch) - .curve(points.armholePitchCp2, points.shoulderCp1, points.shoulder) - .setRender(false) - } else if (options.s3Armhole > 0) { - // Shift shoulder seam forward on the armhole side - points.s3ArmholeSplit = utils.curveIntersectsY( - points.shoulder, - points.mirroredShoulderCp1, - points.mirroredFrontArmholePitchCp2, - points.mirroredFrontArmholePitch, - store.get('s3ArmholeMax') * -1 * options.s3Armhole + points.shoulder.y - ) - paths.backArmhole = new Path() - .move(points.armholePitch) - .curve(points.armholePitchCp2, points.shoulderCp1, points.shoulder) - .join( - new Path() - .move(points.shoulder) - .curve( - points.mirroredShoulderCp1, - points.mirroredFrontArmholePitchCp2, - points.mirroredFrontArmholePitch - ) - .split(points.s3ArmholeSplit)[0] + // Adapt the shoulder seam according to the relevant options + // Note: s3 stands for Shoulder Seam Shift + // Don't bother with less than 10% as that's just asking for trouble + if (options.s3Collar < 0.1 && options.s3Collar > -0.1) { + points.s3CollarSplit = points.hps + paths.backCollar = new Path() + .move(points.hps) + .curve_(points.neckCp2, points.cbNeck) + .setRender(false) + } else if (options.s3Collar > 0) { + // Shift shoulder seam forward on the collar side + points.s3CollarSplit = utils.curveIntersectsY( + points.hps, + points.mirroredNeckCp2Front, + points.mirroredCfNeckCp1, + points.mirroredCfNeck, + store.get('s3CollarMaxFront') * -1 * options.s3Collar ) - .setRender(false) - } else if (options.s3Armhole < 0) { - // Shift shoulder seam backward on the armhole side - points.s3ArmholeSplit = utils.curveIntersectsY( - points.shoulder, - points.shoulderCp1, - points.armholePitchCp2, - points.armholePitch, - store.get('s3ArmholeMax') * -1 * options.s3Armhole + points.shoulder.y - ) - paths.backArmhole = new Path() - .move(points.armholePitch) - .curve(points.armholePitchCp2, points.shoulderCp1, points.shoulder) - .split(points.s3ArmholeSplit)[0] - .setRender(false) - } - - // Seamline - paths.saBase = new Path() - .move(points.cbHem) - .line(points.hem) - .line(points.armhole) - .curve(points.armholeCp2, points.armholeHollowCp1, points.armholeHollow) - .curve(points.armholeHollowCp2, points.armholePitchCp1, points.armholePitch) - .join(paths.backArmhole) - .line(points.s3CollarSplit) - .join(paths.backCollar) - .setRender(false) - paths.seam = new Path() - .move(points.cbNeck) - .line(points.cbHips) - .join(paths.saBase) - .attr('class', 'fabric') - - // Store lengths to fit sleeve - store.set('backArmholeLength', shared.armholeLength(points, Path)) - store.set('backArmholeToArmholePitch', shared.armholeToArmholePitch(points, Path)) - - // Complete pattern? - if (complete) { - macro('cutonfold', { - from: points.cbNeck, - to: points.cbHips, - grainline: true, - }) - - macro('title', { at: points.title, nr: 2, title: 'back' }) - snippets.armholePitchNotch = new Snippet('bnotch', points.armholePitch) - paths.waist = new Path().move(points.cbWaist).line(points.waist).attr('class', 'help') - if (sa) { - paths.sa = paths.saBase - .offset(sa) - .attr('class', 'fabric sa') - .line(points.cbNeck) - .move(points.cbHips) - paths.sa.line(paths.sa.start()) + paths.backCollar = new Path() + .move(points.hps) + ._curve(points.mirroredNeckCp2Front, points.mirroredCfNeckCp1, points.mirroredCfNeck) + .split(points.s3CollarSplit)[0] + .reverse() + .join(new Path().move(points.hps).curve_(points.neckCp2, points.cbNeck)) + .setRender(false) + } else if (options.s3Collar < 0) { + // Shift shoulder seam backward on the collar side + points.s3CollarSplit = utils.curveIntersectsY( + points.hps, + points.neckCp2, + points.cbNeck, + points.cbNeck, + store.get('s3CollarMaxBack') * -1 * options.s3Collar + ) + paths.backCollar = new Path() + .move(points.cbNeck) + ._curve(points.neckCp2, points.neck) + .split(points.s3CollarSplit)[0] + .reverse() + .setRender(false) + } + // Don't bother with less than 10% as that's just asking for trouble + if (options.s3Armhole < 0.1 && options.s3Armhole > -0.1) { + points.s3ArmholeSplit = points.shoulder + paths.backArmhole = new Path() + .move(points.armholePitch) + .curve(points.armholePitchCp2, points.shoulderCp1, points.shoulder) + .setRender(false) + } else if (options.s3Armhole > 0) { + // Shift shoulder seam forward on the armhole side + points.s3ArmholeSplit = utils.curveIntersectsY( + points.shoulder, + points.mirroredShoulderCp1, + points.mirroredFrontArmholePitchCp2, + points.mirroredFrontArmholePitch, + store.get('s3ArmholeMax') * -1 * options.s3Armhole + points.shoulder.y + ) + paths.backArmhole = new Path() + .move(points.armholePitch) + .curve(points.armholePitchCp2, points.shoulderCp1, points.shoulder) + .join( + new Path() + .move(points.shoulder) + .curve( + points.mirroredShoulderCp1, + points.mirroredFrontArmholePitchCp2, + points.mirroredFrontArmholePitch + ) + .split(points.s3ArmholeSplit)[0] + ) + .setRender(false) + } else if (options.s3Armhole < 0) { + // Shift shoulder seam backward on the armhole side + points.s3ArmholeSplit = utils.curveIntersectsY( + points.shoulder, + points.shoulderCp1, + points.armholePitchCp2, + points.armholePitch, + store.get('s3ArmholeMax') * -1 * options.s3Armhole + points.shoulder.y + ) + paths.backArmhole = new Path() + .move(points.armholePitch) + .curve(points.armholePitchCp2, points.shoulderCp1, points.shoulder) + .split(points.s3ArmholeSplit)[0] + .setRender(false) } - // Add notches if the shoulder seam is shifted - shared.s3Notches(part, 'bnotch') - } + // Seamline + paths.saBase = new Path() + .move(points.cbHem) + .line(points.hem) + .line(points.armhole) + .curve(points.armholeCp2, points.armholeHollowCp1, points.armholeHollow) + .curve(points.armholeHollowCp2, points.armholePitchCp1, points.armholePitch) + .join(paths.backArmhole) + .line(points.s3CollarSplit) + .join(paths.backCollar) + .setRender(false) + paths.seam = new Path() + .move(points.cbNeck) + .line(points.cbHips) + .join(paths.saBase) + .attr('class', 'fabric') - // Paperless? - if (paperless) { - shared.dimensions(part, 'back') - macro('hd', { - from: points.cbHips, - to: points.hips, - y: points.hem.y + sa + 15, - }) - macro('vd', { - from: points.cbHem, - to: points.cbWaist, - x: points.cbHips.x - sa - 15, - }) - macro('vd', { - from: points.cbHem, - to: points.cbNeck, - x: points.cbHips.x - sa - 30, - }) - macro('hd', { - from: points.cbNeck, - to: points.s3CollarSplit, - y: points.s3CollarSplit.y - sa - 15, - }) - macro('hd', { - from: points.cbNeck, - to: points.s3ArmholeSplit, - y: points.s3CollarSplit.y - sa - 30, - }) - } + // Store lengths to fit sleeve + store.set('backArmholeLength', shared.armholeLength(points, Path)) + store.set('backArmholeToArmholePitch', shared.armholeToArmholePitch(points, Path)) - return part + // Complete pattern? + if (complete) { + macro('cutonfold', { + from: points.cbNeck, + to: points.cbHips, + grainline: true, + }) + + macro('title', { at: points.title, nr: 2, title: 'back' }) + snippets.armholePitchNotch = new Snippet('bnotch', points.armholePitch) + paths.waist = new Path().move(points.cbWaist).line(points.waist).attr('class', 'help') + if (sa) { + paths.sa = paths.saBase + .offset(sa) + .attr('class', 'fabric sa') + .line(points.cbNeck) + .move(points.cbHips) + paths.sa.line(paths.sa.start()) + } + + // Add notches if the shoulder seam is shifted + shared.s3Notches(part, 'bnotch') + } + + // Paperless? + if (paperless) { + shared.dimensions(part, 'back') + macro('hd', { + from: points.cbHips, + to: points.hips, + y: points.hem.y + sa + 15, + }) + macro('vd', { + from: points.cbHem, + to: points.cbWaist, + x: points.cbHips.x - sa - 15, + }) + macro('vd', { + from: points.cbHem, + to: points.cbNeck, + x: points.cbHips.x - sa - 30, + }) + macro('hd', { + from: points.cbNeck, + to: points.s3CollarSplit, + y: points.s3CollarSplit.y - sa - 15, + }) + macro('hd', { + from: points.cbNeck, + to: points.s3ArmholeSplit, + y: points.s3CollarSplit.y - sa - 30, + }) + } + + return part + } } diff --git a/designs/brian/src/base.js b/designs/brian/src/base.js index 69286767c23..3e3eb627834 100644 --- a/designs/brian/src/base.js +++ b/designs/brian/src/base.js @@ -1,173 +1,181 @@ -export default (part) => { - let { - measurements, - options, - store, - points, - snippets, - Point, - Snippet, - Path, - paths, - utils, - complete, - macro, - } = part.shorthand() +import { _base as options } from '../config/options.js' - store.set('shoulderEase', (measurements.shoulderToShoulder * options.shoulderEase) / 2) - // Center back (cb) vertical axis - points.cbHps = new Point(0, 0) - points.cbNeck = new Point(0, options.backNeckCutout * measurements.neck) - points.cbWaist = new Point(0, measurements.hpsToWaistBack) - points.cbHips = new Point(0, points.cbWaist.y + measurements.waistToHips) +export default { + name: 'base', + hide: true, + options, + draft: (part) => { + const { + measurements, + options, + store, + points, + snippets, + Point, + Snippet, + Path, + paths, + utils, + complete, + macro, + } = part.shorthand() - // Shoulder line - points.neck = new Point((measurements.neck * (1 + options.collarEase)) / options.collarFactor, 0) - points.hps = points.neck.clone() // We started using HPS in many measurements - // Shoulder point using shoulderSlope degree measurement - points.shoulder = utils.beamsIntersect( - points.hps, - points.hps.shift(measurements.shoulderSlope * -1, 100), - new Point(measurements.shoulderToShoulder / 2 + store.get('shoulderEase'), -100), - new Point(measurements.shoulderToShoulder / 2 + store.get('shoulderEase'), 100) - ) - // Determine armhole depth and cbShoulder independent of shoulder slope reduction - points.cbShoulder = new Point(0, points.shoulder.y) - points.cbArmhole = new Point( - 0, - points.shoulder.y + measurements.biceps * (1 + options.bicepsEase) * options.armholeDepthFactor - ) + store.set('shoulderEase', (measurements.shoulderToShoulder * options.shoulderEase) / 2) - // Now take shoulder slope reduction into account - points.shoulder.y -= (points.shoulder.y - points.cbHps.y) * options.shoulderSlopeReduction - // Shoulder should never be higher than HPS - if (points.shoulder.y < points.cbHps.y) points.shoulder = new Point(points.shoulder.x, 0) + // Center back (cb) vertical axis + points.cbHps = new Point(0, 0) + points.cbNeck = new Point(0, options.backNeckCutout * measurements.neck) + points.cbWaist = new Point(0, measurements.hpsToWaistBack) + points.cbHips = new Point(0, points.cbWaist.y + measurements.waistToHips) - points.cbHem = new Point(0, points.cbHips.y * (1 + options.lengthBonus)) - - // Side back (cb) vertical axis - points.armhole = new Point((measurements.chest * (1 + options.chestEase)) / 4, points.cbArmhole.y) - points.waist = new Point(points.armhole.x, points.cbWaist.y) - points.hips = new Point(points.armhole.x, points.cbHips.y) - points.hem = new Point(points.armhole.x, points.cbHem.y) - - // Armhhole - points.armholePitch = new Point( - (measurements.shoulderToShoulder * options.acrossBackFactor) / 2 + - store.get('shoulderEase') / 2, - points.shoulder.y + points.shoulder.dy(points.armhole) / 2 - ) - // Set both an front and back armhole pitch point - // but keep 'armholePitch' for backwards compatibility - points.backArmholePitch = points.armholePitch.clone() - points.frontArmholePitch = points.armholePitch.clone() // will be overwritten below - // Armhole hollow - points._tmp1 = new Point(points.armholePitch.x, points.armhole.y) - points._tmp2 = points._tmp1.shift(45, 10) - points._tmp3 = utils.beamsIntersect( - points._tmp1, - points._tmp2, - points.armhole, - points.armholePitch - ) - points.armholeHollow = points._tmp1.shiftFractionTowards(points._tmp3, 0.5) - points.armholeCp2 = points.armhole.shift(180, points._tmp1.dx(points.armhole) / 4) - points.armholeHollowCp1 = points.armholeHollow.shift( - -45, - points.armholeHollow.dy(points.armhole) / 2 - ) - points.armholeHollowCp2 = points.armholeHollow.shift( - 135, - points.armholePitch.dx(points.armholeHollow) - ) - points.armholePitchCp1 = points.armholePitch.shift( - -90, - points.armholePitch.dy(points.armholeHollow) / 2 - ) - points.backArmholePitchCp1 = points.armholePitchCp1.clone() - points.frontArmholePitchCp1 = points.armholePitchCp1.clone() // will be overwritten below - points.armholePitchCp2 = points.armholePitch.shift( - 90, - points.shoulder.dy(points.armholePitch) / 2 - ) - points.backArmholePitchCp2 = points.armholePitchCp2.clone() - points.frontArmholePitchCp2 = points.armholePitchCp2.clone() // will be overwritten below - points.shoulderCp1 = points.shoulder - .shiftTowards(points.neck, points.shoulder.dy(points.armholePitch) / 5) - .rotate(90, points.shoulder) - - // Neck opening (back) - points._tmp4 = points.neck.shiftTowards(points.shoulder, 10).rotate(-90, points.neck) - points.neckCp2 = utils.beamIntersectsY(points.neck, points._tmp4, points.cbNeck.y) - - // Fit collar - points.cfNeck = points.neck.rotate(-90, new Point(0, 0)) - let target = measurements.neck * (1 + options.collarEase) - let delta = 0 - let run = 0 - do { - run++ - points.cfNeck = points.cfNeck.shift(90, delta / 3) - points.frontNeckCpEdge = utils.beamsIntersect( - points.neck, - points.neckCp2, - points.cfNeck, - new Point(20, points.cfNeck.y) + // Shoulder line + points.neck = new Point((measurements.neck * (1 + options.collarEase)) / options.collarFactor, 0) + points.hps = points.neck.clone() // We started using HPS in many measurements + // Shoulder point using shoulderSlope degree measurement + points.shoulder = utils.beamsIntersect( + points.hps, + points.hps.shift(measurements.shoulderSlope * -1, 100), + new Point(measurements.shoulderToShoulder / 2 + store.get('shoulderEase'), -100), + new Point(measurements.shoulderToShoulder / 2 + store.get('shoulderEase'), 100) + ) + // Determine armhole depth and cbShoulder independent of shoulder slope reduction + points.cbShoulder = new Point(0, points.shoulder.y) + points.cbArmhole = new Point( + 0, + points.shoulder.y + measurements.biceps * (1 + options.bicepsEase) * options.armholeDepthFactor ) - points.cfNeckCp1 = points.cfNeck.shiftFractionTowards(points.frontNeckCpEdge, 0.55) - points.neckCp2Front = points.neck.shiftFractionTowards(points.frontNeckCpEdge, 0.65) - paths.neckOpening = new Path() - .move(points.cfNeck) - .curve(points.cfNeckCp1, points.neckCp2Front, points.neck) - .curve(points.neckCp2, points.cbNeck, points.cbNeck) - .attr('class', 'dashed stroke-xl various') - delta = paths.neckOpening.length() * 2 - target - } while (Math.abs(delta) > 1 && options.brianFitCollar && run < 10) - delete paths.neckOpening - // Anchor point for sampling - points.gridAnchor = points.cbHem + // Now take shoulder slope reduction into account + points.shoulder.y -= (points.shoulder.y - points.cbHps.y) * options.shoulderSlopeReduction + // Shoulder should never be higher than HPS + if (points.shoulder.y < points.cbHps.y) points.shoulder = new Point(points.shoulder.x, 0) - /* - * People would like to have the option to shift the shoulder seam - * See https://github.com/freesewing/freesewing/issues/642 - * So let's make the people happy - */ - // Front armhole is a bit deeper, add those points - let deeper = measurements.chest * options.frontArmholeDeeper - for (const p of ['', 'Cp1', 'Cp2']) { - points[`frontArmholePitch${p}`] = points[`armholePitch${p}`].shift(180, deeper) + points.cbHem = new Point(0, points.cbHips.y * (1 + options.lengthBonus)) + + // Side back (cb) vertical axis + points.armhole = new Point((measurements.chest * (1 + options.chestEase)) / 4, points.cbArmhole.y) + points.waist = new Point(points.armhole.x, points.cbWaist.y) + points.hips = new Point(points.armhole.x, points.cbHips.y) + points.hem = new Point(points.armhole.x, points.cbHem.y) + + // Armhhole + points.armholePitch = new Point( + (measurements.shoulderToShoulder * options.acrossBackFactor) / 2 + + store.get('shoulderEase') / 2, + points.shoulder.y + points.shoulder.dy(points.armhole) / 2 + ) + // Set both an front and back armhole pitch point + // but keep 'armholePitch' for backwards compatibility + points.backArmholePitch = points.armholePitch.clone() + points.frontArmholePitch = points.armholePitch.clone() // will be overwritten below + // Armhole hollow + points._tmp1 = new Point(points.armholePitch.x, points.armhole.y) + points._tmp2 = points._tmp1.shift(45, 10) + points._tmp3 = utils.beamsIntersect( + points._tmp1, + points._tmp2, + points.armhole, + points.armholePitch + ) + points.armholeHollow = points._tmp1.shiftFractionTowards(points._tmp3, 0.5) + points.armholeCp2 = points.armhole.shift(180, points._tmp1.dx(points.armhole) / 4) + points.armholeHollowCp1 = points.armholeHollow.shift( + -45, + points.armholeHollow.dy(points.armhole) / 2 + ) + points.armholeHollowCp2 = points.armholeHollow.shift( + 135, + points.armholePitch.dx(points.armholeHollow) + ) + points.armholePitchCp1 = points.armholePitch.shift( + -90, + points.armholePitch.dy(points.armholeHollow) / 2 + ) + points.backArmholePitchCp1 = points.armholePitchCp1.clone() + points.frontArmholePitchCp1 = points.armholePitchCp1.clone() // will be overwritten below + points.armholePitchCp2 = points.armholePitch.shift( + 90, + points.shoulder.dy(points.armholePitch) / 2 + ) + points.backArmholePitchCp2 = points.armholePitchCp2.clone() + points.frontArmholePitchCp2 = points.armholePitchCp2.clone() // will be overwritten below + points.shoulderCp1 = points.shoulder + .shiftTowards(points.neck, points.shoulder.dy(points.armholePitch) / 5) + .rotate(90, points.shoulder) + + // Neck opening (back) + points._tmp4 = points.neck.shiftTowards(points.shoulder, 10).rotate(-90, points.neck) + points.neckCp2 = utils.beamIntersectsY(points.neck, points._tmp4, points.cbNeck.y) + + // Fit collar + points.cfNeck = points.neck.rotate(-90, new Point(0, 0)) + let target = measurements.neck * (1 + options.collarEase) + let delta = 0 + let run = 0 + do { + run++ + points.cfNeck = points.cfNeck.shift(90, delta / 3) + points.frontNeckCpEdge = utils.beamsIntersect( + points.neck, + points.neckCp2, + points.cfNeck, + new Point(20, points.cfNeck.y) + ) + points.cfNeckCp1 = points.cfNeck.shiftFractionTowards(points.frontNeckCpEdge, 0.55) + points.neckCp2Front = points.neck.shiftFractionTowards(points.frontNeckCpEdge, 0.65) + paths.neckOpening = new Path() + .move(points.cfNeck) + .curve(points.cfNeckCp1, points.neckCp2Front, points.neck) + .curve(points.neckCp2, points.cbNeck, points.cbNeck) + .attr('class', 'dashed stroke-xl various') + delta = paths.neckOpening.length() * 2 - target + } while (Math.abs(delta) > 1 && options.brianFitCollar && run < 10) + delete paths.neckOpening + + // Anchor point for sampling + points.gridAnchor = points.cbHem + + /* + * People would like to have the option to shift the shoulder seam + * See https://github.com/freesewing/freesewing/issues/642 + * So let's make the people happy + */ + // Front armhole is a bit deeper, add those points + let deeper = measurements.chest * options.frontArmholeDeeper + for (const p of ['', 'Cp1', 'Cp2']) { + points[`frontArmholePitch${p}`] = points[`armholePitch${p}`].shift(180, deeper) + } + // Add points needed for the mirrored front&back neck/armhole path + macro('mirror', { + mirror: [points.hps, points.shoulder], + points: [ + points.neckCp2Front, + points.cfNeckCp1, + points.cfNeck, + points.cbNeck, + points.neckCp2, + points.frontArmholePitch, + points.frontArmholePitchCp2, + points.shoulderCp1, + ], + clone: true, + }) + + // How much space do we have to work with here? + // s3 = ShoulderSeamShift + store.set('s3CollarMaxFront', points.hps.dy(points.cfNeck) / 2) + store.set('s3CollarMaxBack', points.hps.dy(points.cbNeck) / 2) + store.set('s3ArmholeMax', points.shoulder.dy(points.frontArmholePitch) / 4) + // Let's leave the actual splitting the curves for the front/back parts + + // Complete pattern? + if (complete) { + points.title = new Point(points.armholePitch.x / 2, points.armholePitch.y) + points.logo = points.title.shift(-90, 100) + snippets.logo = new Snippet('logo', points.logo) + } + + return part } - // Add points needed for the mirrored front&back neck/armhole path - macro('mirror', { - mirror: [points.hps, points.shoulder], - points: [ - points.neckCp2Front, - points.cfNeckCp1, - points.cfNeck, - points.cbNeck, - points.neckCp2, - points.frontArmholePitch, - points.frontArmholePitchCp2, - points.shoulderCp1, - ], - clone: true, - }) - - // How much space do we have to work with here? - // s3 = ShoulderSeamShift - store.set('s3CollarMaxFront', points.hps.dy(points.cfNeck) / 2) - store.set('s3CollarMaxBack', points.hps.dy(points.cbNeck) / 2) - store.set('s3ArmholeMax', points.shoulder.dy(points.frontArmholePitch) / 4) - // Let's leave the actual splitting the curves for the front/back parts - - // Complete pattern? - if (complete) { - points.title = new Point(points.armholePitch.x / 2, points.armholePitch.y) - points.logo = points.title.shift(-90, 100) - snippets.logo = new Snippet('logo', points.logo) - } - - return part } diff --git a/designs/brian/src/front.js b/designs/brian/src/front.js index 1d6a47681a2..e3697bd5c82 100644 --- a/designs/brian/src/front.js +++ b/designs/brian/src/front.js @@ -1,193 +1,198 @@ import * as shared from './shared' +import back from './back' -export default (part) => { - let { - store, - sa, - Point, - points, - Path, - paths, - Snippet, - snippets, - options, - complete, - paperless, - macro, - utils, - } = part.shorthand() +export default { + from: back, + name: 'front', + draft: (part) => { + const { + store, + sa, + Point, + points, + Path, + paths, + Snippet, + snippets, + options, + complete, + paperless, + macro, + utils, + } = part.shorthand() - // Re-use points for deeper armhole at the front - points.armholePitchCp1 = points.frontArmholePitchCp1 - points.armholePitch = points.frontArmholePitch - points.armholePitchCp2 = points.frontArmholePitchCp2 + // Re-use points for deeper armhole at the front + points.armholePitchCp1 = points.frontArmholePitchCp1 + points.armholePitch = points.frontArmholePitch + points.armholePitchCp2 = points.frontArmholePitchCp2 - // Adapt the shoulder line according to the relevant options - // Don't bother with less than 10% as that's just asking for trouble - if (options.s3Collar < 0.1 && options.s3Collar > -0.1) { - points.s3CollarSplit = points.hps - paths.frontCollar = new Path() - .move(points.hps) - .curve(points.neckCp2Front, points.cfNeckCp1, points.cfNeck) - .setRender(false) - } else if (options.s3Collar > 0) { - // Shift shoulder seam forward on the collar side - points.s3CollarSplit = utils.curveIntersectsY( - points.hps, - points.neckCp2Front, - points.cfNeckCp1, - points.cfNeck, - store.get('s3CollarMaxFront') * options.s3Collar - ) - paths.frontCollar = new Path() - .move(points.hps) - .curve(points.neckCp2Front, points.cfNeckCp1, points.cfNeck) - .split(points.s3CollarSplit)[1] - .setRender(false) - } else if (options.s3Collar < 0) { - // Shift shoulder seam backward on the collar side - points.s3CollarSplit = utils.curveIntersectsY( - points.mirroredCbNeck, - points.mirroredCbNeck, - points.mirroredNeckCp2, - points.hps, - store.get('s3CollarMaxBack') * options.s3Collar - ) - paths.frontCollar = new Path() - .move(points.hps) - .curve_(points.mirroredNeckCp2, points.mirroredCbNeck) - .split(points.s3CollarSplit)[0] - .reverse() - .join(new Path().move(points.hps).curve(points.neckCp2Front, points.cfNeckCp1, points.cfNeck)) - .setRender(false) - } - if (options.s3Armhole < 0.1 && options.s3Armhole > -0.1) { - points.s3ArmholeSplit = points.shoulder - paths.frontArmhole = new Path() - .move(points.armholePitch) - .curve(points.armholePitchCp2, points.shoulderCp1, points.shoulder) - .setRender(false) - } else if (options.s3Armhole > 0) { - // Shift shoulder seam forward on the armhole side - points.s3ArmholeSplit = utils.curveIntersectsY( - points.shoulder, - points.shoulderCp1, - points.armholePitchCp2, - points.armholePitch, - store.get('s3ArmholeMax') * options.s3Armhole + points.shoulder.y - ) - paths.frontArmhole = new Path() - .move(points.armholePitch) - .curve(points.armholePitchCp2, points.shoulderCp1, points.shoulder) - .split(points.s3ArmholeSplit)[0] - .setRender(false) - } else if (options.s3Armhole < 0) { - // Shift shoulder seam forward on the armhole side - points.s3ArmholeSplit = utils.curveIntersectsY( - points.shoulder, - points.mirroredShoulderCp1, - points.mirroredFrontArmholePitchCp2, - points.mirroredFrontArmholePitch, - store.get('s3ArmholeMax') * options.s3Armhole + points.shoulder.y - ) - paths.frontArmhole = new Path() - .move(points.armholePitch) - .curve(points.armholePitchCp2, points.shoulderCp1, points.shoulder) - .join( - new Path() - .move(points.shoulder) - .curve( - points.mirroredShoulderCp1, - points.mirroredFrontArmholePitchCp2, - points.mirroredFrontArmholePitch - ) - .split(points.s3ArmholeSplit)[0] + // Adapt the shoulder line according to the relevant options + // Don't bother with less than 10% as that's just asking for trouble + if (options.s3Collar < 0.1 && options.s3Collar > -0.1) { + points.s3CollarSplit = points.hps + paths.frontCollar = new Path() + .move(points.hps) + .curve(points.neckCp2Front, points.cfNeckCp1, points.cfNeck) + .setRender(false) + } else if (options.s3Collar > 0) { + // Shift shoulder seam forward on the collar side + points.s3CollarSplit = utils.curveIntersectsY( + points.hps, + points.neckCp2Front, + points.cfNeckCp1, + points.cfNeck, + store.get('s3CollarMaxFront') * options.s3Collar ) - .setRender(false) - } - - // Rename cb (center back) to cf (center front) - for (let key of ['Shoulder', 'Armhole', 'Waist', 'Hips', 'Hem']) { - points[`cf${key}`] = new Point(points[`cb${key}`].x, points[`cb${key}`].y) - delete points[`cb${key}`] - } - // Front neckline points - points.neckCp2 = new Point(points.neckCp2Front.x, points.neckCp2Front.y) - - // Seamline - paths.saBase = new Path() - .move(points.cfHem) - .line(points.hem) - .line(points.armhole) - .curve(points.armholeCp2, points.armholeHollowCp1, points.armholeHollow) - .curve(points.armholeHollowCp2, points.armholePitchCp1, points.armholePitch) - .join(paths.frontArmhole) - .line(points.s3CollarSplit) - .join(paths.frontCollar) - - paths.saBase.render = false - paths.seam = new Path() - .move(points.cfNeck) - .line(points.cfHem) - .join(paths.saBase) - .attr('class', 'fabric') - - // Store lengths to fit sleeve - store.set('frontArmholeLength', shared.armholeLength(points, Path)) - store.set('frontArmholeToArmholePitch', shared.armholeToArmholePitch(points, Path)) - - // Complete pattern? - if (complete) { - macro('cutonfold', { - from: points.cfNeck, - to: points.cfHips, - grainline: true, - }) - macro('title', { at: points.title, nr: 1, title: 'front' }) - snippets.armholePitchNotch = new Snippet('notch', points.armholePitch) - paths.waist = new Path().move(points.cfWaist).line(points.waist).attr('class', 'help') - if (sa) { - paths.sa = paths.saBase - .offset(sa) - .attr('class', 'fabric sa') - .line(points.cfNeck) - .move(points.cfHips) - paths.sa.line(paths.sa.start()) + paths.frontCollar = new Path() + .move(points.hps) + .curve(points.neckCp2Front, points.cfNeckCp1, points.cfNeck) + .split(points.s3CollarSplit)[1] + .setRender(false) + } else if (options.s3Collar < 0) { + // Shift shoulder seam backward on the collar side + points.s3CollarSplit = utils.curveIntersectsY( + points.mirroredCbNeck, + points.mirroredCbNeck, + points.mirroredNeckCp2, + points.hps, + store.get('s3CollarMaxBack') * options.s3Collar + ) + paths.frontCollar = new Path() + .move(points.hps) + .curve_(points.mirroredNeckCp2, points.mirroredCbNeck) + .split(points.s3CollarSplit)[0] + .reverse() + .join(new Path().move(points.hps).curve(points.neckCp2Front, points.cfNeckCp1, points.cfNeck)) + .setRender(false) + } + if (options.s3Armhole < 0.1 && options.s3Armhole > -0.1) { + points.s3ArmholeSplit = points.shoulder + paths.frontArmhole = new Path() + .move(points.armholePitch) + .curve(points.armholePitchCp2, points.shoulderCp1, points.shoulder) + .setRender(false) + } else if (options.s3Armhole > 0) { + // Shift shoulder seam forward on the armhole side + points.s3ArmholeSplit = utils.curveIntersectsY( + points.shoulder, + points.shoulderCp1, + points.armholePitchCp2, + points.armholePitch, + store.get('s3ArmholeMax') * options.s3Armhole + points.shoulder.y + ) + paths.frontArmhole = new Path() + .move(points.armholePitch) + .curve(points.armholePitchCp2, points.shoulderCp1, points.shoulder) + .split(points.s3ArmholeSplit)[0] + .setRender(false) + } else if (options.s3Armhole < 0) { + // Shift shoulder seam forward on the armhole side + points.s3ArmholeSplit = utils.curveIntersectsY( + points.shoulder, + points.mirroredShoulderCp1, + points.mirroredFrontArmholePitchCp2, + points.mirroredFrontArmholePitch, + store.get('s3ArmholeMax') * options.s3Armhole + points.shoulder.y + ) + paths.frontArmhole = new Path() + .move(points.armholePitch) + .curve(points.armholePitchCp2, points.shoulderCp1, points.shoulder) + .join( + new Path() + .move(points.shoulder) + .curve( + points.mirroredShoulderCp1, + points.mirroredFrontArmholePitchCp2, + points.mirroredFrontArmholePitch + ) + .split(points.s3ArmholeSplit)[0] + ) + .setRender(false) } - // Add notches if the shoulder seam is shifted - shared.s3Notches(part, 'notch') - } + // Rename cb (center back) to cf (center front) + for (let key of ['Shoulder', 'Armhole', 'Waist', 'Hips', 'Hem']) { + points[`cf${key}`] = new Point(points[`cb${key}`].x, points[`cb${key}`].y) + delete points[`cb${key}`] + } + // Front neckline points + points.neckCp2 = new Point(points.neckCp2Front.x, points.neckCp2Front.y) - // Paperless? - if (paperless) { - shared.dimensions(part, 'front') - macro('hd', { - from: points.cfHips, - to: points.hips, - y: points.hem.y + sa + 15, - }) - macro('vd', { - from: points.cfHem, - to: points.cfWaist, - x: points.cfHips.x - sa - 15, - }) - macro('vd', { - from: points.cfHem, - to: points.cfNeck, - x: points.cfHips.x - sa - 30, - }) - macro('hd', { - from: points.cfNeck, - to: points.s3CollarSplit, - y: points.s3CollarSplit.y - sa - 15, - }) - macro('hd', { - from: points.cfNeck, - to: points.s3ArmholeSplit, - y: points.s3CollarSplit.y - sa - 30, - }) - } + // Seamline + paths.saBase = new Path() + .move(points.cfHem) + .line(points.hem) + .line(points.armhole) + .curve(points.armholeCp2, points.armholeHollowCp1, points.armholeHollow) + .curve(points.armholeHollowCp2, points.armholePitchCp1, points.armholePitch) + .join(paths.frontArmhole) + .line(points.s3CollarSplit) + .join(paths.frontCollar) - return part + paths.saBase.render = false + paths.seam = new Path() + .move(points.cfNeck) + .line(points.cfHem) + .join(paths.saBase) + .attr('class', 'fabric') + + // Store lengths to fit sleeve + store.set('frontArmholeLength', shared.armholeLength(points, Path)) + store.set('frontArmholeToArmholePitch', shared.armholeToArmholePitch(points, Path)) + + // Complete pattern? + if (complete) { + macro('cutonfold', { + from: points.cfNeck, + to: points.cfHips, + grainline: true, + }) + macro('title', { at: points.title, nr: 1, title: 'front' }) + snippets.armholePitchNotch = new Snippet('notch', points.armholePitch) + paths.waist = new Path().move(points.cfWaist).line(points.waist).attr('class', 'help') + if (sa) { + paths.sa = paths.saBase + .offset(sa) + .attr('class', 'fabric sa') + .line(points.cfNeck) + .move(points.cfHips) + paths.sa.line(paths.sa.start()) + } + + // Add notches if the shoulder seam is shifted + shared.s3Notches(part, 'notch') + } + + // Paperless? + if (paperless) { + shared.dimensions(part, 'front') + macro('hd', { + from: points.cfHips, + to: points.hips, + y: points.hem.y + sa + 15, + }) + macro('vd', { + from: points.cfHem, + to: points.cfWaist, + x: points.cfHips.x - sa - 15, + }) + macro('vd', { + from: points.cfHem, + to: points.cfNeck, + x: points.cfHips.x - sa - 30, + }) + macro('hd', { + from: points.cfNeck, + to: points.s3CollarSplit, + y: points.s3CollarSplit.y - sa - 15, + }) + macro('hd', { + from: points.cfNeck, + to: points.s3ArmholeSplit, + y: points.s3CollarSplit.y - sa - 30, + }) + } + + return part + } } diff --git a/designs/brian/src/index.js b/designs/brian/src/index.js index b2168915d57..b3517cf719e 100644 --- a/designs/brian/src/index.js +++ b/designs/brian/src/index.js @@ -1,38 +1,38 @@ +// FreeSewing core library import freesewing from '@freesewing/core' -import plugins from '@freesewing/plugin-bundle' -import plugin from '@freesewing/plugin-bust' // Note: conditional plugin -import config from '../config' -// Parts -import draftBase from './base' -import draftBack from './back' -import draftFront from './front' -import draftSleevecap from './sleevecap' -import draftSleeve from './sleeve' +// FreeSewing Plugins +import pluginBundle from '@freesewing/plugin-bundle' +import bustPlugin from '@freesewing/plugin-bust' // Note: conditional plugin +// Design config & options +import { info, measurements, optionalMeasurements } from '../config/index' +//import * as options from '../config/options' +// Design parts +import back from './back' +import front from './front' +import sleeve from './sleeve' +// These are only here to be exported +import base from './base' +import sleevecap from './sleevecap' -/* Check to see whether we should load the bust plugin - * Only of the `draftForHighBust` options is set - * AND the highBust measurement is available - */ -const condition = (settings = false) => - settings && - settings.options && - settings.options.draftForHighBust && - settings.measurements.highBust - ? true - : false -// Create design -const Brian = new freesewing.Design(config, plugins, { plugin, condition }) - -// Attach draft methods to prototype -Brian.prototype.draftBase = draftBase -Brian.prototype.draftBack = draftBack -Brian.prototype.draftFront = draftFront -Brian.prototype.draftSleevecap = draftSleevecap -Brian.prototype.draftSleeve = draftSleeve +// Setup design +const Brian = new freesewing.Design({ + ...info, + measurements, + optionalMeasurements, +// options: { ...options }, + parts: { back, front, sleeve }, + plugins: pluginBundle, + conditionalPlugins: { + plugin: bustPlugin, + condition: (settings=false) => + settings?.options?.draftForHighBust && + settings?.measurements?.highBust + ? true : false + } +}) // Named exports -export { config, Brian } - +export { back, front, sleeve, base, sleevecap } // Default export export default Brian diff --git a/designs/brian/src/sleeve.js b/designs/brian/src/sleeve.js index 6166647f801..84236dfae39 100644 --- a/designs/brian/src/sleeve.js +++ b/designs/brian/src/sleeve.js @@ -1,89 +1,103 @@ -export default (part) => { - const { - store, - sa, - measurements, - options, - Point, - points, - Path, - paths, - Snippet, - snippets, - complete, - paperless, - macro, - } = part.shorthand() +import sleevecap from './sleevecap' +import { _sleeve as options } from '../config/options.js' - // Determine the sleeve length - store.set('sleeveLength', measurements.shoulderToWrist * (1 + options.sleeveLengthBonus)) - points.sleeveTip = paths.sleevecap.edge('top') - points.sleeveTop = new Point(0, points.sleeveTip.y) // Always in center +export default { + from: sleevecap, + name: 'sleeve', + options, + draft: (part) => { + const { + store, + sa, + measurements, + options, + Point, + points, + Path, + paths, + Snippet, + snippets, + complete, + paperless, + macro, + } = part.shorthand() - // Wrist - points.centerWrist = points.sleeveTop.shift(-90, store.get('sleeveLength')) - points.wristRight = points.centerWrist.shift(0, (measurements.wrist * (1 + options.cuffEase)) / 2) - points.wristLeft = points.wristRight.rotate(180, points.centerWrist) + // Remove things inherited + macro('cutonfold', false) + macro('rmad') + delete paths.waist + for (const key in snippets) delete snippets[key] - // Paths - paths.sleevecap.render = false - paths.seam = new Path() - .move(points.bicepsLeft) - .move(points.wristLeft) - .move(points.wristRight) - .line(points.bicepsRight) - .join(paths.sleevecap) - .close() - .attr('class', 'fabric') + // Determine the sleeve length + store.set('sleeveLength', measurements.shoulderToWrist * (1 + options.sleeveLengthBonus)) + points.sleeveTip = paths.sleevecap.edge('top') + points.sleeveTop = new Point(0, points.sleeveTip.y) // Always in center - // Anchor point for sampling - points.gridAnchor = new Point(0, 0) + // Wrist + points.centerWrist = points.sleeveTop.shift(-90, store.get('sleeveLength')) + points.wristRight = points.centerWrist.shift(0, (measurements.wrist * (1 + options.cuffEase)) / 2) + points.wristLeft = points.wristRight.rotate(180, points.centerWrist) - // Complete pattern? - if (complete) { - points.logo = points.centerBiceps.shiftFractionTowards(points.centerWrist, 0.3) - snippets.logo = new Snippet('logo', points.logo) - macro('title', { at: points.centerBiceps, nr: 3, title: 'sleeve' }) - macro('grainline', { from: points.centerWrist, to: points.centerBiceps }) - points.scaleboxAnchor = points.scalebox = points.centerBiceps.shiftFractionTowards( - points.centerWrist, - 0.5 - ) - macro('scalebox', { at: points.scalebox }) + // Paths + paths.sleevecap.render = false + paths.seam = new Path() + .move(points.bicepsLeft) + .move(points.wristLeft) + .move(points.wristRight) + .line(points.bicepsRight) + .join(paths.sleevecap) + .close() + .attr('class', 'fabric') - points.frontNotch = paths.sleevecap.shiftAlong(store.get('frontArmholeToArmholePitch')) - points.backNotch = paths.sleevecap.reverse().shiftAlong(store.get('backArmholeToArmholePitch')) - snippets.frontNotch = new Snippet('notch', points.frontNotch) - snippets.backNotch = new Snippet('bnotch', points.backNotch) - if (sa) paths.sa = paths.seam.offset(sa).attr('class', 'fabric sa') + // Anchor point for sampling + points.gridAnchor = new Point(0, 0) + + // Complete pattern? + if (complete) { + points.logo = points.centerBiceps.shiftFractionTowards(points.centerWrist, 0.3) + snippets.logo = new Snippet('logo', points.logo) + macro('title', { at: points.centerBiceps, nr: 3, title: 'sleeve' }) + macro('grainline', { from: points.centerWrist, to: points.centerBiceps }) + points.scaleboxAnchor = points.scalebox = points.centerBiceps.shiftFractionTowards( + points.centerWrist, + 0.5 + ) + macro('scalebox', { at: points.scalebox }) + + points.frontNotch = paths.sleevecap.shiftAlong(store.get('frontArmholeToArmholePitch')) + points.backNotch = paths.sleevecap.reverse().shiftAlong(store.get('backArmholeToArmholePitch')) + snippets.frontNotch = new Snippet('notch', points.frontNotch) + snippets.backNotch = new Snippet('bnotch', points.backNotch) + if (sa) paths.sa = paths.seam.offset(sa).attr('class', 'fabric sa') + } + + // Paperless? + if (paperless) { + macro('vd', { + from: points.wristLeft, + to: points.bicepsLeft, + x: points.bicepsLeft.x - sa - 15, + }) + macro('vd', { + from: points.wristLeft, + to: points.sleeveTip, + x: points.bicepsLeft.x - sa - 30, + }) + macro('hd', { + from: points.bicepsLeft, + to: points.bicepsRight, + y: points.sleeveTip.y - sa - 30, + }) + macro('hd', { + from: points.wristLeft, + to: points.wristRight, + y: points.wristLeft.y + sa + 30, + }) + macro('pd', { + path: paths.sleevecap.reverse(), + d: -1 * sa - 15, + }) + } + return part } - - // Paperless? - if (paperless) { - macro('vd', { - from: points.wristLeft, - to: points.bicepsLeft, - x: points.bicepsLeft.x - sa - 15, - }) - macro('vd', { - from: points.wristLeft, - to: points.sleeveTip, - x: points.bicepsLeft.x - sa - 30, - }) - macro('hd', { - from: points.bicepsLeft, - to: points.bicepsRight, - y: points.sleeveTip.y - sa - 30, - }) - macro('hd', { - from: points.wristLeft, - to: points.wristRight, - y: points.wristLeft.y + sa + 30, - }) - macro('pd', { - path: paths.sleevecap.reverse(), - d: -1 * sa - 15, - }) - } - return part } diff --git a/designs/brian/src/sleevecap.js b/designs/brian/src/sleevecap.js index 8fbe87bf3b6..95a5d5e6ff2 100644 --- a/designs/brian/src/sleevecap.js +++ b/designs/brian/src/sleevecap.js @@ -1,3 +1,6 @@ +import front from './front' +import { _sleevecap as options } from '../config/options.js' + /** Calculates the differece between actual and optimal sleevecap length * Positive values mean sleevecap is longer than armhole */ @@ -136,25 +139,31 @@ function draftSleevecap(part, run) { } } -export default (part) => { - let { store, units, options, Point, points, paths, raise } = part.shorthand() +export default { + from: front, + name: 'sleevecap', + hide: true, + options, + draft: (part) => { + const { store, units, options, Point, points, paths, raise } = part.shorthand() - store.set('sleeveFactor', 1) - let run = 0 - let delta = 0 - do { - draftSleevecap(part, run) - delta = sleevecapDelta(store) - sleevecapAdjust(store) - run++ - raise.debug(`Fitting Brian sleevecap. Run ${run}: delta is ${units(delta)}`) - } while (options.brianFitSleeve === true && run < 50 && Math.abs(sleevecapDelta(store)) > 2) + store.set('sleeveFactor', 1) + let run = 0 + let delta = 0 + do { + draftSleevecap(part, run) + delta = sleevecapDelta(store) + sleevecapAdjust(store) + run++ + raise.debug(`Fitting Brian sleevecap. Run ${run}: delta is ${units(delta)}`) + } while (options.brianFitSleeve === true && run < 50 && Math.abs(sleevecapDelta(store)) > 2) - // Paths - paths.sleevecap.attr('class', 'fabric') + // Paths + paths.sleevecap.attr('class', 'fabric') - // Anchor point for sampling - points.gridAnchor = new Point(0, 0) + // Anchor point for sampling + points.gridAnchor = new Point(0, 0) - return part + return part + } } diff --git a/packages/core/src/design.js b/packages/core/src/design.js index 62b4b5dc04b..499f94a7f89 100644 --- a/packages/core/src/design.js +++ b/packages/core/src/design.js @@ -5,7 +5,17 @@ import Pattern from './pattern' * So it's sort of a super-constructor */ export default function Design(config, plugins = false, conditionalPlugins = false) { - + // Add part options to config + if (!config.options) config.options = {} + if (config.parts) { + for (const partName in config.parts) { + if (config.parts[partName].options) { + for (const optionName in config.parts[partName].options) { + config.options[optionName] = config.parts[partName].options[optionName] + } + } + } + } // Ensure all options have a hide() method config.options = optionsWithHide(config.options) diff --git a/packages/core/src/pattern.js b/packages/core/src/pattern.js index 1c932638234..2aeef7c3494 100644 --- a/packages/core/src/pattern.js +++ b/packages/core/src/pattern.js @@ -1,4 +1,4 @@ -import { macroName, sampleStyle, capitalize } from './utils' +import { macroName, sampleStyle, capitalize, decoratePartDependency } from './utils' import Part from './part' import Point from './point' import Path from './path' @@ -77,26 +77,12 @@ export default function Pattern(config = { options: {} }) { if (typeof this.config.dependencies === 'undefined') this.config.dependencies = {} if (typeof this.config.inject === 'undefined') this.config.inject = {} if (typeof this.config.hide === 'undefined') this.config.hide = [] - this.config.resolvedDependencies = this.resolveDependencies(this.config.dependencies) - this.config.draftOrder = this.draftOrder(this.config.resolvedDependencies) // Convert options - for (let i in config.options) { - let option = config.options[i] - if (typeof option === 'object') { - if (typeof option.pct !== 'undefined') this.settings.options[i] = option.pct / 100 - else if (typeof option.mm !== 'undefined') this.settings.options[i] = option.mm - else if (typeof option.deg !== 'undefined') this.settings.options[i] = option.deg - else if (typeof option.count !== 'undefined') this.settings.options[i] = option.count - else if (typeof option.bool !== 'undefined') this.settings.options[i] = option.bool - else if (typeof option.dflt !== 'undefined') this.settings.options[i] = option.dflt - else { - let err = 'Unknown option type: ' + JSON.stringify(option) - this.raise.error(err) - throw new Error(err) - } - } else { - this.settings.options[i] = option + this.addOptions(this.config.options) + if (this.config.parts) { + for (const partName in this.config.parts) { + if (this.config.parts[partName].options) this.addOptions(this.config.parts[partName].options) } } @@ -127,6 +113,59 @@ export default function Pattern(config = { options: {} }) { } } +// Converts/adds options +Pattern.prototype.addOptions = function(options={}) { + for (let i in options) { + const option = options[i] + if (typeof option === 'object') { + if (typeof option.pct !== 'undefined') this.settings.options[i] = option.pct / 100 + else if (typeof option.mm !== 'undefined') this.settings.options[i] = option.mm + else if (typeof option.deg !== 'undefined') this.settings.options[i] = option.deg + else if (typeof option.count !== 'undefined') this.settings.options[i] = option.count + else if (typeof option.bool !== 'undefined') this.settings.options[i] = option.bool + else if (typeof option.dflt !== 'undefined') this.settings.options[i] = option.dflt + else { + let err = 'Unknown option type: ' + JSON.stringify(option) + this.raise.error(err) + throw new Error(err) + } + } else { + this.settings.options[i] = option + } + } + + // Make it chainable + return this +} + +/* + * Defer some things that used to happen in the constructor to + * facilitate late-stage adding of parts + */ +Pattern.prototype.init = function () { + // Resolve all dependencies + this.dependencies = this.config.dependencies + this.inject = this.config.inject + this.hide = this.config.hide + if (Array.isArray(this.config.parts)) { + this.resolvedDependencies = this.resolveDependencies(this.dependencies) + } + else if (typeof this.config.parts === 'object') { + this.__parts = this.config.parts + this.preresolveDependencies() + this.resolvedDependencies = this.resolveDependencies(this.dependencies) + } + this.config.draftOrder = this.draftOrder(this.resolvedDependencies) + + // Make all parts uniform + for (const [key, value] of Object.entries(this.__parts)) { + this.__parts[key] = decoratePartDependency(value) + } + + return this +} + + function snappedOption(option, pattern) { const conf = pattern.config.options[option] const abs = conf.toAbs(pattern.settings.options[option], pattern.settings) @@ -191,10 +230,26 @@ Pattern.prototype.runHooks = function (hookName, data = false) { } } +/* + * Allows adding a part at run-time + */ +Pattern.prototype.addPart = function (part, name=false, key) { + if (!part.draft) part = decoratePartDependency(part, givenName) + if (typeof part?.draft === 'function') { + this.__parts[part.name] = part + } + else this.raise.warning(`Cannot attach part ${name} because it is not a part`) + + return this +} + /** * The default draft method with pre- and postDraft hooks */ Pattern.prototype.draft = function () { + // Late-stage initialization + this.init() + if (this.is !== 'sample') { this.is = 'draft' this.cutList = {} @@ -212,33 +267,49 @@ Pattern.prototype.draft = function () { } this.runHooks('preDraft') - for (let partName of this.config.draftOrder) { + for (const partName of this.config.draftOrder) { + // Create parts this.raise.debug(`Creating part \`${partName}\``) this.parts[partName] = new this.Part(partName) - if (typeof this.config.inject[partName] === 'string') { + // Handle inject/inheritance + if (typeof this.inject[partName] === 'string') { this.raise.debug( - `Injecting part \`${this.config.inject[partName]}\` into part \`${partName}\`` + `Injecting part \`${this.inject[partName]}\` into part \`${partName}\`` ) try { - this.parts[partName].inject(this.parts[this.config.inject[partName]]) + this.parts[partName].inject(this.parts[this.inject[partName]]) } catch (err) { this.raise.error([ - `Could not inject part \`${this.config.inject[partName]}\` into part \`${partName}\``, + `Could not inject part \`${this.inject[partName]}\` into part \`${partName}\``, err, ]) } } if (this.needs(partName)) { - let method = 'draft' + capitalize(partName) - if (typeof this[method] !== 'function') { - this.raise.error(`Method \`pattern.${method}\` is callable`) - throw new Error('Method "' + method + '" on pattern object is not callable') + // Draft part + const method = 'draft' + capitalize(partName) + if (typeof this.__parts?.[partName]?.draft === 'function') { + // 2022 way - Part is contained in config + try { + this.parts[partName] = this.__parts[partName].draft(this.parts[partName]) + if (this.parts[partName].render) this.cutList[partName] = this.parts[partName].cut + } catch (err) { + this.raise.error([`Unable to draft part \`${partName}\``, err]) + } } - try { - this.parts[partName] = this[method](this.parts[partName]) - if (this.parts[partName].render ) this.cutList[partName] = this.parts[partName].cut - } catch (err) { - this.raise.error([`Unable to draft part \`${partName}\``, err]) + else if (typeof this[method] === 'function') { + // Legacy way - Part is attached to the prototype + this.raise.warning(`Adding parts to the prototype is deprecated and will be removed in FreeSewing v4 (part: \`${partName}\`)`) + try { + this.parts[partName] = this[method](this.parts[partName]) + if (this.parts[partName].render ) this.cutList[partName] = this.parts[partName].cut + } catch (err) { + this.raise.error([`Unable to draft part \`${partName}\``, err]) + } + } + else { + this.raise.error(`Unable to draft pattern. Part is not available in iether legacy or 2022`) + throw new Error('Method "' + method + '" on pattern object is not callable') } if (typeof this.parts[partName] === 'undefined') { this.raise.error( @@ -570,7 +641,7 @@ Pattern.prototype.draftOrder = function (graph = this.resolveDependencies()) { Pattern.prototype.resolveDependency = function ( seen, part, - graph = this.config.dependencies, + graph = this.dependencies, deps = [] ) { if (typeof seen[part] === 'undefined') seen[part] = true @@ -586,33 +657,52 @@ Pattern.prototype.resolveDependency = function ( return deps } +/** Pre-Resolves part dependencies that are passed in 2022 style */ +Pattern.prototype.preresolveDependencies = function (count=0) { + const len = Object.keys(this.__parts).length + if (!this.__parts) return + for (const [name, part] of Object.entries(this.__parts)) { + if (part.from) { + this.inject[name] = part.from.name + if (typeof this.__parts[part.from.name] === 'undefined') { + this.__parts[part.from.name] = decoratePartDependency(part.from) + } + } + } + const newlen = Object.keys(this.__parts).length + + return (Object.keys(this.__parts).length > len) + ? this.preresolveDependencies() + : this +} + /** Resolves part dependencies into a flat array */ -Pattern.prototype.resolveDependencies = function (graph = this.config.dependencies) { - for (let i in this.config.inject) { - let dependency = this.config.inject[i] - if (typeof this.config.dependencies[i] === 'undefined') this.config.dependencies[i] = dependency - else if (this.config.dependencies[i] !== dependency) { - if (typeof this.config.dependencies[i] === 'string') { - this.config.dependencies[i] = [this.config.dependencies[i], dependency] - } else if (Array.isArray(this.config.dependencies[i])) { - if (this.config.dependencies[i].indexOf(dependency) === -1) - this.config.dependencies[i].push(dependency) +Pattern.prototype.resolveDependencies = function (graph = this.dependencies) { + for (let i in this.inject) { + let dependency = this.inject[i] + if (typeof this.dependencies[i] === 'undefined') this.dependencies[i] = dependency + else if (this.dependencies[i] !== dependency) { + if (typeof this.dependencies[i] === 'string') { + this.dependencies[i] = [this.dependencies[i], dependency] + } else if (Array.isArray(this.dependencies[i])) { + if (this.dependencies[i].indexOf(dependency) === -1) + this.dependencies[i].push(dependency) } else { this.raise.error('Part dependencies should be a string or an array of strings') throw new Error('Part dependencies should be a string or an array of strings') } } // Parts both in the parts and dependencies array trip up the dependency resolver - if (Array.isArray(this.config.parts)) { - let pos = this.config.parts.indexOf(this.config.inject[i]) - if (pos !== -1) this.config.parts.splice(pos, 1) + if (Array.isArray(this.__parts)) { + let pos = this.__parts.indexOf(this.inject[i]) + if (pos !== -1) this.__parts.splice(pos, 1) } } // Include parts outside the dependency graph if (Array.isArray(this.config.parts)) { for (let part of this.config.parts) { - if (typeof this.config.dependencies[part] === 'undefined') this.config.dependencies[part] = [] + if (typeof this.dependencies[part] === 'undefined') this.dependencies[part] = [] } } @@ -632,15 +722,15 @@ Pattern.prototype.needs = function (partName) { if (typeof this.settings.only === 'undefined' || this.settings.only === false) return true else if (typeof this.settings.only === 'string') { if (this.settings.only === partName) return true - if (Array.isArray(this.config.resolvedDependencies[this.settings.only])) { - for (let dependency of this.config.resolvedDependencies[this.settings.only]) { + if (Array.isArray(this.resolvedDependencies[this.settings.only])) { + for (let dependency of this.resolvedDependencies[this.settings.only]) { if (dependency === partName) return true } } } else if (Array.isArray(this.settings.only)) { for (let part of this.settings.only) { if (part === partName) return true - for (let dependency of this.config.resolvedDependencies[part]) { + for (let dependency of this.resolvedDependencies[part]) { if (dependency === partName) return true } } @@ -651,9 +741,11 @@ Pattern.prototype.needs = function (partName) { /* Checks whether a part is hidden in the config */ Pattern.prototype.isHidden = function (partName) { - if (Array.isArray(this.config.hide)) { - if (this.config.hide.indexOf(partName) !== -1) return true + if (Array.isArray(this.hide)) { + if (this.hide.indexOf(partName) !== -1) return true } + // 2022 style + if (this.__parts?.[partName]?.hide) return true return false } diff --git a/packages/core/src/utils.js b/packages/core/src/utils.js index edc9ea06ffb..c83fab9bf78 100644 --- a/packages/core/src/utils.js +++ b/packages/core/src/utils.js @@ -384,3 +384,7 @@ export const generatePartTransform = (x, y, rotate, flipX, flipY, part) => { } } +/* + * Makes sure an object passed to be attached as a part it not merely a method + */ +export const decoratePartDependency = (obj, name) => (typeof obj === 'function') ? { draft: obj, name } : obj