diff --git a/packages/sandy/config/index.js b/packages/sandy/config/index.js index 997c67e5ec3..02d2611b3ed 100644 --- a/packages/sandy/config/index.js +++ b/packages/sandy/config/index.js @@ -10,17 +10,15 @@ export default { difficulty: 3, tags: ["skirt", "top", "basics"], optionGroups: { - fit: ["waistbandPosition"], + fit: ["waistbandPosition", "waistbandShape"], style: [ "lengthBonus", "circleRatio", "waistbandWidth", - "waistbandShape", "waistbandOverlap", - "gathering", - "seamlessFullCircle", - "hemWidth" - ] + "gathering" + ], + construction: ["seamlessFullCircle", "hemWidth"] }, measurements: [ "naturalWaist", @@ -32,6 +30,9 @@ export default { waistband: "skirt" }, options: { + // Constants + minimumOverlap: 15, // Lower than this and we don't draw a button + // Bool seamlessFullCircle: { bool: false }, @@ -44,7 +45,7 @@ export default { circleRatio: { pct: 50, min: 20, max: 100 }, waistbandOverlap: { pct: 3, min: 0, max: 15 }, gathering: { pct: 0, min: 0, max: 200 }, - hemWidth: { pct: 1, min: 0, max: 5 }, + hemWidth: { pct: 2, min: 1, max: 10 }, // Lists waistbandShape: { diff --git a/packages/sandy/src/curved-waistband.js b/packages/sandy/src/curved-waistband.js index c533207a693..7c0e69be234 100644 --- a/packages/sandy/src/curved-waistband.js +++ b/packages/sandy/src/curved-waistband.js @@ -52,8 +52,61 @@ export default function(part) { // Complete pattern? if (complete) { - if (sa) { + points.title = points.in1Rotated + .shiftFractionTowards(points.ex1Rotated, 0.5) + .shift(0, 25); + macro("title", { + at: points.title, + nr: 2, + title: "curvedWaistband", + scale: 0.5 + }); + points.grainlineFrom = utils.curveIntersectsY( + points.ex2FlippedRotated, + points.ex2CFlippedRotated, + points.ex1CFlippedRotated, + points.ex1Rotated, + points.title.y + ); + points.grainlineTo = points.grainlineFrom.flipX(); + macro("grainline", { + from: points.grainlineFrom, + to: points.grainlineTo + }); + + if (store.get("waistbandOverlap") >= options.minimumOverlap) { + points.pivot = points.in2Rotated.shiftFractionTowards( + points.ex2Rotated, + 0.5 + ); + points.button = points.pivot + .shiftTowards(points.ex2Rotated, store.get("waistbandOverlap") / 2) + .rotate(-90, points.pivot); + points.buttonhole = points.button.flipX(); + snippets.button = new Snippet("button", points.button); + snippets.buttonhole = new Snippet("buttonhole", points.buttonhole).attr( + "data-rotate", + -1 * points.ex2FlippedRotated.angle(points.in2FlippedRotated) + ); + points.centerNotch = new Path() + .move(points.ex1Rotated) + .curve( + points.ex1CFlippedRotated, + points.ex2CFlippedRotated, + points.ex2FlippedRotated + ) + .shiftAlong(store.get("waistbandOverlap") / 2); + points.buttonNotch = new Path() + .move(points.ex2Rotated) + .curve(points.ex2CRotated, points.ex1CRotated, points.ex1Rotated) + .shiftAlong(store.get("waistbandOverlap")); + macro("sprinkle", { + snippet: "notch", + on: ["centerNotch", "buttonNotch", "ex2FlippedRotated"] + }); } + + if (sa) paths.sa = paths.seam.offset(sa * -1).attr("class", "fabric sa"); } // Paperless? diff --git a/packages/sandy/src/shared.js b/packages/sandy/src/shared.js index 2da5ba488d1..03f97d30579 100644 --- a/packages/sandy/src/shared.js +++ b/packages/sandy/src/shared.js @@ -1,4 +1,4 @@ -const draftRingSector = (part, rot, an, radIn, radEx) => { +const draftRingSector = (part, rot, an, radIn, radEx, rotate = false) => { let { utils, store, @@ -101,6 +101,13 @@ const draftRingSector = (part, rot, an, radIn, radEx) => { ]) points[id + "Rotated"] = points[id].rotate(rot, points.center); + if (rotate) { + // Rotate all points so the line from in1Rotated to ex1Rotated is vertical + let deg = 270 - points.in1Rotated.angle(points.ex1Rotated); + for (let id in points) { + points[id] = points[id].rotate(deg, points.in1Rotated); + } + } // Return the path of the full ring sector return new Path() .move(points.in2Flipped) diff --git a/packages/sandy/src/skirt.js b/packages/sandy/src/skirt.js index 5db6ef7085c..05ea189a87e 100644 --- a/packages/sandy/src/skirt.js +++ b/packages/sandy/src/skirt.js @@ -18,103 +18,6 @@ export default function(part) { macro } = part.shorthand(); - const oundExtended = (radius, angle = 90) => { - let arg = utils.deg2rad(angle / 2); - - return (radius * 4 * (1 - Math.cos(arg))) / Math.sin(arg) / 3; - }; - - const raftRingSector = (rot, an, radIn, radEx) => { - /** - * Calculates the distance of the control point for the internal - * and external arcs using bezierCircleExtended - */ - let distIn = roundExtended(radIn, an / 2); - let distEx = roundExtended(radEx, an / 2); - // The centre of the circles - points.center = new Point(0, 0); - - /** - * This function is expected to draft ring sectors for - * angles up to 180%. Since roundExtended works - * best for angles until 90º, we generate the ring - * sector using the half angle and then duplicate it - */ - - /** - * The first point of the internal arc, situated at - * a radIn distance below the centre - */ - points.in1 = points.center.shift(-90, radIn); - - /** - * The control point for 'in1'. It's situated at a - * distance $distIn calculated with bezierCircleExtended - * and the line between it and 'in1' is perpendicular to - * the line between 'in1' and the centre, so it's - * shifted in the direction 0º - */ - points.in1C = points.in1.shift(0, distIn); - - /** - * The second point of the internal arc, situated at - * a $radIn distance of the centre in the direction - * $an/2 - 90º - */ - points.in2 = points.center.shift(an / 2 - 90, radIn); - - /** - * The control point for 'in2'. It's situated at a - * distance $distIn calculated with bezierCircleExtended - * and the line between it and 'in2' is perpendicular to - * the line between 'in2' and the centre, so it's - * shifted in the direction $an/2 + 180º - */ - points.in2C = points.in2.shift(an / 2 + 180, distIn); - - /** - * The points for the external arc are generated in the - * same way, using $radEx and $distEx instead - */ - points.ex1 = points.center.shift(-90, radEx); - points.ex1C = points.ex1.shift(0, distEx); - points.ex2 = points.center.shift(an / 2 - 90, radEx); - points.ex2C = points.ex2.shift(an / 2 + 180, distEx); - - // Flip all the points to generate the full ring sector - for (let id of ["in2", "in2C", "in1C", "ex1C", "ex2C", "ex2"]) - points[id + "Flipped"] = points[id].flipX(); - - // Rotate all the points an angle rot - for (let id of [ - "in1", - "in1C", - "in2", - "in2C", - "ex1", - "ex1C", - "ex2", - "ex2C", - "in2Flipped", - "in2CFlipped", - "in1CFlipped", - "ex1CFlipped", - "ex2CFlipped", - "ex2Flipped" - ]) - points[id + "Rotated"] = points[id].rotate(rot, points.center); - - // Return the path of the full ring sector - return new Path() - .move(points.in2Flipped) - .curve(points.in2CFlipped, points.in1CFlipped, points.in1) - .curve(points.in1C, points.in2C, points.in2) - .line(points.ex2) - .curve(points.ex2C, points.ex1C, points.ex1) - .curve(points.ex1CFlipped, points.ex2CFlipped, points.ex2Flipped) - .close(); - }; - // Circumference of the top of the waistband, calculated from the waistbandPosition option store.set( "topCircumference", @@ -155,7 +58,6 @@ export default function(part) { measurements.naturalWaistToHip * options.waistbandPosition ); - console.log("STORE", store.data); let radiusWaist, an; if (options.seamlessFullCircle) { /** @@ -201,17 +103,103 @@ export default function(part) { let rot = an / 2; // Call draftRingSector to draft the part - paths.seam = draftRingSector(part, rot, an, radiusWaist, radiusHem).attr( - "class", - "fabric" - ); + paths.seam = draftRingSector( + part, + rot, + an, + radiusWaist, + radiusHem, + true + ).attr("class", "fabric"); // Anchor samples to the centre of the waist points.gridAnchor = points.in2Flipped.clone(); // Complete pattern? if (complete) { + macro("cutonfold", { + from: points.in2Flipped, + to: points.ex2Flipped, + grainline: true + }); + if (options.seamlessFullCircle) { + macro("cutonfold", { + from: points.ex1Rotated, + to: points.in1Rotated, + prefix: "double" + }); + } + points.logo = points.in2FlippedRotated.shiftFractionTowards( + points.ex2FlippedRotated, + 0.3 + ); + snippets.logo = new Snippet("logo", points.logo); + + points.title = points.in2FlippedRotated.shiftFractionTowards( + points.ex2FlippedRotated, + 0.5 + ); + macro("title", { at: points.title, nr: 1, title: "skirt" }); + + points.scalebox = points.in2FlippedRotated.shiftFractionTowards( + points.ex2FlippedRotated, + 0.7 + ); + macro("scalebox", { at: points.scalebox }); + + macro("sprinkle", { + snippet: "notch", + on: ["in1Rotated", "gridAnchor"] + }); + if (sa) { + paths.hemBase = new Path() + .move(points.ex1Rotated) + .curve( + points.ex1CFlippedRotated, + points.ex2CFlippedRotated, + points.ex2FlippedRotated + ) + .curve(points.ex1CFlipped, points.ex2CFlipped, points.ex2Flipped) + .offset( + store.get("fullLength") * options.lengthBonus * options.hemWidth * -1 + ); + paths.saBase = new Path() + .move(points.in2Flipped) + .curve(points.in2CFlipped, points.in1CFlipped, points.in2FlippedRotated) + .curve( + points.in2CFlippedRotated, + points.in1CFlippedRotated, + points.in1Rotated + ); + if (!options.seamlessFullCircle) + paths.saBase = paths.saBase.line(points.ex1Rotated); + paths.saBase = paths.saBase.offset(sa * -1); + + paths.hemBase.render = false; + paths.saBase.render = false; + + if (options.seamlessFullCircle) { + paths.sa = new Path() + .move(points.in2Flipped) + .line(paths.saBase.start()) + .join(paths.saBase) + .line(points.in1Rotated) + .move(points.ex1Rotated) + .line(paths.hemBase.start()) + .join(paths.hemBase) + .line(points.ex2Flipped) + .attr("class", "fabric sa"); + } else { + paths.sa = new Path() + .move(points.in2Flipped) + .line(paths.saBase.start()) + .join(paths.saBase) + .line(paths.hemBase.start()) + .join(paths.hemBase) + .line(points.ex2Flipped) + .attr("class", "fabric sa"); + } } } diff --git a/packages/sandy/src/straight-waistband.js b/packages/sandy/src/straight-waistband.js index edc695a9b09..25212050d4e 100644 --- a/packages/sandy/src/straight-waistband.js +++ b/packages/sandy/src/straight-waistband.js @@ -50,8 +50,39 @@ export default function(part) { // Complete pattern? if (complete) { - if (sa) { - } + points.title = points.center.shiftFractionTowards(points.centerRight, 0.5); + macro("title", { at: points.title, nr: 2, title: "straightWaistband" }); + points.grainlineFrom = points.centerLeft.shiftFractionTowards( + points.topLeft, + 0.5 + ); + points.grainlineTo = points.grainlineFrom.flipX(); + macro("grainline", { + from: points.grainlineFrom, + to: points.grainlineTo + }); + points.button = points.centerRight + .shiftFractionTowards(points.bottomRight, 0.5) + .shift(180, store.get("waistbandOverlap") / 2); + points.buttonhole = points.centerLeft + .shiftFractionTowards(points.bottomLeft, 0.5) + .shift(0, store.get("waistbandOverlap") / 2); + snippets.button = new Snippet("button", points.button); + snippets.buttonhole = new Snippet("buttonhole", points.buttonhole); + points.centerNotch = new Point( + (-1 * store.get("waistbandOverlap")) / 2, + points.bottomLeft.y + ); + points.buttonNotch = points.bottomRight.shift( + 180, + store.get("waistbandOverlap") + ); + macro("sprinkle", { + snippet: "notch", + on: ["centerNotch", "buttonNotch", "bottomLeft"] + }); + + if (sa) paths.sa = paths.seam.offset(sa).attr("class", "fabric sa"); } // Paperless?