diff --git a/config/changelog.yaml b/config/changelog.yaml index 72d2584fb58..247af167e8c 100644 --- a/config/changelog.yaml +++ b/config/changelog.yaml @@ -1,6 +1,10 @@ Unreleased: Added: + core: + - Added the `Path.combine()` method + - `Path.join()` is now variadico + - `Path.length()` now takes an parameter to include move operations in the length calculation lumina: - Initial release lumira: @@ -17,6 +21,10 @@ Unreleased: tristan: - Inital release + Deprecated: + core: + - Calling `Path.join` with a second parameter to indicate that the resulting paths most be closed is now deprecated and will be removed in FreeSewing v4. + Fixed: carlton: - Fixed a stray seam allowance path on the collar diff --git a/markdown/dev/reference/api/path/combine/en.md b/markdown/dev/reference/api/path/combine/en.md new file mode 100644 index 00000000000..8a108b24d8b --- /dev/null +++ b/markdown/dev/reference/api/path/combine/en.md @@ -0,0 +1,58 @@ +--- +title: Path.combine() +--- + +The `Path.combines()` method combines this path with one or more other paths +into a single Path instance. + +Any gaps in the path (caused by move operations) will be left as-is, rather +than joined with a line. If that's not what you want, you should use +[`Path.join()`](/reference/api/path/join) instead. + +## Signature + +```js +Path path.combine(path other) +``` + +## Examples + + +```js +({ Point, points, Path, paths, part }) => { + + points.A1 = new Point(0, 0) + points.A2 = new Point(60, 0) + points.B1 = new Point(0, 10) + points.B2 = new Point(60, 10) + points.C1 = new Point(0, 20) + points.C2 = new Point(60, 20) + + paths.path1 = new Path() + .move(points.A1) + .line(points.A2) + .setClass("various") + + paths.path2 = new Path() + .move(points.B1) + .line(points.B2) + .setClass("note") + + paths.path3 = new Path() + .move(points.C1) + .line(points.C2) + .setClass("canvas") + + paths.combo = paths.path1 + .combine(paths.path2, paths.path3) + .setClass("lining dotted") + + return part +} +``` + + + +## Notes + +`Path.combine()` method is _variadic_, so you can pass multiple paths to join diff --git a/markdown/dev/reference/api/path/join/en.md b/markdown/dev/reference/api/path/join/en.md index 7aec94cad14..68dbebfb235 100644 --- a/markdown/dev/reference/api/path/join/en.md +++ b/markdown/dev/reference/api/path/join/en.md @@ -2,7 +2,11 @@ title: Path.join() --- -The `Path.join()` method joins this path with another path. +The `Path.join()` method joins this path with one or more other paths. + +Any gaps in the path (caused by move operations) will be filled-in with a line. +If that's not what you want, you should use +[`Path.combine()`](/reference/api/path/combine) instead. ## Signature @@ -17,24 +21,30 @@ Path path.join(path other) ```js ({ Point, points, Path, paths, part }) => { - points.A = new Point(45, 60) - points.B = new Point(10, 30) - points.BCp2 = new Point(40, 20) - points.C = new Point(90, 30) - points.CCp1 = new Point(50, -30) + points.A1 = new Point(0, 0) + points.A2 = new Point(60, 0) + points.B1 = new Point(0, 10) + points.B2 = new Point(60, 10) + points.C1 = new Point(0, 20) + points.C2 = new Point(60, 20) paths.path1 = new Path() - .move(points.A) - .line(points.B) + .move(points.A1) + .line(points.A2) .setClass("various") paths.path2 = new Path() - .move(points.B) - .curve(points.BCp2, points.CCp1, points.C) + .move(points.B1) + .line(points.B2) .setClass("note") + paths.path3 = new Path() + .move(points.C1) + .line(points.C2) + .setClass("canvas") + paths.joint = paths.path1 - .join(paths.path2) + .join(paths.path2, paths.path3) .setClass("lining dotted") return part @@ -45,4 +55,5 @@ Path path.join(path other) ## Notes -You cannot join a closed path to another path +- `Path.join()` is _variadic_, so you can pass multiple paths to join +- You cannot join a closed path to another path diff --git a/markdown/dev/reference/api/path/length/en.md b/markdown/dev/reference/api/path/length/en.md index 85924ec412a..4d4975fe014 100644 --- a/markdown/dev/reference/api/path/length/en.md +++ b/markdown/dev/reference/api/path/length/en.md @@ -7,50 +7,43 @@ The `Path.length()` method returns the length of the path. ## Signature ```js -float path.length() +float path.length(bool withMoves = false) ``` ## Example ```js -({ Point, points, Path, paths, macro, utils, units, part }) => { +({ Point, points, Path, paths, units, part }) => { - points.A = new Point(45, 60) - points.B = new Point(10, 30) - points.BCp2 = new Point(40, 20) - points.C = new Point(90, 30) - points.CCp1 = new Point(50, -30) + points.A1 = new Point(0, 0) + points.A2 = new Point(160, 0) + points.B1 = new Point(0, 10) + points.B2 = new Point(160, 10) + points.C1 = new Point(0, 20) + points.C2 = new Point(160, 20) - paths.AB = new Path() - .move(points.A) - .line(points.B) + paths.path1 = new Path() + .move(points.A1) + .line(points.A2) + .move(points.B1) + .line(points.B2) + .move(points.C1) + .line(points.C2) + .setClass("various") - paths.BC = new Path() - .move(points.B) - .curve(points.BCp2, points.CCp1, points.C) + points.label1 = new Point(25, 8).addText('Total length = ' + units(paths.path1.length())) + points.label2 = new Point(25, 18).addText('Total length with moves = ' + units(paths.path1.length(true))) - const lengthABC = paths.AB.length() + paths.BC.length() - - macro("pd", { - path: new Path().move(points.B).line(points.A), - d: 10 - }) - - macro("pd", { - path: new Path().move(points.B).curve(points.BCp2, points.CCp1, points.C), - d: -10 - }) - - points.label = new Point(25, 40) - .addText('Total length = ' + units(lengthABC)) - - // Set a path to prevent clipping - paths.noclip = new Path() - .move(new Point(10, -15)) - .move(new Point(90, 60)) return part } ``` + +## Notes + +By default, `Path.length()` will measure the combined length of all drawing operations in the Path, but skip +over gaps in the path (caused by move operations). +If you want the full length of the Path, including move operations, pass `true` to `Path.length()`. + diff --git a/packages/core/src/path.mjs b/packages/core/src/path.mjs index 22fcbfb846c..52f0f75e883 100644 --- a/packages/core/src/path.mjs +++ b/packages/core/src/path.mjs @@ -266,6 +266,16 @@ Path.prototype.close = function () { return this } +/** + * Combines one or more Paths into a single Path instance + * + * @param {array} paths - The paths to combine + * @return {Path} combo - The combined Path instance + */ +Path.prototype.combine = function (...paths) { + return __combinePaths(this, ...paths) +} + /** * Adds a curve operation via cp1 & cp2 to Point to * @@ -512,28 +522,63 @@ Path.prototype.intersectsY = function (y) { /** * Joins this Path with that Path, and closes them if wanted * - * @param {Path} that - The Path to join this Path with - * @param {bool} closed - Whether or not to close the joint Path + * The legacy (prior to v3.2) form of this method too two parameters: + * - The Path to join this path with + * - A boolean expressing whether the joined path should be closed + * In retrospect, that was kind of a dumb idea, because if the path + * needs tobe closed, you can juse chain the .join() with a .close() + * + * So now, this method is variadic, and it will join as many paths as you want. + * However, we keep it backwards compatible, and raise a deprecation warning when used that way. + * + * @param {array} paths - The Paths to join this Path with * @return {Path} joint - The joint Path instance */ -Path.prototype.join = function (that, closed = false) { - if (that instanceof Path !== true) +Path.prototype.join = function (...paths) { + if (paths.length < 1) { this.log.error('Called `Path.join(that)` but `that` is not a `Path` object') - return __joinPaths([this, that], closed) + return this + } + + /* + * Check for legacy signature + */ + if (paths.length === 2 && [true, false].includes(paths[1])) { + this.log.warn( + '`Path.join()` was called with the legacy signature passing a bool as second parameter. This is deprecated and will be removed in FreeSewing v4' + ) + return paths[1] ? __joinPaths([this, paths[0]]).close() : __joinPaths([this, paths[0]]) + } + + /* + * New variadic approach + */ + let i = 0 + for (const path of paths) { + if (path instanceof Path !== true) + this.log.error( + `Called \`Path.join(paths)\` but the path with index \`${i}\` is not a \`Path\` object` + ) + i++ + } + + return __joinPaths([this, ...paths]) } /** * Return the length of this Path * + * @param {bool} withMoves - Include length of move operations inside the path * @return {float} length - The length of this path */ -Path.prototype.length = function () { +Path.prototype.length = function (withMoves = false) { let current, start let length = 0 for (let i in this.ops) { let op = this.ops[i] if (op.type === 'move') { - start = op.to + if (typeof start === 'undefined') start = op.to + else if (withMoves) length += current.dist(op.to) } else if (op.type === 'line') { length += current.dist(op.to) } else if (op.type === 'curve') { @@ -861,8 +906,8 @@ Path.prototype.split = function (point) { } } - if (firstHalf.length > 0) firstHalf = __joinPaths(firstHalf, false) - if (secondHalf.length > 0) secondHalf = __joinPaths(secondHalf, false) + if (firstHalf.length > 0) firstHalf = __joinPaths(firstHalf) + if (secondHalf.length > 0) secondHalf = __joinPaths(secondHalf) return [firstHalf, secondHalf] } @@ -946,9 +991,9 @@ Path.prototype.trim = function () { first = false } let joint - if (trimmedStart.length > 0) joint = __joinPaths(trimmedStart, false).join(glue) + if (trimmedStart.length > 0) joint = __joinPaths(trimmedStart).join(glue) else joint = glue - if (trimmedEnd.length > 0) joint = joint.join(__joinPaths(trimmedEnd, false)) + if (trimmedEnd.length > 0) joint = joint.join(__joinPaths(trimmedEnd)) return joint.trim() } @@ -1187,6 +1232,21 @@ function __bbbbox(boxes) { return { topLeft: new Point(minX, minY), bottomRight: new Point(maxX, maxY) } } +/** + * Combines path segments into a single path instance + * + * @private + * @param {Array} paths - An Array of Path objects + * @return {object} path - A Path instance + */ +function __combinePaths(...paths) { + const joint = new Path().__withLog(paths[0].log) + const ops = [] + for (const path of paths) joint.ops.push(...path.ops) + + return joint +} + /** * Returns an object holding topLeft and bottomRight Points of the bounding box of a curve * @@ -1208,10 +1268,9 @@ function __curveBoundingBox(curve) { * * @private * @param {Array} paths - An Array of Path objects - * @param {bool} closed - Whether or not to close the joined paths * @return {object} path - A Path instance */ -function __joinPaths(paths, closed = false) { +function __joinPaths(paths) { let joint = new Path().__withLog(paths[0].log).move(paths[0].ops[0].to) let current for (let p of paths) { @@ -1231,7 +1290,6 @@ function __joinPaths(paths, closed = false) { if (op.to) current = op.to } } - if (closed) joint.close() return joint } @@ -1335,7 +1393,7 @@ function __pathOffset(path, distance) { if (!start) start = current } - return __joinPaths(offset, closed) + return closed ? __joinPaths(offset).close() : __joinPaths(offset) } /** diff --git a/packages/core/tests/path.test.mjs b/packages/core/tests/path.test.mjs index 6410e41ef35..1cbf31f2065 100644 --- a/packages/core/tests/path.test.mjs +++ b/packages/core/tests/path.test.mjs @@ -197,6 +197,7 @@ describe('Path', () => { .curve(new Point(0, 40), new Point(123, 34), new Point(230, 4)) const joint = curve.join(line) expect(joint.ops.length).to.equal(4) + expect(joint.ops[2].type).to.equal('line') }) it('Should join paths that have noop operations', () => { @@ -209,7 +210,7 @@ describe('Path', () => { expect(joint.ops.length).to.equal(6) }) - it('Should throw error when joining a closed paths', () => { + it('Should throw error when joining a closed path', () => { const line = new Path().move(new Point(0, 0)).line(new Point(0, 40)) const curve = new Path() .move(new Point(123, 456)) @@ -218,6 +219,16 @@ describe('Path', () => { expect(() => curve.join(line)).to.throw() }) + it('Should combine paths', () => { + const line = new Path().move(new Point(0, 0)).line(new Point(0, 40)) + const curve = new Path() + .move(new Point(123, 456)) + .curve(new Point(0, 40), new Point(123, 34), new Point(230, 4)) + const combo = curve.combine(line) + expect(combo.ops.length).to.equal(4) + expect(combo.ops[2].type).to.equal('move') + }) + it('Should shift along a line', () => { const line = new Path().move(new Point(0, 0)).line(new Point(0, 40)) expect(line.shiftAlong(20).y).to.equal(20) diff --git a/packages/core/tests/pattern-draft.test.mjs b/packages/core/tests/pattern-draft.test.mjs index 48c59941280..2ab8df67d73 100644 --- a/packages/core/tests/pattern-draft.test.mjs +++ b/packages/core/tests/pattern-draft.test.mjs @@ -155,7 +155,6 @@ describe('Pattern', () => { const pattern = new Test() pattern.draft() - console.log(pattern.setStores[pattern.activeSet].logs.error[0]) expect(pattern.setStores[pattern.activeSet].logs.error[0]).to.include( 'Could not inject part `otherPart` into part `front`' ) diff --git a/sites/dev/jargon.mjs b/sites/dev/jargon.mjs index bb90069b0e7..b652d1ddcf0 100644 --- a/sites/dev/jargon.mjs +++ b/sites/dev/jargon.mjs @@ -1,4 +1,5 @@ export const jargon = { cjs: 'CJS stands for CommonJS, it is the JavaScript module format popularized by NodeJS, but now increasingly phased out in favor of ESM', esm: 'ESM stands for EcmaScript Module, it is the standardized module syntax in JavaScript', + variadic: 'A variadic function is a function that accepts a variable number of arguments', }