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',
}