diff --git a/config/scripts.yaml b/config/scripts.yaml index 00b262ae57c..4d3ad581d19 100644 --- a/config/scripts.yaml +++ b/config/scripts.yaml @@ -25,6 +25,8 @@ i18n: prebuild: 'node scripts/prebuilder.mjs' test: *test testci: *testci +models: + test: "npx mocha tests/*.test.mjs" new-design: build: "SITE=new-design/shared node ../../sites/shared/prebuild/i18n-only.mjs && cp ../../scripts/banner.mjs ./lib && node --experimental-json-modules build.mjs" mbuild: '!' diff --git a/packages/models/README.md b/packages/models/README.md index 7f924e0f78c..58068bedd6b 100644 --- a/packages/models/README.md +++ b/packages/models/README.md @@ -60,25 +60,6 @@ import { manSize38 } from @freesewing/models ``` which will give you an object with `measurement: value` pairs. -The example above gives you: - -```js -{ - bicepsCircumference: 305, - centerBackNeckToWaist: 495, - chestCircumference: 965, - headCircumference: 580, - hipsCircumference: 838, - hipsToUpperLeg: 202, - naturalWaistToHip: 110, - neckCircumference: 391, - shoulderSlope: 49, - shoulderToShoulder: 444, - shoulderToWrist: 680, - upperLegCircumference: 598, - wristCircumference: 185 -} -``` ## Units @@ -86,12 +67,34 @@ All measurements are in mm. ## Available models - - manSize34 - - manSize36 - - manSize38 - - manSize40 - - manSize42 - - manSize44 +We have menswear and womenswear models, but all of them have all measurements. +The digits in the model refer to the neck circumference in cm. + +- womenswear28 +- womenswear30 +- womenswear32 +- womenswear34 +- womenswear36 +- womenswear38 +- womenswear40 +- womenswear42 +- womenswear44 +- womenswear46 +- menswear32 +- menswear34 +- menswear36 +- menswear38 +- menswear40 +- menswear42 +- menswear44 +- menswear46 +- menswear48 +- menswear50 + +## Other named exports + +- measurements: A list/array of measurement names +- sizes: An object with `menswear` and `womenswear` keys that hold a list/array of sizes we provide diff --git a/packages/models/build.mjs b/packages/models/build.mjs index 9d1da1994da..0f283af1c6d 100644 --- a/packages/models/build.mjs +++ b/packages/models/build.mjs @@ -14,7 +14,7 @@ const banner = `/** const options = { banner: { js: banner }, bundle: true, - entryPoints: ['src/index.js'], + entryPoints: ['src/index.mjs'], format: 'esm', outfile: 'dist/index.mjs', external: ["@freesewing"], diff --git a/packages/models/info.md b/packages/models/info.md index bcdf5f27df1..b912a70907a 100644 --- a/packages/models/info.md +++ b/packages/models/info.md @@ -8,25 +8,6 @@ import { manSize38 } from @freesewing/models ``` which will give you an object with `measurement: value` pairs. -The example above gives you: - -```js -{ - bicepsCircumference: 305, - centerBackNeckToWaist: 495, - chestCircumference: 965, - headCircumference: 580, - hipsCircumference: 838, - hipsToUpperLeg: 202, - naturalWaistToHip: 110, - neckCircumference: 391, - shoulderSlope: 49, - shoulderToShoulder: 444, - shoulderToWrist: 680, - upperLegCircumference: 598, - wristCircumference: 185 -} -``` ## Units @@ -34,10 +15,32 @@ All measurements are in mm. ## Available models - - manSize34 - - manSize36 - - manSize38 - - manSize40 - - manSize42 - - manSize44 +We have menswear and womenswear models, but all of them have all measurements. +The digits in the model refer to the neck circumference in cm. + +- womenswear28 +- womenswear30 +- womenswear32 +- womenswear34 +- womenswear36 +- womenswear38 +- womenswear40 +- womenswear42 +- womenswear44 +- womenswear46 +- menswear32 +- menswear34 +- menswear36 +- menswear38 +- menswear40 +- menswear42 +- menswear44 +- menswear46 +- menswear48 +- menswear50 + +## Other named exports + +- measurements: A list/array of measurement names +- sizes: An object with `menswear` and `womenswear` keys that hold a list/array of sizes we provide diff --git a/packages/models/package.json b/packages/models/package.json index c82b13c73ac..f64f8e9fd4c 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -31,7 +31,7 @@ "clean": "rimraf dist", "mbuild": "NO_MINIFY=1 node --experimental-json-modules build.mjs", "symlink": "mkdir -p ./node_modules/@freesewing && cd ./node_modules/@freesewing && ln -s -f ../../../* . && cd -", - "test": "echo \"models: No tests configured. Perhaps you'd like to do this?\" && exit 0", + "test": "npx mocha tests/*.test.mjs", "vbuild": "VERBOSE=1 node --experimental-json-modules build.mjs", "lab": "cd ../../sites/lab && yarn start", "tips": "node ../../scripts/help.mjs", diff --git a/packages/models/src/index.js b/packages/models/src/index.js deleted file mode 100644 index 222bf1fadbd..00000000000 --- a/packages/models/src/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import neckstimate from './neckstimate' -import measurements from './measurements' -import sizes from './sizes' - -const withBreasts = {} -const withoutBreasts = {} - -for (let s of sizes.womenswear) { - withBreasts['size' + s] = {} - for (let m of measurements.womenswear) { - withBreasts['size' + s][m] = neckstimate(s * 10, m, true) - } -} - -for (let s of sizes.menswear) { - withoutBreasts['size' + s] = {} - for (let m of measurements.menswear) { - withoutBreasts['size' + s][m] = neckstimate(s * 10, m, false) - } -} - -export { measurements, sizes, withoutBreasts, withBreasts } diff --git a/packages/models/src/index.mjs b/packages/models/src/index.mjs new file mode 100644 index 00000000000..cf83ff48953 --- /dev/null +++ b/packages/models/src/index.mjs @@ -0,0 +1,39 @@ +import { measurements, neckstimate } from './neckstimate.mjs' + +const getMeasurements = (size, breasts) => { + const all = {} + for (const m of measurements) { + all[m] = neckstimate(size * 10, m, breasts) + } + + return all +} + +export const sizes = { + womenswear: [28,30,32,34,36,38,40,42,44,46], + menswear: [32,34,36,38,40,42,44,46,48,50], +} + +export const womenswear28 = getMeasurements(28, true) +export const womenswear30 = getMeasurements(30, true) +export const womenswear32 = getMeasurements(32, true) +export const womenswear34 = getMeasurements(34, true) +export const womenswear36 = getMeasurements(36, true) +export const womenswear38 = getMeasurements(38, true) +export const womenswear40 = getMeasurements(40, true) +export const womenswear42 = getMeasurements(42, true) +export const womenswear44 = getMeasurements(44, true) +export const womenswear46 = getMeasurements(46, true) + +export const menswear32 = getMeasurements(32, false) +export const menswear34 = getMeasurements(34, false) +export const menswear36 = getMeasurements(36, false) +export const menswear38 = getMeasurements(38, false) +export const menswear40 = getMeasurements(40, false) +export const menswear42 = getMeasurements(42, false) +export const menswear44 = getMeasurements(44, false) +export const menswear46 = getMeasurements(46, false) +export const menswear48 = getMeasurements(48, false) +export const menswear50 = getMeasurements(50, false) + +export { measurements } diff --git a/packages/models/src/measurements.js b/packages/models/src/measurements.js deleted file mode 100644 index a1b7d60513d..00000000000 --- a/packages/models/src/measurements.js +++ /dev/null @@ -1,74 +0,0 @@ -export default { - menswear: [ - 'ankle', - 'biceps', - 'chest', - 'crossSeam', - 'crossSeamFront', - 'crotchDepth', - 'head', - 'heel', - 'hips', - 'hpsToBust', - 'hpsToWaistBack', - 'inseam', - 'knee', - 'neck', - 'seat', - 'seatBack', - 'shoulderSlope', - 'shoulderToElbow', - 'shoulderToShoulder', - 'shoulderToWrist', - 'upperLeg', - 'waist', - 'waistBack', - 'waistToArmhole', - 'waistToFloor', - 'waistToHips', - 'waistToKnee', - 'waistToSeat', - 'waistToUpperLeg', - 'wrist', - ], - womenswear: [ - 'ankle', - 'biceps', - 'bustFront', - 'bustPointToUnderbust', - 'bustSpan', - 'chest', - 'crossSeam', - 'crossSeamFront', - 'crotchDepth', - 'head', - 'heel', - 'highBust', - 'highBustFront', - 'hips', - 'hpsToBust', - 'hpsToWaistBack', - 'hpsToWaistFront', - 'inseam', - 'knee', - 'neck', - 'seat', - 'seatBack', - 'shoulderSlope', - 'shoulderToElbow', - 'shoulderToShoulder', - 'shoulderToWrist', - 'underbust', - 'upperLeg', - 'waist', - 'waistBack', - 'waistToArmhole', - 'waistToFloor', - 'waistToHips', - 'waistToKnee', - 'waistToSeat', - 'waistToUnderbust', - 'waistToUpperLeg', - 'wrist', - ], -} diff --git a/packages/models/src/neckstimate.mjs b/packages/models/src/neckstimate.mjs new file mode 100644 index 00000000000..959a0ea9b55 --- /dev/null +++ b/packages/models/src/neckstimate.mjs @@ -0,0 +1,157 @@ +/* + * This completes the list of measurements with the ones + * we can calculate based on what we already have + */ +function complete(m) { + // Added by plugin-bust: + m.bust = m.chest + + // Added by plugin-measurements: + m.crossSeamBack = [0,1].map(i => m.crossSeam[i] - m.crossSeamFront[i]) + m.seatBackArc = [0,1].map(i => m.seatBack[i] / 2) + m.waistBackArc = [0,1].map(i => m.waistBack[i] / 2) + m.bustBack = [0,1].map(i => m.bust[i] - m.bustFront[i]) + m.seatFront = [0,1].map(i => m.seat[i] - m.seatBack[i]) + m.seatFrontArc = [0,1].map(i => m.seatFront[i] / 2) + m.waistFront = [0,1].map(i => m.waist[i] - m.waistBack[i]) + m.waistFrontArc = [0,1].map(i => m.waistFront[i] / 2) + m.highBustBack = [0,1].map(i => m.highBust[i] - m.highBustFront[i]) + + return m +} + + +/* + * These are a set of measurements of an average-sized [woman, man]. + * We simply extrapolate for other sizes (based on neck) + * by keeping the same proportions. + * That is almost certainly not the best sizing table you can get, + * but we are not in the business of standard sizes, so this will do. + */ + +const base = complete({ + ankle: [245, 235], + biceps: [270, 350], + bustFront: [480, 560], // FIXME: Estimate + bustPointToUnderbust: [100, 60], // FIXME: Estimate + bustSpan: [160, 190], // FIXME: Estimate + chest: [925, 1000], + crossSeam: [740, 870], + crossSeamFront: [370, 410], + crotchDepth: [270, 340], + heel: [315, 360], + head: [565, 590], + highBust: [865, 1030], + highBustFront: [440, 570], // FIXME: Estimate + hips: [900, 840], + hpsToBust: [275, 280], + hpsToWaistBack: [395, 470], + hpsToWaistFront: [400, 460], // FIXME: Estimate + inseam: [765, 780], + knee: [380, 410], + neck: [340, 380], + seat: [1010, 1020], + seatBack: [520, 560], + shoulderSlope: [13, 13], + shoulderToElbow: [340, 360], + shoulderToShoulder: [415, 450], + shoulderToWrist: [590, 630], + underbust: [780, 980], // FIXME: Estimate + upperLeg: [570, 625], + waist: [750, 810], + waistBack: [380, 410], + waistToArmhole: [170, 210], + waistToFloor: [1050, 1160], + waistToHips: [125, 130], + waistToKnee: [600, 640], + waistToSeat: [250, 270], + waistToUnderbust: [80, ], + waistToUpperLeg: [285, 340], + wrist: [165, 175], +}) + +/* + * Since linear measurements don't scale the same as circumference + * measurements, we apply a correction ratio. + */ +let a = 0.5 // arc +let c = 1 // circumference +let v = 0.65 // vertical +const ratio = { + // Arc measurements + bustFront: a, + bustPointToUnderbust: a, + bustSpan: a, + highBustFront: a, + // Circumference measurements + ankle: c, + biceps: c, + chest: c, + highBust: c, + hips: c, + neck: c, + underbust: c, + // Vertical measurements + crotchDepth: v, + hpsToBust: v, + hpsToWaistBack: v, + hpsToWaistFront: v, + waistToHips: v, + waistToKnee: v, + waistToSeat: v, + waistToUnderbust: v, + waistToUpperLeg: v, + // Other + seatBack: 0.6, + waistBack: 0.85, + crossSeam: 0.6, + crossSeamFront: 0.3, + head: 0.35, + heel: 0.25, + inseam: 0.25, + knee: 0.65, + seat: 0.6, + shoulderToElbow: 0.5, + shoulderToShoulder: 0.65, + shoulderToWrist: 0.3, + upperLeg: 0.45, + waist: 0.85, + waistToFloor: 0.4, + wrist: 0.5 +} + +export const measurements = Object.keys(base) + +// This estimates a measurement based on the neck +export const neckstimate = (neck = false, measurement = false, breasts = false, noRound=false) => { + + if (typeof base[measurement] === 'undefined') { + console.log( + new Error(`neckstimate() called with an invalid measurement name (${measurement})`) + ) + return null + } + if (!measurement) { + // No measurement passed + throw new Error( + 'new neckstimate() requires a valid measurement name as second parameter. (received ' + + JSON.stringify(measurement) + + ')' + ) + } + + const i = breasts ? 0 : 1 + + // Shoulder slope is in degrees. Always return the base. + if (measurement === 'shoulderSlope') base.shoulderSlope[i] + + if (!neck) throw new Error('neckstimate() requires a neck measurement in mm as first parameter') + + // This is what should happen + const delta = (neck / base.neck[i]) * base[measurement][i] - base[measurement][i] + + return noRound + ? base[measurement][i] + delta * ratio[measurement] + : Math.round(base[measurement][i] + delta * ratio[measurement]) +} + diff --git a/packages/models/src/neckstimate/complete.js b/packages/models/src/neckstimate/complete.js deleted file mode 100644 index c173d1a6d55..00000000000 --- a/packages/models/src/neckstimate/complete.js +++ /dev/null @@ -1,19 +0,0 @@ -// The completes the list of measurements with the ones -// we can calculate based on what we already have -export default function complete(m) { - // Added by plugin-bust: - m.bust = m.chest - - // Added by plugin-measurements: - m.crossSeamBack = m.crossSeam - m.crossSeamFront - m.seatBackArc = m.seatBack / 2 - m.waistBackArc = m.waistBack / 2 - if (m.bust && m.bustFront) m.bustBack = m.bust - m.bustFront - m.seatFront = m.seat - m.seatBack - m.seatFrontArc = m.seatFront / 2 - m.waistFront = m.waist - m.waistBack - m.waistFrontArc = m.waistFront / 2 - if (m.hightBust && m.highBustFront) m.highBustBack = m.highBust - m.highBustFront - - return m -} diff --git a/packages/models/src/neckstimate/index.js b/packages/models/src/neckstimate/index.js deleted file mode 100644 index ff17603a12d..00000000000 --- a/packages/models/src/neckstimate/index.js +++ /dev/null @@ -1,45 +0,0 @@ -import withBreasts from './with-breasts' -import withoutBreasts from './without-breasts' -import ratio from './ratio' - -// This estimates a measurement based on the neck -const neckstimate = (neck = false, measurement = false, breasts = false, noRound=false) => { - let data = breasts ? withBreasts : withoutBreasts - - // Shoulder slope is in degrees now. Always return de default. - if (measurement === 'shoulderSlope') return withBreasts.shoulderSlope - - if (!neck) throw new Error('neckstimate() requires a neck measurement in mm as first parameter') - if (!measurement) { - // No measurement passed - throw new Error( - 'new neckstimate() requires a valid measurement name as second parameter. (received ' + - JSON.stringify(measurement) + - ')' - ) - } - if (typeof data[measurement] === 'undefined') { - if (typeof withBreasts[measurement] === 'undefined') { - // We used to throw this error, but let's just return null instead so things don't go off the rails - console.log( - new Error(`neckstimate() called with an invalid measurement name (${measurement})`) - ) - return null - } else { - console.log( - `WARNING: neckstimate() called for a breasts-only measurement (${measurement}) on a no-breasts person` - ) - // Return something anyway, rather than fall over - data = withBreasts - } - } - - // This is what should happen - let delta = (neck / data.neck) * data[measurement] - data[measurement] - - return noRound - ? data[measurement] + delta * ratio[measurement] - : Math.round(data[measurement] + delta * ratio[measurement]) -} - -export default neckstimate diff --git a/packages/models/src/neckstimate/ratio.js b/packages/models/src/neckstimate/ratio.js deleted file mode 100644 index 82378d489b4..00000000000 --- a/packages/models/src/neckstimate/ratio.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Since linear measurements don't scale the same as circumference - * measurements, we apply a correction ratio. - */ - -let a = 0.5 // arc -let c = 1 // circumference -let v = 0.65 // vertical - -export default { - // Arc measurements - bustFront: a, - bustPointToUnderbust: a, - bustSpan: a, - highBustFront: a, - // Circumference measurements - ankle: c, - biceps: c, - chest: c, - highBust: c, - hips: c, - neck: c, - underbust: c, - // Vertical measurements - crotchDepth: v, - hpsToBust: v, - hpsToWaistBack: v, - hpsToWaistFront: v, - waistToHips: v, - waistToKnee: v, - waistToSeat: v, - waistToUnderbust: v, - waistToUpperLeg: v, - // Other - seatBack: 0.6, - waistBack: 0.85, - crossSeam: 0.6, - crossSeamFront: 0.3, - head: 0.35, - heel: 0.25, - inseam: 0.25, - knee: 0.65, - seat: 0.6, - shoulderToElbow: 0.5, - shoulderToShoulder: 0.65, - shoulderToWrist: 0.3, - upperLeg: 0.45, - waist: 0.85, - waistToFloor: 0.4, - wrist: 0.5 -} diff --git a/packages/models/src/neckstimate/with-breasts.js b/packages/models/src/neckstimate/with-breasts.js deleted file mode 100644 index 8d9bd35ed31..00000000000 --- a/packages/models/src/neckstimate/with-breasts.js +++ /dev/null @@ -1,50 +0,0 @@ -import complete from './complete' - -/* - * These are a set of measurements of an average-sized woman. - * We simply extrapolate for other sizes (based on neck) - * by keeping the same proportions. - * That is almost certainly not the best sizing table you can get, - * but we are not in the business of standard sizes, so this will do. - */ - -export default complete({ - ankle: 245, - biceps: 270, - bustFront: 480, - bustPointToUnderbust: 100, - bustSpan: 160, - chest: 925, - crossSeam: 740, - crossSeamFront: 370, - crotchDepth: 270, - heel: 315, - head: 565, - highBust: 865, - highBustFront: 440, - hips: 900, - hpsToBust: 275, - hpsToWaistBack: 395, - hpsToWaistFront: 400, - inseam: 765, - knee: 380, - neck: 340, - seat: 1010, - seatBack: 520, - shoulderSlope: 13, - shoulderToElbow: 340, - shoulderToShoulder: 415, - shoulderToWrist: 590, - underbust: 780, - upperLeg: 570, - waist: 750, - waistBack: 380, - waistToArmhole: 17, - waistToFloor: 1050, - waistToHips: 125, - waistToKnee: 600, - waistToSeat: 250, - waistToUnderbust: 80, - waistToUpperLeg: 285, - wrist: 165 -}) diff --git a/packages/models/src/neckstimate/without-breasts.js b/packages/models/src/neckstimate/without-breasts.js deleted file mode 100644 index b6d7246f8be..00000000000 --- a/packages/models/src/neckstimate/without-breasts.js +++ /dev/null @@ -1,42 +0,0 @@ -import complete from './complete' - -/* - * These are a set of measurements of an average-sized man. - * We simply extrapolate for other sizes (based on neck) - * by keeping the same proportions. - * That is almost certainly not the best sizing table you can get, - * but we are not in the business of standard sizes, so this will do. - */ -export default complete({ - ankle: 235, - biceps: 350, - chest: 1000, - crossSeam: 870, - crossSeamFront: 410, - crotchDepth: 340, - heel: 360, - head: 590, - highBust: 103, - hips: 840, - hpsToBust: 280, - hpsToWaistBack: 470, - inseam: 780, - knee: 410, - neck: 380, - seat: 1020, - seatBack: 560, - shoulderSlope: 13, - shoulderToElbow: 360, - shoulderToShoulder: 450, - shoulderToWrist: 630, - upperLeg: 625, - waist: 810, - waistBack: 410, - waistToArmhole: 21, - waistToFloor: 1160, - waistToHips: 130, - waistToKnee: 640, - waistToSeat: 270, - waistToUpperLeg: 340, - wrist: 175 -}) diff --git a/packages/models/src/sizes.js b/packages/models/src/sizes.js deleted file mode 100644 index 7f33fb385cd..00000000000 --- a/packages/models/src/sizes.js +++ /dev/null @@ -1,4 +0,0 @@ -export default { - menswear: [32, 34, 36, 38, 40, 42, 44, 46, 48, 50], - womenswear: [28, 30, 32, 34, 36, 38, 40, 42, 44, 46], -} diff --git a/packages/models/tests/models.test.mjs b/packages/models/tests/models.test.mjs new file mode 100644 index 00000000000..a89fe6688f9 --- /dev/null +++ b/packages/models/tests/models.test.mjs @@ -0,0 +1,26 @@ +import chai from "chai" +import * as all from "./dist/index.mjs" + +const expect = chai.expect +const { measurements, sizes } = all + +describe('Measurements', () => { + it("Measurements should be a named export and match the sizes", () => { + for (const m of measurements) { + expect(typeof all.womenswear28[m]).to.equal('number'); + } + }) +}) + +for (const type in sizes) { + describe(`Sizes: ${type}`, () => { + for (const size of sizes[type]) { + it(`${type}${size} should have all measurements`, () => { + for (const m of measurements) { + expect(typeof all[`${type}${size}`][m]).to.equal('number'); + } + }) + } + }) +} +