diff --git a/CHANGELOG.md b/CHANGELOG.md index 48617772d6c..bee8c576506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,11 @@ ### simone +#### Added + + - Added Bust-aligned buttons option and functionality. Closes [#2154] (https://github.com/freesewing/freesewing/issues/2154) + - Added a notch at the center front bustline. + #### Fixed - Don't do a negative FBA from there's no need for an FBA Fixes [#2121](https://github.com/freesewing/freesewing/issues/2121) diff --git a/config/changelog.yaml b/config/changelog.yaml index d6795381478..4c3d7182c63 100644 --- a/config/changelog.yaml +++ b/config/changelog.yaml @@ -8,6 +8,10 @@ Unreleased: plugin-title: - Added support for removing the title via a macro call - Added a render timestamp to the title + simone: + - Added Bust-aligned buttons option and functionality. + Closes [#2154] (https://github.com/freesewing/freesewing/issues/2154) + - Added a notch at the center front bustline. Changed: charlie: diff --git a/designs/simon/config/index.js b/designs/simon/config/index.js index d59d6c70b94..e1f5d57dbc9 100644 --- a/designs/simon/config/index.js +++ b/designs/simon/config/index.js @@ -131,6 +131,10 @@ const config = { extraTopButton: { bool: true }, seperateButtonPlacket: { bool: false }, seperateButtonholePlacket: { bool: false }, + // Not used in Simon but needed for Simone + bustAlignedButtons: { + dflt: 'Disabled', + list: ['Even spacing', 'Split spacing', 'Disabled'], }, // Collar collarEase: { pct: 2, min: 0, max: 10 }, diff --git a/designs/simon/src/buttonholeplacket.js b/designs/simon/src/buttonholeplacket.js index 8757a2ee759..ab561db006e 100644 --- a/designs/simon/src/buttonholeplacket.js +++ b/designs/simon/src/buttonholeplacket.js @@ -25,7 +25,7 @@ export default (part) => { } for (const id in paths) delete part.paths[id] - for (const i of ['waist', 'armholePitch', 'hips', 'armhole']) { + for (const i of ['waist', 'armholePitch', 'hips', 'armhole', 'bust',]) { delete snippets[i + '-notch'] } const width = store.get('buttonholePlacketWidth') @@ -98,6 +98,12 @@ export default (part) => { // Notches snippets['cfArmhole-notch'].anchor.x = points.cfArmhole.x - fold * 2 snippets['cfWaist-notch'].anchor.x = points.cfArmhole.x - fold * 2 + // Not available in Simon + if (typeof snippets['cfBust-notch'] !== 'undefined') + snippets['cfBust-notch'].anchor.x = points.cfArmhole - fold * 2 + // Not available in Simon + if (typeof snippets['cfHem-notch'] !== 'undefined') + snippets['cfHem-notch'].anchor.x = points.cfArmhole.x - fold * 2 // This notch is not available in Simone if (typeof snippets['cfHips-notch'] !== 'undefined') snippets['cfHips-notch'].anchor.x = points.cfArmhole.x - fold * 2 diff --git a/designs/simon/src/buttonplacket.js b/designs/simon/src/buttonplacket.js index 2dbfdf7b732..40649b08239 100644 --- a/designs/simon/src/buttonplacket.js +++ b/designs/simon/src/buttonplacket.js @@ -27,8 +27,10 @@ export default (part) => { for (const id in paths) { if (id !== 'seam') delete part.paths[id] } - for (const i in snippets) { - if (i.indexOf('notch')) delete snippets[i] + let notchesToKeep = ['cfBust-notch', 'cfArmhole-notch', 'cfWaist-notch', + 'cfHem-notch'] + for (const id in snippets) { + if (!notchesToKeep.includes(id)) delete snippets[id] } macro('flip') const width = store.get('buttonPlacketWidth') diff --git a/designs/simon/src/frontleft-classic-cuton.js b/designs/simon/src/frontleft-classic-cuton.js index 78459e14734..b1e5523a078 100644 --- a/designs/simon/src/frontleft-classic-cuton.js +++ b/designs/simon/src/frontleft-classic-cuton.js @@ -22,6 +22,8 @@ export default (part) => { points.placketBottomOuterEdgeUnder = points.placketCfHem.shift(180, width / 2 + fold) points.placketTopEdge = points.placketTopOuterEdgeFold.shift(180, width) points.placketBottomEdge = points.placketBottomOuterEdgeFold.shift(180, width) + if (typeof points.cfBust !== 'undefined') + points.cfBust = points.cfBust.shift(180, fold * 2) paths.seam.line(points.placketTopEdge).line(points.placketBottomEdge).close() @@ -63,6 +65,8 @@ export default (part) => { points.placketEdgeWaist = new Point(points.placketBottomEdge.x, points.waist.y) points.placketEdgeArmhole = new Point(points.placketBottomEdge.x, points.armhole.y) points.placketEdgeHips = new Point(points.placketBottomEdge.x, points.hips.y) + // Delete old cfBust location notch, so we can re-add in new location. + delete snippets['cfBust-notch'] macro('sprinkle', { snippet: 'notch', on: [ @@ -83,6 +87,7 @@ export default (part) => { 'placketBottomOuterEdgeFold', 'placketBottomOuterEdgeOver', 'placketBottomOuterEdgeUnder', + 'cfBust' ], }) delete snippets['cfWaist-notch'] diff --git a/designs/simon/src/frontleft-classic-seperate.js b/designs/simon/src/frontleft-classic-seperate.js index 055df2fa8da..0fa378f0a86 100644 --- a/designs/simon/src/frontleft-classic-seperate.js +++ b/designs/simon/src/frontleft-classic-seperate.js @@ -25,6 +25,7 @@ export default (part) => { delete snippets['cfWaist-notch'] delete snippets['cfHips-notch'] delete snippets['cfArmhole-notch'] + delete snippets['cfBust-notch'] points.edgeArmhole = new Point(points.neckEdge.x, points.armhole.y) points.edgeWaist = new Point(points.neckEdge.x, points.waist.y) points.edgeHips = new Point(points.neckEdge.x, points.hips.y) diff --git a/designs/simon/src/frontright-classic-seperate.js b/designs/simon/src/frontright-classic-seperate.js index 8d73dcf4bd1..fa0256b7078 100644 --- a/designs/simon/src/frontright-classic-seperate.js +++ b/designs/simon/src/frontright-classic-seperate.js @@ -23,6 +23,7 @@ export default (part) => { delete snippets['cfWaist-notch'] delete snippets['cfHips-notch'] delete snippets['cfArmhole-notch'] + delete snippets['cfBust-notch'] points.edgeArmhole = new Point(points.placketTopIn.x, points.armhole.y) points.edgeWaist = new Point(points.placketTopIn.x, points.waist.y) points.edgeHips = new Point(points.placketTopIn.x, points.hips.y) diff --git a/designs/simon/src/shared.js b/designs/simon/src/shared.js index d63fac81a9f..611f332c2bf 100644 --- a/designs/simon/src/shared.js +++ b/designs/simon/src/shared.js @@ -27,15 +27,82 @@ export const calculateReduction = function (part) { export const addButtons = function (part, origin = 'cfNeck', snippet = 'button') { const { points, options, snippets, Snippet } = part.shorthand() - const len = points.cfNeck.dist(points.cfHips) * (1 - options.buttonFreeLength) - for (let i = 1; i <= options.buttons; i++) { - points['button' + i] = points[origin].shift(-90, (len / options.buttons) * i) - snippets[snippet + i] = new Snippet(snippet, points['button' + i]) + const full_len = points.cfNeck.dist(points.cfHips) + const adjusted_len = full_len * (1 - options.buttonFreeLength) + const total_buttons = options.buttons + + switch (options.bustAlignedButtons) { + case 'Even spacing': { + // Strategy: Even button spacing, + // - Determine the correct spacing above the bustline and use that + // spacing for all buttons. + // - The bottom button position is variable, and it ignores the "Button + // free length" setting. + const top_len = points.cfNeck.dist(points.cfBust) + const top_percentage = top_len / full_len + const top_number_buttons = Math.round(total_buttons * top_percentage) + const top_spacing = top_len / top_number_buttons + const even_spacing = top_spacing + for (let i = 1; i <= total_buttons; i++) { + points['button' + i] = points[origin].shift(-90, (even_spacing * i)) + snippets[snippet + i] = new Snippet(snippet, points['button' + i]) + } + break } + case 'Split spacing': { + // Strategy: Different spacings above and below. + // - Calculate the number of buttons that should be above and below + // the bustline by proportion. + // - Calculate the correct spacings to be used above and below the + // bustline, adhering to the "Button free length" setting. + // - For the first and last bottom buttons, slightly shift their + // positions to make the difference in spacings less noticeable + // at the bustline. + const top_len = points.cfNeck.dist(points.cfBust) + const bot_len = adjusted_len - top_len + const top_percentage = top_len / adjusted_len + const top_number_buttons = Math.round(total_buttons * top_percentage) + const bot_number_buttons = total_buttons - top_number_buttons + const top_spacing = top_len / top_number_buttons + const bot_spacing = bot_len / bot_number_buttons + // Top buttons + for (let i = 1; i <= top_number_buttons; i++) { + points['button' + i] = points[origin].shift(-90, top_spacing * i) + snippets[snippet + i] = new Snippet(snippet, points['button' + i]) + } + // Bottom buttons + const adjustment = (top_spacing - bot_spacing) / 2 + points.currentpoint = points['cfBust'].clone() + for (let i = top_number_buttons + 1; i <= total_buttons; i++) { + points.currentpoint = points.currentpoint.shift(-90, bot_spacing) + if (i == top_number_buttons + 1) { + // Adjust first button position + points.currentpoint = points.currentpoint.shift(-90, adjustment) + } else if (i == total_buttons) { + // Adjust last button position in opposite direction. + points.currentpoint = points.currentpoint.shift(90, adjustment) + } + points['button' + i] = points.currentpoint.clone() + snippets[snippet + i] = new Snippet(snippet, points['button' + i]) + } + break } + case 'Disabled': + default: { + // Strategy: The default strategy. + // - Buttons are evenly spaced without regard to the bustline. + // - The "Button free length" setting is obeyed. + const default_spacing = adjusted_len / total_buttons + for (let i = 1; i <= total_buttons; i++) { + points['button' + i] = points[origin].shift(-90, default_spacing * i) + snippets[snippet + i] = new Snippet(snippet, points['button' + i]) + } + } } + + // Add optional extra top button if (options.extraTopButton) snippets['top' + snippet] = new Snippet( snippet, - points[origin].shift(-90, len / options.buttons / 2) + points[origin].shift(-90, adjusted_len / total_buttons / 2) ) } diff --git a/designs/simone/config/index.js b/designs/simone/config/index.js index 4353ffde57c..a190ba1b2a6 100644 --- a/designs/simone/config/index.js +++ b/designs/simone/config/index.js @@ -7,7 +7,12 @@ const config = { name: 'simone', optionGroups: { ...simonConfig.optionGroups, - style: [...simonConfig.optionGroups.style, 'frontDarts', 'contour'], + style: [ + ...simonConfig.optionGroups.style, + 'frontDarts', + 'contour', + 'bustAlignedButtons', + ], advanced: [ ...simonConfig.optionGroups.advanced, 'bustDartAngle', @@ -38,6 +43,9 @@ const config = { frontDarts: { bool: false }, frontDartLength: { pct: 45, min: 30, max: 60 }, contour: { pct: 50, min: 30, max: 75 }, + bustAlignedButtons: { + dflt: 'Disabled', + list: ['Even spacing', 'Split spacing', 'Disabled'], }, }, } diff --git a/designs/simone/src/fba-front.js b/designs/simone/src/fba-front.js index 5755ebab443..ba2840a6253 100644 --- a/designs/simone/src/fba-front.js +++ b/designs/simone/src/fba-front.js @@ -65,6 +65,11 @@ export default (part) => { * it's based on the model's measurements. (bust span and high point shoulder (HPS) to bust). * So we need to find the bust point that would end up in the right place AFTER we do the FBA * For this, we'll just rotate it FBARot in the other direction + * In other words, we are pre-rotating points.bust now, so it gets rotated + * back to its original position during the FBA procedure. + * For convenience and clarity, we're defining points.realBustPoint here. + * However, points.bust will eventually be identical to points.realBustPoint + * after the FBA procedure. */ points.realBustPoint = points.bust.clone() points.bust = points.bust.rotate(FBARot * -1, points.armholePitch) @@ -276,7 +281,7 @@ export default (part) => { 'armholePitchCp1', ] for (let p of clone1) points[p] = points[`${p}_rot1`].clone() - let clone2 = ['hem', 'hips', 'hipsCp2', 'waistCp1', 'waist'] + let clone2 = ['hem', 'hips', 'hipsCp2', 'waistCp1', 'waist', 'bust'] for (let p of clone2) points[p] = points[`${p}_rot2`].clone() points.cfHem = new Point(points.cfHem.x, points.bustHem_rot2.y) points.waistCp2 = points.belowDartCpBottom_rot2.clone() @@ -287,6 +292,7 @@ export default (part) => { points.cfArmhole = new Point(0, points.armhole.y) points.cfWaist = new Point(0, points.waist.y) points.cfHips = new Point(0, points.hips.y) + points.cfBust = new Point(0, points.bust.y) // // Smooth out the armhole to avoid a kink where we rotated @@ -311,7 +317,8 @@ export default (part) => { for (let s in snippets) delete snippets[s] macro('sprinkle', { snippet: 'notch', - on: ['armhole', 'armholePitch', 'cfArmhole', 'cfWaist', 'cfHem', 'hips', 'waist', 'bust_rot2'], + on: ['armhole', 'armholePitch', 'cfArmhole', 'cfWaist', 'cfHem', 'hips', + 'waist', 'bust', 'cfBust',], }) points.logo = new Point(points.armhole.x / 2, points.armhole.y) snippets.logo = new Snippet('logo', points.logo) diff --git a/markdown/org/docs/patterns/simone/options/bustalignedbuttons/en.md b/markdown/org/docs/patterns/simone/options/bustalignedbuttons/en.md new file mode 100644 index 00000000000..a32fd253d34 --- /dev/null +++ b/markdown/org/docs/patterns/simone/options/bustalignedbuttons/en.md @@ -0,0 +1,15 @@ +--- +title: "Bust-aligned buttons" +--- + +Select an optional bust-aligned button spacing strategy. + +- Even spacing +- Split spacing +- Disabled + +By default bust-aligned button spacing is "Disabled", and center front buttons are spaced without regard to the bustline. Choosing "Even spacing" or "Split spacing" will ensure that there is a button positioned on the bustline to prevent possible gaping. + +- Even spacing: Button spacing is calculated for buttons above the bustline, and this spacing is used for all buttons. The `Button free length` setting is ignored for this option. This option might cause the bottommost button to be positioned in a non-optimal location. If this occurs, you may want to experiment with adding or subtracting a button to see if it produces a better design. +- Split spacing: Different button spacings are calculated and used for buttons above and below the bustline. Buttons above the bustline are spaced evenly. Buttons below the bustline are also spaced evenly, except for the topmost and bottommost buttons. The spacings for those buttons are shifted slightly to make the transition between the top and bottom spacings less noticeable. The `Button free length` setting is obeyed for this option. This option might cause non-optimal, visibly different spacings above and below the bustline. If this occurs, you may want to experiment with adding or subtracting a button to see if it produces a better design. +- Disabled: Even button spacing is calculated and used, without regard to the bustline. The `Button free length` setting is obeyed for this option. diff --git a/packages/i18n/src/locales/en/options/simone.yml b/packages/i18n/src/locales/en/options/simone.yml index ec0923fcdf4..5906739f46f 100644 --- a/packages/i18n/src/locales/en/options/simone.yml +++ b/packages/i18n/src/locales/en/options/simone.yml @@ -1,3 +1,7 @@ +bustAlignedButtons: + title: Bust-aligned buttons + description: Optional button spacing strategies to ensure a button at the bustline + bustDartAngle: title: Bust dart angle description: Controls the angle by which the (side) bust dart slopes downward